diff --git a/.github/workflows/ci-bonsai.yml b/.github/workflows/ci-bonsai.yml
index 4c8364a958..6fcc61658f 100644
--- a/.github/workflows/ci-bonsai.yml
+++ b/.github/workflows/ci-bonsai.yml
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- pyver: [py311, py312]
+ pyver: [py311, py312, py313]
config:
- {
name: "Windows Build",
@@ -42,6 +42,11 @@ jobs:
name: "MacOS ARM Build",
short_name: macosm1,
}
+ exclude:
+ # Python 3.13 is needed for Blender 5.1+ and Blender dropped Intel Mac support in 5.0.
+ - pyver: py313
+ config:
+ short_name: macos
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
diff --git a/.github/workflows/ci-black-formatting.yaml b/.github/workflows/ci-lint.yaml
similarity index 91%
rename from .github/workflows/ci-black-formatting.yaml
rename to .github/workflows/ci-lint.yaml
index f081f02945..6dc18453ad 100644
--- a/.github/workflows/ci-black-formatting.yaml
+++ b/.github/workflows/ci-lint.yaml
@@ -1,4 +1,4 @@
-name: ci-black-formatting
+name: ci-lint
on:
push:
@@ -30,6 +30,7 @@ jobs:
uv tool install ruff
uv tool install black
uv tool install poethepoet
+ uv tool install ty
# black doesn't catch all syntax errors, so we check them explicitly.
- name: Check syntax errors
@@ -57,6 +58,13 @@ jobs:
black --diff --check . | black-codeclimate | python .github/workflows/black_to_github_annotations.py
continue-on-error: true
+ - name: ty check
+ id: ty
+ run: |
+ poe ty-venv
+ poe ty
+ continue-on-error: true
+
- name: Ruff check
id: ruff
run: |
@@ -105,4 +113,7 @@ jobs:
if [ "${{ steps.ruff.outcome }}" != "success" ]; then
echo "::error::Ruff check failed, see Summary or 'ruff' step for the details." && ERROR=1
fi
+ if [ "${{ steps.ty.outcome }}" != "success" ]; then
+ echo "::error::ty check failed, see 'ty check' step for the details." && ERROR=1
+ fi
exit $ERROR
diff --git a/.github/workflows/ci-pyodide-wasm-release.yml b/.github/workflows/ci-pyodide-wasm-release.yml
new file mode 100644
index 0000000000..eff7bc30d9
--- /dev/null
+++ b/.github/workflows/ci-pyodide-wasm-release.yml
@@ -0,0 +1,43 @@
+name: Release Pyodide WASM Wheel
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build-and-push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout IfcOpenShell
+ uses: actions/checkout@v6
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+
+ - name: Build wheel
+ working-directory: pyodide
+ run: uv run pack_wheel.py --build
+
+ - name: Find wheel
+ id: wheel
+ run: |
+ WHEEL=$(ls pyodide/dist/ifcopenshell-*.whl)
+ echo "path=$WHEEL" >> $GITHUB_OUTPUT
+ echo "name=$(basename $WHEEL)" >> $GITHUB_OUTPUT
+
+ - name: Checkout wasm-wheels
+ uses: actions/checkout@v6
+ with:
+ repository: IfcOpenShell/wasm-wheels
+ path: wasm-wheels
+ token: ${{ secrets.WASM_WHEELS_TOKEN }}
+
+ - name: Commit and push wheel to wasm-wheels
+ run: |
+ WHEEL_NAME="${{ steps.wheel.outputs.name }}"
+ cp "${{ steps.wheel.outputs.path }}" "wasm-wheels/$WHEEL_NAME"
+ cd wasm-wheels
+ git config user.name "IfcOpenBot"
+ git config user.email "ifcopenbot@ifcopenshell.org"
+ git add "$WHEEL_NAME"
+ git commit -m "Add $WHEEL_NAME"
+ git push origin main
diff --git a/.github/workflows/publish-aichat-app.yaml b/.github/workflows/publish-aichat-app.yaml
index 776781428e..20369577f5 100644
--- a/.github/workflows/publish-aichat-app.yaml
+++ b/.github/workflows/publish-aichat-app.yaml
@@ -32,7 +32,7 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Checkout intermediate Pages repo
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
repository: IfcOpenShell/aichat_ifcopenshell_org_static_html
ref: gh-pages
@@ -42,7 +42,7 @@ jobs:
run: |
rsync -av --delete --exclude='.git/' src/ifcchat/ output/
- name: Setup Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Download wheels
diff --git a/.github/workflows/publish-pyodide-demo-app.yml b/.github/workflows/publish-pyodide-demo-app.yml
index 9f4c3ddbf8..6b0141fc29 100644
--- a/.github/workflows/publish-pyodide-demo-app.yml
+++ b/.github/workflows/publish-pyodide-demo-app.yml
@@ -32,7 +32,7 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Checkout intermediate Pages repo
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
repository: IfcOpenShell/wasm_ifcopenshell_org_static_html
ref: gh-pages
diff --git a/nix/build-all.py b/nix/build-all.py
index 46b7b5c30c..f5436262d2 100644
--- a/nix/build-all.py
+++ b/nix/build-all.py
@@ -1515,7 +1515,7 @@ if "IfcOpenShell-Python" in targets:
)
# Copy setup.py where pyodide build system expects it.
shutil.copy(REPO_PATH / "pyodide" / "setup.py", REPO_PATH)
- # Empty pyproject so it's contents won't affect the resulting wheelthe the
+ # Empty pyproject so it's contents won't affect the resulting wheel
# otherwise the wheel will use version and dependencies from toml, not setup.py.
(REPO_PATH / "pyproject.toml").write_text("")
diff --git a/pyodide/build_pyodide.sh b/pyodide/build_pyodide.sh
index 20ad946162..db5c5f08b0 100755
--- a/pyodide/build_pyodide.sh
+++ b/pyodide/build_pyodide.sh
@@ -14,18 +14,11 @@ source .venv/bin/activate
uv pip install pyodide-build
# `uv run` is required, so xbuildenv would skip using `pip`.
uv run pyodide xbuildenv install
+uv run pyodide xbuildenv install-emscripten
-# Emscripten doesn't come with xbuildenv.
-if [ ! -d emsdk ]; then
- git clone https://github.com/emscripten-core/emsdk
-fi
-pushd emsdk
-PYODIDE_EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version)
-./emsdk install ${PYODIDE_EMSCRIPTEN_VERSION}
-./emsdk activate ${PYODIDE_EMSCRIPTEN_VERSION}
-source emsdk_env.sh
+EMSDK_ROOT=$(pyodide config get emscripten_dir)
+source ${EMSDK_ROOT}/emsdk_env.sh
which emcc
-popd
mkdir -p packages/ifcopenshell
VERSION=`cat IfcOpenShell/VERSION`
diff --git a/pyodide/pack_wheel.py b/pyodide/pack_wheel.py
new file mode 100644
index 0000000000..7b6c2a63d5
--- /dev/null
+++ b/pyodide/pack_wheel.py
@@ -0,0 +1,232 @@
+#
+# /// script
+# # Latest Pyodide build env versions are listed here:
+# # https://pyodide.github.io/pyodide/api/pyodide-cross-build-environments.json
+# # https://github.com/pyodide/pyodide-build/blob/main/pyodide_build/xbuildenv_releases.py
+# requires-python = "==3.13.2"
+# dependencies = [
+# "requests",
+# "setuptools",
+# ]
+# ///
+"""
+Pack an IfcOpenShell WASM wheel using Pyodide build system.
+
+Usage:
+ uv run make_wheel.py # Show this help
+ uv run make_wheel.py --build # Build wheel
+ uv run make_wheel.py --clean # Clean build artifacts and exit
+"""
+
+import argparse
+import os
+import re
+import shutil
+import subprocess
+import time
+import zipfile
+from pathlib import Path
+from urllib.parse import quote
+
+import requests
+
+# Get repo root (parent of this script's parent directory)
+REPO_ROOT = Path(__file__).parent.parent
+PYODIDE_DIR = REPO_ROOT / "pyodide"
+BUILD_DIR = PYODIDE_DIR / "build"
+
+# Hardcoded path (Windows packing workaround with --dev flag)
+PYODIDE_BUILD = Path(r"L:\Projects\Github\pyodide-build")
+
+# Wheel platform tag (from PYODIDE_EMSCRIPTEN_VERSION in pyodide-build/Makefile.envs)
+WHEEL_PLATFORM_TAG = "emscripten_4_0_9_wasm32"
+
+# Location where ifcopenshell will be extracted
+IFCOPENSHELL_DIR = PYODIDE_DIR / "ifcopenshell"
+
+
+class WheelBuilder:
+ @staticmethod
+ def extract_ifcopenshell_from_git(dst: Path) -> None:
+ """Extract ifcopenshell directory from git repo into destination."""
+ Tools.rmrf(dst)
+
+ print(f"Extracting ifcopenshell from git to {dst}...")
+ # Use git ls-files piped to git checkout-index to avoid copying
+ # untracked or ignored files from the actual repo.
+ ls_proc = subprocess.Popen(
+ ["git", "ls-files", "-z", "src/ifcopenshell-python/ifcopenshell"],
+ cwd=REPO_ROOT,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ checkout_proc = subprocess.Popen(
+ ["git", "checkout-index", "-z", "--prefix", "pyodide/", "--stdin"],
+ cwd=REPO_ROOT,
+ stdin=ls_proc.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ assert ls_proc.stdout is not None
+ ls_proc.stdout.close()
+ checkout_proc.communicate()
+
+ if checkout_proc.returncode != 0:
+ assert checkout_proc.stderr is not None
+ raise RuntimeError(f"Failed to extract: {checkout_proc.stderr.decode()}")
+
+ # Move src/ifcopenshell-python/ifcopenshell to ifcopenshell.
+ temp_src = PYODIDE_DIR / "src" / "ifcopenshell-python" / "ifcopenshell"
+ shutil.move(temp_src, dst)
+
+ # Clean up temporary src directory.
+ Tools.rmrf(PYODIDE_DIR / "src")
+
+ print("✓ Extracted ifcopenshell from git")
+
+ @staticmethod
+ def get_wheel_url(makefile_path: Path) -> str:
+ """Get S3 wheel URL based on BINARY_VERSION and BUILD_COMMIT from Makefile."""
+
+ def parse_makefile_vars() -> dict[str, str]:
+ content = makefile_path.read_text()
+ vars: dict[str, str] = {}
+ for match in re.finditer(r"^(BINARY_VERSION|BUILD_COMMIT):=(.+)$", content, re.MULTILINE):
+ vars[match.group(1)] = match.group(2).strip()
+ return vars
+
+ vars: dict[str, str] = parse_makefile_vars()
+ binary_version = vars["BINARY_VERSION"]
+ build_commit = vars["BUILD_COMMIT"]
+ filename = f"ifcopenshell-{binary_version}+{build_commit}-cp313-cp313-pyodide_2025_0_wasm32.whl"
+ encoded_filename = quote(filename, safe="")
+ return f"https://s3.amazonaws.com/ifcopenshell-builds/{encoded_filename}"
+
+ @staticmethod
+ def download_and_extract_so(url: str, build_dir: Path) -> tuple[Path, Path]:
+ """Download wheel from URL and extract .so and .py files."""
+ py_wrapper_filename = "ifcopenshell_wrapper.py"
+ build_dir.mkdir(parents=True, exist_ok=True)
+
+ wheel_path = build_dir / url.rsplit("/", 1)[-1]
+
+ if wheel_path.exists():
+ print(f"Using cached wheel: {wheel_path}")
+ else:
+ print(f"Downloading {url}...")
+ response = requests.get(url)
+ response.raise_for_status()
+ wheel_path.write_bytes(response.content)
+
+ print("Extracting _ifcopenshell_wrapper files...")
+ with zipfile.ZipFile(wheel_path) as zf:
+ so_files = [f for f in zf.namelist() if f.endswith(".so")]
+ py_files = [f for f in zf.namelist() if f.endswith(py_wrapper_filename)]
+
+ assert so_files, "No .so file found in wheel"
+ assert py_files, f"No {py_wrapper_filename} file found in wheel"
+
+ so_file = so_files[0]
+ so_dst = build_dir / Path(so_file).name
+ so_dst.write_bytes(zf.read(so_file))
+
+ py_file = py_files[0]
+ py_dst = build_dir / Path(py_file).name
+ py_dst.write_bytes(zf.read(py_file))
+
+ return so_dst, py_dst
+
+
+class Tools:
+ @staticmethod
+ def run(
+ cmd: list[str],
+ cwd: Path | None = None,
+ ) -> None:
+ print(f"$ {' '.join(cmd)}")
+ subprocess.check_call(cmd, cwd=cwd)
+
+ @staticmethod
+ def create_symlink(dst: Path, src: Path) -> None:
+ Tools.rmrf(dst)
+ dst.symlink_to(src)
+
+ @staticmethod
+ def rmrf(path: Path) -> None:
+ if path.exists() or path.is_symlink():
+ if path.is_dir() and not path.is_symlink():
+ shutil.rmtree(path)
+ else:
+ path.unlink()
+
+
+def clean() -> None:
+ """Remove build artifacts."""
+ paths_to_remove = (
+ BUILD_DIR,
+ PYODIDE_DIR / ".pyodide_build",
+ PYODIDE_DIR / "dist",
+ PYODIDE_DIR / "ifcopenshell.egg-info",
+ PYODIDE_DIR / "src",
+ IFCOPENSHELL_DIR,
+ )
+ for path in paths_to_remove:
+ if path.exists() or path.is_symlink():
+ print(f"Removing {path}...")
+ Tools.rmrf(path)
+ print("✓ Clean complete")
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description=__doc__, add_help=False)
+ parser.add_argument("--build", action="store_true", help="Build the wheel")
+ parser.add_argument("--clean", action="store_true", help="Clean build folder")
+ parser.add_argument(
+ "--dev",
+ action="store_true",
+ help="Use editable pyodide-build from hardcoded path (Windows packing workaround)",
+ )
+ args = parser.parse_args()
+
+ if not args.build and not args.clean:
+ print(__doc__)
+ return
+
+ if args.clean:
+ clean()
+ return
+
+ start_time = time.time()
+
+ WheelBuilder.extract_ifcopenshell_from_git(IFCOPENSHELL_DIR)
+
+ print("Downloading and extracting _ifcopenshell_wrapper files...")
+ makefile = REPO_ROOT / "src" / "ifcopenshell-python" / "Makefile"
+ wheel_url = WheelBuilder.get_wheel_url(makefile)
+ so_file, py_file = WheelBuilder.download_and_extract_so(wheel_url, BUILD_DIR)
+
+ Tools.create_symlink(IFCOPENSHELL_DIR / Path(so_file).name, so_file)
+ Tools.create_symlink(IFCOPENSHELL_DIR / Path(py_file).name, py_file)
+
+ print("Installing pyodide-build...")
+ if args.dev:
+ Tools.run(["uv", "pip", "install", "-e", str(PYODIDE_BUILD)])
+ else:
+ Tools.run(["uv", "pip", "install", "pyodide-build"])
+
+ print("Building with pyodide...")
+ # Use --no-isolation due to pyodide-build Windows support issues:
+ # symlink_unisolated_packages fails with missing `_sysconfigdata_$(CPYTHON_ABI_FLAGS)_emscripten_wasm32-emscripten.py`.
+ # Hardcode platform name since pyodide doesn't yet support overriding wheel tags on Windows.
+ #
+ # Use `LEGACY_PLATFORM` since pyodide 0.34.1 introduced new tag for wheels `pyemscripten`,
+ # which doesn't work with pyodide itself yet - https://github.com/pyodide/pyodide/issues/6177.
+ os.environ["USE_LEGACY_PLATFORM"] = "1"
+ Tools.run(["pyodide", "build", f"-C--build-option=--plat-name={WHEEL_PLATFORM_TAG}"])
+
+ elapsed = time.time() - start_time
+ print(f"\n✓ Done! ({elapsed:.1f}s)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyodide/setup.py b/pyodide/setup.py
index 9678de0ac3..474a0b45a4 100644
--- a/pyodide/setup.py
+++ b/pyodide/setup.py
@@ -2,12 +2,16 @@
# because `tool.setuptools.ext-modules` is still experimental in pyproject.toml
# and we need it to get the wheel suffix right.
import os
+import sys
from pathlib import Path
import tomllib
from setuptools import Extension, find_packages, setup
+from setuptools.command.build_ext import build_ext
-REPO_FOLDER = Path(__file__).parent
+# Detect repo folder: if setup.py is in pyodide folder, go to parent
+SETUP_DIR = Path(__file__).parent
+REPO_FOLDER = SETUP_DIR.parent if SETUP_DIR.name == "pyodide" else SETUP_DIR
def get_version() -> str:
@@ -25,6 +29,39 @@ def get_dependencies() -> list[str]:
return dependencies
+class UnixBuildExt(build_ext):
+ """Customize ``build_ext`` to support packing on Windows."""
+
+ def finalize_options(self):
+ from distutils import sysconfig
+
+ super().finalize_options()
+ if sys.platform == "win32":
+ self.compiler = "unix"
+
+ # Configure sysconfig for Windows builds
+ # CCSHARED is the only variable that's not customizable with env vars.
+ # Basically avoiding this:
+ # File ".venv\Lib\site-packages\setuptools\_distutils\sysconfig.py", line 366, in customize_compiler
+ # compiler_so=cc_cmd + ' ' + ccshared,
+ # ~~~~~~~~~~~~~^~~~~~~~~~
+ # TypeError: can only concatenate str (not "NoneType") to str
+ sysconfig.get_config_vars() # Initialize config cache
+ if sysconfig._config_vars.get("CCSHARED") is None:
+ sysconfig._config_vars["CCSHARED"] = "-fPIC"
+ # Override compiler type before it's instantiated
+
+ # Set Emscripten compiler environment variables
+ os.environ["CC"] = "emcc"
+ os.environ["CXX"] = "em++"
+ os.environ["CFLAGS"] = ""
+ os.environ["CXXFLAGS"] = ""
+ os.environ["LDSHARED"] = "emcc -shared"
+ os.environ["AR"] = "emar"
+ os.environ["ARFLAGS"] = "rcs"
+ os.environ["SETUPTOOLS_EXT_SUFFIX"] = ".cpython-313-wasm32-emscripten.so"
+
+
setup(
name="ifcopenshell",
version=get_version(),
@@ -44,4 +81,5 @@ setup(
},
# Has to provide extension to get the correct wheel suffix.
ext_modules=[Extension("ifcopenshell._ifcopenshell_wrapper", sources=[])],
+ cmdclass={"build_ext": UnixBuildExt},
)
diff --git a/pyproject.toml b/pyproject.toml
index 330f6150d0..eb6b4620d7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,8 +3,9 @@ name = "IfcOpenShell"
version = "0.0.0"
dependencies = [
"black==26.3.1",
- "ruff==0.15.8",
+ "ruff==0.15.9",
"poethepoet",
+ "ty==0.0.29",
"gersemi==0.26.1",
]
@@ -28,6 +29,9 @@ extend-exclude = '''
reportInvalidTypeForm = false
disableBytesTypePromotions = true
reportUnnecessaryTypeIgnoreComment = true
+reportRedeclaration = false
+# Ignore warnings from bpy stubs missing actual source files.
+reportMissingModuleSource = false
# Pylance doesn't respect gitignore, so we have to exclude files manually here
# to avoid VS Code slowing down.
# https://github.com/microsoft/pylance-release/issues/5169
@@ -84,7 +88,6 @@ all = "ignore"
# Structural rules (no deep type inference needed, easier to adapt).
abstract-method-in-final-class = "error"
ambiguous-protocol-member = "error"
-byte-string-type-annotation = "error"
conflicting-declarations = "error"
conflicting-metaclass = "error"
cyclic-class-definition = "error"
@@ -96,7 +99,6 @@ empty-body = "error"
escape-character-in-forward-annotation = "error"
final-on-non-method = "error"
final-without-value = "error"
-fstring-type-annotation = "error"
ignore-comment-unknown-rule = "error"
implicit-concatenated-string-type-annotation = "error"
inconsistent-mro = "error"
@@ -224,7 +226,7 @@ ty.sequence = ["ty-bonsai", "ty-ios"]
ty.help = "Run ty type checker. Requires ty-venv to be set up first."
ty-bonsai = "ty check src/bonsai --python=src/bonsai/.venv"
-ty-venv.sequence = ["ty-venv-bonsai", "ty-venv-ios"]
+ty-venv.sequence = ["bonsai-deps", "ty-venv-bonsai", "ty-venv-ios"]
ty-venv-bonsai.sequence = [
{cmd = "uv venv src/bonsai/.venv --python=3.11 --allow-existing"},
diff --git a/src/bonsai/bonsai/bim/module/aggregate/decorator.py b/src/bonsai/bonsai/bim/module/aggregate/decorator.py
index 2cd1c1bca0..eb389a58bd 100644
--- a/src/bonsai/bonsai/bim/module/aggregate/decorator.py
+++ b/src/bonsai/bonsai/bim/module/aggregate/decorator.py
@@ -101,7 +101,7 @@ class AggregateDecorator:
cls.is_installed = False
def dotted_line_shader(self):
- vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
+ vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
vert_out.smooth("FLOAT", "v_ArcLength")
shader_info = gpu.types.GPUShaderCreateInfo()
diff --git a/src/bonsai/bonsai/bim/module/attribute/operator.py b/src/bonsai/bonsai/bim/module/attribute/operator.py
index 13901c0f81..8069aa29ce 100644
--- a/src/bonsai/bonsai/bim/module/attribute/operator.py
+++ b/src/bonsai/bonsai/bim/module/attribute/operator.py
@@ -295,13 +295,13 @@ class ExplorerShowUIPopup(bpy.types.Operator):
bl_description = "Show Explorer UI to select element as attribute value or edit it."
bl_options = {"REGISTER", "UNDO"}
- ifc_class: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ ifc_class: bpy.props.StringProperty()
"""Element IFC class."""
- attribute_name: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ attribute_name: bpy.props.StringProperty()
"""IFC class attribute name."""
- data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ data_path: bpy.props.StringProperty()
"""Full data path"""
- preselect_ifc_id: bpy.props.IntProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
+ preselect_ifc_id: bpy.props.IntProperty(options={"SKIP_SAVE"})
"""IFC id to preselect in the popup."""
if TYPE_CHECKING:
diff --git a/src/bonsai/bonsai/bim/module/attribute/prop.py b/src/bonsai/bonsai/bim/module/attribute/prop.py
index acff2424b7..425c875e37 100644
--- a/src/bonsai/bonsai/bim/module/attribute/prop.py
+++ b/src/bonsai/bonsai/bim/module/attribute/prop.py
@@ -41,7 +41,7 @@ class BIMAttributeProperties(PropertyGroup):
class ExplorerEntity(PropertyGroup):
- ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ ifc_definition_id: bpy.props.IntProperty()
if TYPE_CHECKING:
ifc_definition_id: int
@@ -60,7 +60,7 @@ class BIMExplorerProperties(PropertyGroup):
self.property_unset("editing_entity_id")
self.entity_attributes.clear()
- is_loaded: BoolProperty( # pyright: ignore[reportRedeclaration]
+ is_loaded: BoolProperty(
name="Toggle Explorer UI",
update=update_is_loaded,
)
@@ -76,15 +76,15 @@ class BIMExplorerProperties(PropertyGroup):
def update_ifc_class(self, context: object) -> None:
tool.Attribute.refresh_uilist_entities()
- ifc_class: EnumProperty( # pyright: ignore[reportRedeclaration]
+ ifc_class: EnumProperty(
name="IFC Class To Search",
items=get_ifc_class,
update=update_ifc_class,
)
- entities: CollectionProperty(type=ExplorerEntity) # pyright: ignore[reportRedeclaration]
- active_entity_index: IntProperty() # pyright: ignore[reportRedeclaration]
- editing_entity_id: IntProperty() # pyright: ignore[reportRedeclaration]
- entity_attributes: CollectionProperty(type=Attribute) # pyright: ignore[reportRedeclaration]
+ entities: CollectionProperty(type=ExplorerEntity)
+ active_entity_index: IntProperty()
+ editing_entity_id: IntProperty()
+ entity_attributes: CollectionProperty(type=Attribute)
if TYPE_CHECKING:
is_loaded: bool
diff --git a/src/bonsai/bonsai/bim/module/clash/operator.py b/src/bonsai/bonsai/bim/module/clash/operator.py
index 3788130a12..ae5f622bbd 100644
--- a/src/bonsai/bonsai/bim/module/clash/operator.py
+++ b/src/bonsai/bonsai/bim/module/clash/operator.py
@@ -201,16 +201,10 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
"ALT+click to run a quick clash without selecting a file to save."
)
- filter_glob: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
- default="*.bcf;*.json", options={"HIDDEN"}
- )
- format: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
- name="Format", items=[(i, i, "") for i in ("bcf", "json")]
- )
- filepath: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
- subtype="FILE_PATH", options={"SKIP_SAVE"}
- )
- quick_clash: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
+ format: bpy.props.EnumProperty(name="Format", items=[(i, i, "") for i in ("bcf", "json")])
+ filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
+ quick_clash: bpy.props.BoolProperty(
options={"SKIP_SAVE"},
)
diff --git a/src/bonsai/bonsai/bim/module/clash/prop.py b/src/bonsai/bonsai/bim/module/clash/prop.py
index 8bcd71632b..1ef3403e64 100644
--- a/src/bonsai/bonsai/bim/module/clash/prop.py
+++ b/src/bonsai/bonsai/bim/module/clash/prop.py
@@ -37,12 +37,12 @@ from bonsai.bim.prop import BIMFilterGroup, StrProperty
class ClashSource(PropertyGroup):
- name: StringProperty( # pyright: ignore[reportRedeclaration]
+ name: StringProperty(
name="File",
description="Absolute filepath to existing .ifc file to use as a clash source.",
)
- filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") # pyright: ignore[reportRedeclaration]
- mode: EnumProperty( # pyright: ignore[reportRedeclaration]
+ filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
+ mode: EnumProperty(
items=[
("a", "All Elements", "All elements will be used for clashing"),
("i", "Include", "Only the selected elements are included for clashing"),
@@ -62,7 +62,7 @@ class Clash(PropertyGroup):
b_global_id: StringProperty(name="B")
a_name: StringProperty(name="A Name")
b_name: StringProperty(name="B Name")
- clash_type: EnumProperty( # pyright: ignore[reportRedeclaration]
+ clash_type: EnumProperty(
name="Clash Type",
items=tuple((i, i, "") for i in CLASH_TYPE_ITEMS),
)
diff --git a/src/bonsai/bonsai/bim/module/cost/operator.py b/src/bonsai/bonsai/bim/module/cost/operator.py
index b612100a1d..ac9d2d4bf1 100644
--- a/src/bonsai/bonsai/bim/module/cost/operator.py
+++ b/src/bonsai/bonsai/bim/module/cost/operator.py
@@ -87,7 +87,7 @@ class CopyCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Copy Cost Schedule"
bl_description = "Create a duplicate of the provided cost schedule."
bl_options = {"REGISTER", "UNDO"}
- cost_schedule: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ cost_schedule: bpy.props.IntProperty()
if TYPE_CHECKING:
cost_schedule: int
diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py
index 9315386008..c88ea4a00e 100644
--- a/src/bonsai/bonsai/bim/module/debug/operator.py
+++ b/src/bonsai/bonsai/bim/module/debug/operator.py
@@ -260,14 +260,14 @@ class CreateAllShapes(bpy.types.Operator):
)
bl_options = {"REGISTER"}
- geometry_library: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ geometry_library: bpy.props.EnumProperty(
name="Geometry Library",
description="Geometry library to use for testing shape creation.",
items=[(i, i, "") for i in get_args(ifcopenshell.geom.GEOMETRY_LIBRARY)],
# By default use the same library as used for importing ifc project.
default="hybrid-cgal-simple-opencascade",
)
- custom_geometry_library: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
+ custom_geometry_library: bpy.props.StringProperty(
name="Custom Geometry Library",
description="Provide a custom geometry library name, will override the 'geometry library' property.",
)
@@ -781,7 +781,7 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Purge Unused Objects"
bl_options = {"REGISTER", "UNDO"}
- object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ object_type: bpy.props.EnumProperty(
name="Object Type",
items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)),
)
@@ -827,7 +827,7 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
)
bl_options = {"REGISTER", "UNDO"}
- object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ object_type: bpy.props.EnumProperty(
name="Object Type",
items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)),
)
@@ -1073,7 +1073,7 @@ class ChangeLogLevel(bpy.types.Operator):
bl_options = {"REGISTER"}
bl_description = "Change general log level across all Python code in Blender"
- log_level: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ log_level: bpy.props.EnumProperty(
name="Log Level",
items=[(i, i, "") for i in get_args(LogLevelType)],
default="WARNING",
diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py
index 45f67b0769..d6594364bf 100644
--- a/src/bonsai/bonsai/bim/module/drawing/operator.py
+++ b/src/bonsai/bonsai/bim/module/drawing/operator.py
@@ -246,17 +246,17 @@ class CreateDrawing(bpy.types.Operator):
+ "Add the CTRL modifier to optionally open drawings to view them as\n"
+ "they are created"
)
- print_all: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ print_all: bpy.props.BoolProperty(
name="Print All",
default=False,
options={"SKIP_SAVE"},
)
- open_viewer: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ open_viewer: bpy.props.BoolProperty(
name="Open in Viewer",
default=False,
options={"SKIP_SAVE"},
)
- sync: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ sync: bpy.props.BoolProperty(
name="Sync Before Creating Drawing",
description="Could save some time if you're sure IFC and current Blender session are already in sync",
default=True,
@@ -2322,14 +2322,14 @@ class ActivateDrawingBase(tool.Ifc.Operator):
+ "SHIFT+CLICK to load a quick preview of the drawing view"
)
- drawing: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- should_view_from_camera: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ drawing: bpy.props.IntProperty()
+ should_view_from_camera: bpy.props.BoolProperty(
name="Should View From Camera",
description="Move view to the activated drawing's camera position.",
default=True,
options={"SKIP_SAVE"},
)
- use_quick_preview: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ use_quick_preview: bpy.props.BoolProperty(
name="Use Quick Preview",
description="Just move the camera to the drawing view, without loading anything else.",
default=False,
@@ -3635,14 +3635,12 @@ class ToggleTargetView(bpy.types.Operator):
bl_label = "Toggle Target View"
bl_options = {"REGISTER", "UNDO"}
- target_view: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
- toggle_all: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ target_view: bpy.props.StringProperty()
+ toggle_all: bpy.props.BoolProperty(
default=False,
options={"SKIP_SAVE"},
)
- option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
- items=[(i, i, "") for i in get_args(ToggleOption)]
- )
+ option: bpy.props.EnumProperty(items=[(i, i, "") for i in get_args(ToggleOption)])
if TYPE_CHECKING:
target_view: str
diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py
index 57c182c02e..1647d44773 100644
--- a/src/bonsai/bonsai/bim/module/drawing/prop.py
+++ b/src/bonsai/bonsai/bim/module/drawing/prop.py
@@ -860,13 +860,13 @@ class BIMTextProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
literals: CollectionProperty(name="Literals", type=LiteralProps)
newline_at: IntProperty(name="Newline At")
- symbol: EnumProperty( # pyright: ignore[reportRedeclaration]
+ symbol: EnumProperty(
name="Symbol",
description="Symbol from symbols.svg to use for this text.",
items=[(s, s, "") for s in ["NO SYMBOL", "CUSTOM SYMBOL"] + tool.Drawing.DEFAULT_SYMBOLS],
default="NO SYMBOL",
)
- custom_symbol: StringProperty( # pyright: ignore[reportRedeclaration]
+ custom_symbol: StringProperty(
name="Custom Symbol",
description="Non-default symbol to use for this text.",
)
diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py
index 7382b093f4..8d075a9605 100644
--- a/src/bonsai/bonsai/bim/module/geometry/operator.py
+++ b/src/bonsai/bonsai/bim/module/geometry/operator.py
@@ -85,7 +85,7 @@ class EditObjectPlacement(bpy.types.Operator, tool.Ifc.Operator):
class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.override_mesh_separate"
bl_label = "IFC Mesh Separate"
- blender_op = bpy.ops.mesh.separate.get_rna_type()
+ blender_op = bpy.ops.mesh.separate.get_rna_type() # ty: ignore[missing-argument]
bl_description = blender_op.description + ".\nAlso makes sure changes are in sync with IFC."
bl_options = {"REGISTER", "UNDO"}
blender_type_prop = blender_op.properties["type"]
@@ -246,7 +246,7 @@ class OverrideMeshSeparate(bpy.types.Operator, tool.Ifc.Operator):
class OverrideOriginSet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.override_origin_set"
- blender_op = bpy.ops.object.origin_set.get_rna_type()
+ blender_op = bpy.ops.object.origin_set.get_rna_type() # ty: ignore[missing-argument]
bl_label = "IFC Origin Set"
bl_description = (
blender_op.description + ".\nAlso makes sure changes are in sync with IFC (operator works only on IFC objects)"
@@ -801,7 +801,7 @@ def calc_delete_is_batch(ifc_file: ifcopenshell.file, context: bpy.types.Context
class OverrideDelete(bpy.types.Operator):
bl_idname = "bim.override_object_delete"
bl_label = "IFC Delete"
- blender_op = bpy.ops.object.delete.get_rna_type()
+ blender_op = bpy.ops.object.delete.get_rna_type() # ty: ignore[missing-argument]
bl_description = (
blender_op.description
+ ".\nAlso makes sure changes in sync with IFC."
@@ -821,7 +821,7 @@ class OverrideDelete(bpy.types.Operator):
def poll(cls, context):
# Match `object.delete` poll for consistency.
# `object.delete` poll just checks for OBJECT mode.
- poll = bpy.ops.object.delete.poll()
+ poll = bpy.ops.object.delete.poll() # ty: ignore[missing-argument]
if poll:
return True
cls.poll_message_set("Only available in OBJECT mode")
@@ -1045,7 +1045,7 @@ class SelectedIdsData(NamedTuple):
class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.override_outliner_delete"
bl_label = "IFC Delete"
- blender_op = bpy.ops.outliner.delete.get_rna_type()
+ blender_op = bpy.ops.outliner.delete.get_rna_type() # ty: ignore[missing-argument]
bl_description = (
blender_op.description
+ ".\nAlso makes sure changes in sync with IFC."
@@ -1060,7 +1060,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
def poll(cls, context) -> bool:
# Match `outliner.delete` poll for consistency.
# `outliner.delete` just checks `area.type` == `OUTLINER`.
- poll = bpy.ops.outliner.delete.poll()
+ poll = bpy.ops.outliner.delete.poll() # ty: ignore[missing-argument]
if poll:
return True
cls.poll_message_set("Only available from Outliner.")
@@ -1164,7 +1164,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
def poll(cls, context) -> bool:
# Match `object.duplicate_move` poll for consistency.
# `object.duplicate_move` poll checks for OBJECT mode.
- poll = bpy.ops.object.duplicate_move.poll()
+ poll = bpy.ops.object.duplicate_move.poll() # ty: ignore[missing-argument]
if poll:
return True
cls.poll_message_set("Only available in OBJECT mode")
@@ -1908,7 +1908,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.override_object_join"
bl_label = "IFC Join"
- blender_op = bpy.ops.mesh.separate.get_rna_type()
+ blender_op = bpy.ops.mesh.separate.get_rna_type() # ty: ignore[missing-argument]
bl_description = (
blender_op.description
+ ".\nAlso makes sure changes are in sync with IFC."
@@ -1926,7 +1926,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
@classmethod
def poll(cls, context):
- if not bpy.ops.object.join.poll():
+ if not bpy.ops.object.join.poll(): # ty: ignore[missing-argument]
cls.poll_message_set("Active object is not EDITable.")
return False
if not context.selected_editable_objects:
diff --git a/src/bonsai/bonsai/bim/module/group/operator.py b/src/bonsai/bonsai/bim/module/group/operator.py
index adea9ee48c..f58ba72763 100644
--- a/src/bonsai/bonsai/bim/module/group/operator.py
+++ b/src/bonsai/bonsai/bim/module/group/operator.py
@@ -43,11 +43,11 @@ class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Toggle Group"
bl_options = {"REGISTER", "UNDO"}
- ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- group_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ ifc_definition_id: bpy.props.IntProperty()
+ group_type: bpy.props.EnumProperty(
items=[(i, i, "") for i in get_args(tool.Group.GroupType)],
)
- option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ option: bpy.props.EnumProperty(
items=[(i, i, "") for i in get_args(tool.Group.ToggleOption)],
)
diff --git a/src/bonsai/bonsai/bim/module/ifcgit/operator.py b/src/bonsai/bonsai/bim/module/ifcgit/operator.py
index cc0f75577a..65bec6b252 100644
--- a/src/bonsai/bonsai/bim/module/ifcgit/operator.py
+++ b/src/bonsai/bonsai/bim/module/ifcgit/operator.py
@@ -314,7 +314,7 @@ class SelectConflictEntity(bpy.types.Operator):
bl_idname = "ifcgit.select_conflict_entity"
bl_options = {"REGISTER"}
- step_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ step_id: bpy.props.IntProperty()
if TYPE_CHECKING:
step_id: int
@@ -515,7 +515,7 @@ class RunGitDiff(bpy.types.Operator):
)
bl_options = set()
- save_to_temp: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
+ save_to_temp: bpy.props.BoolProperty(options={"SKIP_SAVE"})
if TYPE_CHECKING:
save_to_temp: bool
@@ -547,7 +547,7 @@ class RenameBranch(bpy.types.Operator):
bl_idname = "ifcgit.rename_branch"
bl_options = {"REGISTER"}
- new_name: bpy.props.StringProperty(name="New name") # pyright: ignore[reportRedeclaration]
+ new_name: bpy.props.StringProperty(name="New name")
if TYPE_CHECKING:
new_name: str
diff --git a/src/bonsai/bonsai/bim/module/light/operator.py b/src/bonsai/bonsai/bim/module/light/operator.py
index 6ee37f2285..c3f377e9b9 100644
--- a/src/bonsai/bonsai/bim/module/light/operator.py
+++ b/src/bonsai/bonsai/bim/module/light/operator.py
@@ -272,21 +272,21 @@ class RadianceRender(bpy.types.Operator):
+ '''" map_u map_v
0
1 0.5
-
+
# This is a multiplier to colour balance the env map
# In this case, it provides a rough ground luminance from 3k-5k
env_map colorfunc env_colour
4 100 100 100 .
0
0
-
+
# .37 .57 1.5 is measured from a HDRI image
# It is multiplied by a factor such that grey(r,g,b) = 1
skyfunc colorfunc sky_colour
4 .64 .99 2.6 .
0
0
-
+
void mixpict composite
7 env_colour sky_colour grey "'''
+ hdr_mask_path
@@ -295,22 +295,22 @@ void mixpict composite
+ """" map_u map_v
0
2 0.5 1
-
+
composite glow env_map_glow
0
0
4 1 1 1 0
-
+
env_map_glow source sky
0
0
4 0 0 1 180
-
+
env_colour glow ground_glow
0
0
4 1 1 1 0
-
+
ground_glow source ground
0
0
@@ -566,7 +566,7 @@ class LightPickCoordinates(bpy.types.Operator):
)
bl_options = {"REGISTER", "UNDO"}
- use_current_location: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
+ use_current_location: bpy.props.BoolProperty(options={"SKIP_SAVE"})
if TYPE_CHECKING:
use_current_location: bool
diff --git a/src/bonsai/bonsai/bim/module/misc/operator.py b/src/bonsai/bonsai/bim/module/misc/operator.py
index 33da9a05dc..203bc9dd6c 100644
--- a/src/bonsai/bonsai/bim/module/misc/operator.py
+++ b/src/bonsai/bonsai/bim/module/misc/operator.py
@@ -136,7 +136,7 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
"Will unassign element from a type if type has a representation."
)
bl_options = {"REGISTER", "UNDO"}
- mode: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ mode: bpy.props.EnumProperty(
default="BOOLEAN",
items=tuple((i, i, "") for i in get_args(SplitAlongEdgeMode)),
)
@@ -359,7 +359,7 @@ class ConfirmQuickFavoriteOperator(bpy.types.Operator):
bl_idname = "bim.confirm_quick_favorite_operator"
bl_label = "Confirm Operator"
bl_options = {"REGISTER", "UNDO"}
- index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ index: bpy.props.IntProperty()
if TYPE_CHECKING:
index: int
@@ -452,10 +452,8 @@ class MoveQuickFavoritesItem(bpy.types.Operator):
bl_idname = "bim.move_quick_favorites_item"
bl_label = "Move Quick Favorites Item"
bl_options = {"REGISTER", "UNDO"}
- index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- direction: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
- items=[("UP", "Up", ""), ("DOWN", "Down", "")]
- )
+ index: bpy.props.IntProperty()
+ direction: bpy.props.EnumProperty(items=[("UP", "Up", ""), ("DOWN", "Down", "")])
if TYPE_CHECKING:
index: int
@@ -474,7 +472,7 @@ class RemoveQuickFavoritesItem(bpy.types.Operator):
bl_idname = "bim.remove_quick_favorites_item"
bl_label = "Remove Quick Favorites Item"
bl_options = {"REGISTER", "UNDO"}
- index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ index: bpy.props.IntProperty()
if TYPE_CHECKING:
index: int
diff --git a/src/bonsai/bonsai/bim/module/misc/prop.py b/src/bonsai/bonsai/bim/module/misc/prop.py
index ddeda73b88..74a82f06cd 100644
--- a/src/bonsai/bonsai/bim/module/misc/prop.py
+++ b/src/bonsai/bonsai/bim/module/misc/prop.py
@@ -36,9 +36,9 @@ QuickFavoriteValueType = Literal["float_value", "bool_value", "int_value", "stri
class QuickFavoriteEnumItem(PropertyGroup):
- name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration]
- display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration]
- description: StringProperty(name="Description", default="") # pyright: ignore[reportRedeclaration]
+ name: StringProperty(name="Name", default="")
+ display_name: StringProperty(name="Display Name", default="")
+ description: StringProperty(name="Description", default="")
if TYPE_CHECKING:
name: str
@@ -51,19 +51,19 @@ def get_enum_items(self: "QuickFavoriteProperty", context: bpy.types.Context | N
class QuickFavoriteProperty(PropertyGroup):
- name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration]
- display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration]
- value_prop: EnumProperty( # pyright: ignore[reportRedeclaration]
+ name: StringProperty(name="Name", default="")
+ display_name: StringProperty(name="Display Name", default="")
+ value_prop: EnumProperty(
name="Value Prop",
items=tuple((v, v, "") for v in get_args(QuickFavoriteValueType)),
)
- string_value: StringProperty(name="String Value", default="") # pyright: ignore[reportRedeclaration]
- float_value: FloatProperty(name="Float Value", default=0.0) # pyright: ignore[reportRedeclaration]
- int_value: IntProperty(name="Int Value", default=0) # pyright: ignore[reportRedeclaration]
- bool_value: BoolProperty(name="Bool Value", default=False) # pyright: ignore[reportRedeclaration]
- enum_value: EnumProperty(name="Enum Value", items=get_enum_items) # pyright: ignore[reportRedeclaration]
- enum_items: CollectionProperty(type=QuickFavoriteEnumItem) # pyright: ignore[reportRedeclaration]
- is_active: BoolProperty( # pyright: ignore[reportRedeclaration]
+ string_value: StringProperty(name="String Value", default="")
+ float_value: FloatProperty(name="Float Value", default=0.0)
+ int_value: IntProperty(name="Int Value", default=0)
+ bool_value: BoolProperty(name="Bool Value", default=False)
+ enum_value: EnumProperty(name="Enum Value", items=get_enum_items)
+ enum_items: CollectionProperty(type=QuickFavoriteEnumItem)
+ is_active: BoolProperty(
name="Is Active",
description="Only active properties will be added to the operator when invoked from Quick Favorites",
default=False,
@@ -100,20 +100,20 @@ def get_operator_suggestions(self: "QuickFavoritesItem", context: bpy.types.Cont
class QuickFavoritesItem(PropertyGroup):
- is_expanded: BoolProperty(name="Is Expanded", default=False) # pyright: ignore[reportRedeclaration]
- search: StringProperty( # pyright: ignore[reportRedeclaration]
+ is_expanded: BoolProperty(name="Is Expanded", default=False)
+ search: StringProperty(
name="Search",
default="",
search=get_operator_suggestions,
# Resetting `search_options`, allowing users only to use suggestions.
search_options=set(),
)
- properties: CollectionProperty(type=QuickFavoriteProperty) # pyright: ignore[reportRedeclaration]
- operator_id: StringProperty( # pyright: ignore[reportRedeclaration]
+ properties: CollectionProperty(type=QuickFavoriteProperty)
+ operator_id: StringProperty(
name="Operator ID",
default="",
)
- label: StringProperty( # pyright: ignore[reportRedeclaration]
+ label: StringProperty(
name="Label",
description="Label that will be used in Quick Favorites for this operator",
default="",
@@ -139,15 +139,15 @@ class QuickFavoritesItem(PropertyGroup):
class BIMMiscProperties(PropertyGroup):
- total_storeys: IntProperty( # pyright: ignore[reportRedeclaration]
+ total_storeys: IntProperty(
name="Total Storeys",
description="Number of storeys above object's storey to take into account for resizing",
default=1,
)
- override_colour: FloatVectorProperty( # pyright: ignore[reportRedeclaration]
+ override_colour: FloatVectorProperty(
name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
)
- quick_favorites: CollectionProperty(type=QuickFavoritesItem) # pyright: ignore[reportRedeclaration]
+ quick_favorites: CollectionProperty(type=QuickFavoritesItem)
if TYPE_CHECKING:
total_storeys: int
diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py
index 4cf4e00172..75f5441827 100644
--- a/src/bonsai/bonsai/bim/module/model/product.py
+++ b/src/bonsai/bonsai/bim/module/model/product.py
@@ -545,7 +545,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.change_type_page"
bl_label = "Change Type Page"
bl_options = {"REGISTER"}
- page: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ page: bpy.props.IntProperty()
if TYPE_CHECKING:
page: int
diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py
index 1369ad3cb6..e5c0991e8e 100644
--- a/src/bonsai/bonsai/bim/module/model/profile.py
+++ b/src/bonsai/bonsai/bim/module/model/profile.py
@@ -271,7 +271,7 @@ class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.extend_profile"
bl_label = "Extend Profile"
bl_options = {"REGISTER", "UNDO"}
- join_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ join_type: bpy.props.EnumProperty(
items=[("-", "Unjoin", ""), ("L", "L", ""), ("V", "V", ""), ("T", "T", "")],
default="-",
)
diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py
index c4956056aa..ff6ea96130 100644
--- a/src/bonsai/bonsai/bim/module/model/prop.py
+++ b/src/bonsai/bonsai/bim/module/model/prop.py
@@ -1729,20 +1729,20 @@ def poll_sverchok_nodes(self: "BIMExternalParametricGeometryProperties", node_tr
class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup):
- is_editing: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ is_editing: bpy.props.BoolProperty(
name="Is Editing Paramteric Geometry",
description="Toggle editing parametric geometry.",
default=False,
update=update_is_editing,
)
- geometry_source: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ geometry_source: bpy.props.EnumProperty(
name="Geometry Source",
items=[
("GEONODES", "Geometry Nodes", ""),
("IFCSVERCHOK", "IFC Sverchok", ""),
],
)
- geo_nodes: bpy.props.PointerProperty( # pyright: ignore[reportRedeclaration]
+ geo_nodes: bpy.props.PointerProperty(
name="Geometry Nodes",
description="Geometry nodes tree to use as a source for representation.",
type=bpy.types.GeometryNodeTree,
@@ -1750,7 +1750,7 @@ class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup):
poll=lambda self, node_tree: not node_tree.name.startswith("BBIM_EPG"),
)
- sverchok_nodes: bpy.props.PointerProperty( # pyright: ignore[reportRedeclaration]
+ sverchok_nodes: bpy.props.PointerProperty(
name="Sverchok Nodes",
description="Sverchok node tree to use as a source for representation.",
type=bpy.types.NodeTree,
diff --git a/src/bonsai/bonsai/bim/module/nest/decorator.py b/src/bonsai/bonsai/bim/module/nest/decorator.py
index 4a3637caa6..28c3835ba7 100644
--- a/src/bonsai/bonsai/bim/module/nest/decorator.py
+++ b/src/bonsai/bonsai/bim/module/nest/decorator.py
@@ -101,7 +101,7 @@ class NestDecorator:
cls.is_installed = False
def dotted_line_shader(self):
- vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
+ vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
vert_out.smooth("FLOAT", "v_ArcLength")
shader_info = gpu.types.GPUShaderCreateInfo()
diff --git a/src/bonsai/bonsai/bim/module/owner/operator.py b/src/bonsai/bonsai/bim/module/owner/operator.py
index 2470fd8227..b934337b64 100644
--- a/src/bonsai/bonsai/bim/module/owner/operator.py
+++ b/src/bonsai/bonsai/bim/module/owner/operator.py
@@ -33,7 +33,7 @@ class EnableEditingPerson(bpy.types.Operator):
bl_idname = "bim.enable_editing_person"
bl_label = "Enable Editing Person"
bl_options = {"REGISTER", "UNDO"}
- person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ person: bpy.props.IntProperty()
if TYPE_CHECKING:
person: int
@@ -75,7 +75,7 @@ class RemovePerson(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_person"
bl_label = "Remove Person"
bl_options = {"REGISTER", "UNDO"}
- person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ person: bpy.props.IntProperty()
if TYPE_CHECKING:
person: int
@@ -88,7 +88,7 @@ class AddPersonAttribute(bpy.types.Operator):
bl_idname = "bim.add_person_attribute"
bl_label = "Add Person Attribute"
bl_options = {"REGISTER", "UNDO"}
- name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ name: bpy.props.EnumProperty(
items=tuple((i, i, "") for i in get_args(tool.Owner.PersonAttributeType)),
)
@@ -104,10 +104,10 @@ class RemovePersonAttribute(bpy.types.Operator):
bl_idname = "bim.remove_person_attribute"
bl_label = "Remove Person Attribute"
bl_options = {"REGISTER", "UNDO"}
- name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ name: bpy.props.EnumProperty(
items=tuple((i, i, "") for i in get_args(tool.Owner.PersonAttributeType)),
)
- id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ id: bpy.props.IntProperty()
if TYPE_CHECKING:
name: tool.Owner.PersonAttributeType # pyright: ignore[reportIncompatibleVariableOverride]
@@ -122,7 +122,7 @@ class EnableEditingRole(bpy.types.Operator):
bl_idname = "bim.enable_editing_role"
bl_label = "Enable Editing Role"
bl_options = {"REGISTER", "UNDO"}
- role: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ role: bpy.props.IntProperty()
if TYPE_CHECKING:
role: int
@@ -146,7 +146,7 @@ class AddRole(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_role"
bl_label = "Add Role"
bl_options = {"REGISTER", "UNDO"}
- parent: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ parent: bpy.props.IntProperty()
if TYPE_CHECKING:
parent: int
@@ -168,7 +168,7 @@ class RemoveRole(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_role"
bl_label = "Remove Role"
bl_options = {"REGISTER", "UNDO"}
- role: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ role: bpy.props.IntProperty()
if TYPE_CHECKING:
role: int
@@ -181,8 +181,8 @@ class AddAddress(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_address"
bl_label = "Add Address"
bl_options = {"REGISTER", "UNDO"}
- parent: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- ifc_class: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ parent: bpy.props.IntProperty()
+ ifc_class: bpy.props.EnumProperty(
items=tuple((i, i, "") for i in get_args(ADDRESS_TYPE)),
)
@@ -198,7 +198,7 @@ class AddAddressAttribute(bpy.types.Operator):
bl_idname = "bim.add_address_attribute"
bl_label = "Add Address Attribute"
bl_options = {"REGISTER", "UNDO"}
- name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ name: bpy.props.EnumProperty(
items=tuple((i, i, "") for i in get_args(tool.Owner.AddressAttributeType)),
)
@@ -214,10 +214,10 @@ class RemoveAddressAttribute(bpy.types.Operator):
bl_idname = "bim.remove_address_attribute"
bl_label = "Remove Address Attribute"
bl_options = {"REGISTER", "UNDO"}
- name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ name: bpy.props.EnumProperty(
items=tuple((i, i, "") for i in get_args(tool.Owner.AddressAttributeType)),
)
- id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ id: bpy.props.IntProperty()
if TYPE_CHECKING:
name: tool.Owner.AddressAttributeType # pyright: ignore[reportIncompatibleVariableOverride]
@@ -232,7 +232,7 @@ class EnableEditingAddress(bpy.types.Operator):
bl_idname = "bim.enable_editing_address"
bl_label = "Enable Editing Address"
bl_options = {"REGISTER", "UNDO"}
- address: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ address: bpy.props.IntProperty()
if TYPE_CHECKING:
address: int
@@ -265,7 +265,7 @@ class RemoveAddress(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_address"
bl_label = "Remove Address"
bl_options = {"REGISTER", "UNDO"}
- address: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ address: bpy.props.IntProperty()
if TYPE_CHECKING:
address: int
@@ -278,7 +278,7 @@ class EnableEditingOrganisation(bpy.types.Operator):
bl_idname = "bim.enable_editing_organisation"
bl_label = "Enable Editing Organisation"
bl_options = {"REGISTER", "UNDO"}
- organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ organisation: bpy.props.IntProperty()
if TYPE_CHECKING:
organisation: int
@@ -320,7 +320,7 @@ class RemoveOrganisation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_organisation"
bl_label = "Remove Organisation"
bl_options = {"REGISTER", "UNDO"}
- organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ organisation: bpy.props.IntProperty()
if TYPE_CHECKING:
organisation: int
@@ -333,8 +333,8 @@ class AddPersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_person_and_organisation"
bl_label = "Add Person And Organisation"
bl_options = {"REGISTER", "UNDO"}
- person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ person: bpy.props.IntProperty()
+ organisation: bpy.props.IntProperty()
if TYPE_CHECKING:
person: int
@@ -350,7 +350,7 @@ class RemovePersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_person_and_organisation"
bl_label = "Remove Person And Organisation"
bl_options = {"REGISTER", "UNDO"}
- person_and_organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ person_and_organisation: bpy.props.IntProperty()
if TYPE_CHECKING:
person_and_organisation: int
@@ -365,7 +365,7 @@ class SetUser(bpy.types.Operator):
bl_idname = "bim.set_user"
bl_label = "Set User"
bl_options = {"REGISTER", "UNDO"}
- user: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ user: bpy.props.IntProperty()
if TYPE_CHECKING:
user: int
@@ -401,7 +401,7 @@ class EnableEditingActor(bpy.types.Operator):
bl_idname = "bim.enable_editing_actor"
bl_label = "Enable Editing Actor"
bl_options = {"REGISTER", "UNDO"}
- actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ actor: bpy.props.IntProperty()
if TYPE_CHECKING:
actor: int
@@ -434,7 +434,7 @@ class RemoveActor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_actor"
bl_label = "Remove Actor"
bl_options = {"REGISTER", "UNDO"}
- actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ actor: bpy.props.IntProperty()
if TYPE_CHECKING:
actor: int
@@ -447,7 +447,7 @@ class AssignActor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_actor"
bl_label = "Assign Actor"
bl_options = {"REGISTER", "UNDO"}
- actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ actor: bpy.props.IntProperty()
if TYPE_CHECKING:
actor: int
@@ -462,7 +462,7 @@ class UnassignActor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_actor"
bl_label = "Unassign Actor"
bl_options = {"REGISTER", "UNDO"}
- actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ actor: bpy.props.IntProperty()
if TYPE_CHECKING:
actor: int
@@ -481,7 +481,7 @@ class RemoveApplication(bpy.types.Operator, tool.Ifc.Operator):
"Remove provided IfcApplication."
"\n\nFor safety will only work on applications without inverses (they are typically marked as '(unused)'."
)
- application_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ application_id: bpy.props.IntProperty()
if TYPE_CHECKING:
application_id: int
@@ -525,7 +525,7 @@ class EnableEditingApplication(bpy.types.Operator):
bl_idname = "bim.enable_editing_application"
bl_label = "Enable Editing Application"
bl_options = {"REGISTER", "UNDO"}
- application_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ application_id: bpy.props.IntProperty()
if TYPE_CHECKING:
application_id: int
diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py
index e24f6f41d8..d38c4f3b68 100644
--- a/src/bonsai/bonsai/bim/module/project/operator.py
+++ b/src/bonsai/bonsai/bim/module/project/operator.py
@@ -86,9 +86,7 @@ class NewProject(bpy.types.Operator):
bl_label = "New Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Start a new IFC project in a fresh session"
- preset: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
- items=[(i, i, "") for i in get_args(PresetType)]
- )
+ preset: bpy.props.EnumProperty(items=[(i, i, "") for i in get_args(PresetType)])
if TYPE_CHECKING:
preset: PresetType
@@ -178,13 +176,9 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
bl_description = (
"Select an IFC file that can be used as a library.\n\nALT+click to reload the current loaded library file."
)
- filter_glob: bpy.props.StringProperty(
- default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
- append_all: bpy.props.BoolProperty(default=False) # pyright: ignore[reportRedeclaration]
- use_relative_path: bpy.props.BoolProperty(
- name="Use Relative Path", default=False
- ) # pyright: ignore[reportRedeclaration]
+ filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
+ append_all: bpy.props.BoolProperty(default=False)
+ use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
if TYPE_CHECKING:
filter_glob: str
@@ -568,7 +562,7 @@ class AppendLibraryElementByQuery(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.append_library_element_by_query"
bl_label = "Append Library Element By Query"
- query: bpy.props.StringProperty(name="Query") # pyright: ignore[reportRedeclaration]
+ query: bpy.props.StringProperty(name="Query")
if TYPE_CHECKING:
query: str
@@ -600,11 +594,9 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
"Append element to the current project.\n\n"
"ALT+CLICK to skip reusing materials, profiles, styles based on their name (may result in duplicates)"
)
- definition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- prop_index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- assume_unique_by_name: bpy.props.BoolProperty(
- name="Assume Unique By Name", default=True, options={"SKIP_SAVE"}
- ) # pyright: ignore[reportRedeclaration]
+ definition: bpy.props.IntProperty()
+ prop_index: bpy.props.IntProperty()
+ assume_unique_by_name: bpy.props.BoolProperty(name="Assume Unique By Name", default=True, options={"SKIP_SAVE"})
if TYPE_CHECKING:
definition: int
@@ -959,28 +951,24 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
bl_label = "Load Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load an existing IFC project"
- filepath: bpy.props.StringProperty(
- subtype="FILE_PATH", options={"SKIP_SAVE"}
- ) # pyright: ignore[reportRedeclaration]
- filter_glob: bpy.props.StringProperty(
- default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
- is_advanced: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
+ filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"})
+ is_advanced: bpy.props.BoolProperty(
name="Enable Advanced Mode",
description="Load IFC file with advanced settings. Checking this option will skip loading IFC file and will open advanced load settings",
default=False,
)
- use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ use_relative_path: bpy.props.BoolProperty(
name="Use Relative Path",
description="Store the IFC project path relative to the .blend file. Requires .blend file to be saved",
default=False,
)
- should_start_fresh_session: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ should_start_fresh_session: bpy.props.BoolProperty(
name="Should Start Fresh Session",
description="Clear current Blender session before loading IFC. Not supported with 'Use Relative Path' option",
default=True,
)
- import_without_ifc_data: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ import_without_ifc_data: bpy.props.BoolProperty(
name="Import Without IFC Data",
description=(
"Import IFC objects as Blender objects without any IFC metadata and authoring capabilities."
@@ -988,9 +976,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
),
default=False,
)
- use_detailed_tooltip: bpy.props.BoolProperty(
- default=False, options={"HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
+ use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
filename_ext = ".ifc"
if TYPE_CHECKING:
@@ -1300,7 +1286,7 @@ class ToggleFilterCategories(bpy.types.Operator):
bl_idname = "bim.toggle_filter_categories"
bl_label = "Toggle Filter Categories"
bl_options = {"REGISTER", "UNDO"}
- should_select: bpy.props.BoolProperty(name="Should Select", default=True) # pyright: ignore[reportRedeclaration]
+ should_select: bpy.props.BoolProperty(name="Should Select", default=True)
if TYPE_CHECKING:
should_select: bool
@@ -1327,7 +1313,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
default=False,
)
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
- query: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
+ query: bpy.props.StringProperty(
name="Query",
description=(
"Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n"
@@ -1404,7 +1390,7 @@ class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove the selected file from the link list"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
if TYPE_CHECKING:
link_index: int
@@ -1428,7 +1414,7 @@ class UnloadLink(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Unload the selected linked file"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
if TYPE_CHECKING:
link_index: int
@@ -1454,9 +1440,9 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load the selected file"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
- use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) # pyright: ignore[reportRedeclaration]
- query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
+ use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
+ query: bpy.props.StringProperty()
if TYPE_CHECKING:
link_index: int
@@ -1631,7 +1617,7 @@ class ReloadLink(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Reload the selected file"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
if TYPE_CHECKING:
link_index: int
@@ -1647,7 +1633,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle selectability"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
if TYPE_CHECKING:
link_index: int
@@ -1679,8 +1665,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Toggle visibility between SOLID and WIREFRAME"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
- mode: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
+ mode: bpy.props.EnumProperty(
name="Visibility Mode",
items=((i, i, "") for i in ("WIREFRAME", "VISIBLE")),
)
@@ -1821,7 +1807,7 @@ class SelectLinkHandle(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select link empty object handle"
- link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration]
+ link_index: bpy.props.IntProperty(name="Link Index")
if TYPE_CHECKING:
link_index: int
@@ -1843,7 +1829,7 @@ class SelectLinkedModelElement(bpy.types.Operator):
bl_options = {"REGISTER"}
bl_description = "Select an element in the currently selected linked model by providing GlobalId."
- guid: bpy.props.StringProperty(name="GlobalId") # pyright: ignore[reportRedeclaration]
+ guid: bpy.props.StringProperty(name="GlobalId")
if TYPE_CHECKING:
guid: str
@@ -1882,21 +1868,11 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
bl_options = {"REGISTER", "UNDO"}
filename_ext = ".ifc"
supported_filexts = (".ifc", ".ifczip", ".ifcjson")
- filter_glob: bpy.props.StringProperty(
- default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
- json_version: bpy.props.EnumProperty(
- items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version"
- ) # pyright: ignore[reportRedeclaration]
- json_compact: bpy.props.BoolProperty(
- name="Export Compact IFCJSON", default=False
- ) # pyright: ignore[reportRedeclaration]
- should_save_as: bpy.props.BoolProperty(
- name="Should Save As", default=False, options={"HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
- use_relative_path: bpy.props.BoolProperty(
- name="Use Relative Path", default=False
- ) # pyright: ignore[reportRedeclaration]
+ filter_glob: bpy.props.StringProperty(default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"})
+ json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
+ json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
+ should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
+ use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
if TYPE_CHECKING:
filter_glob: str
@@ -2053,7 +2029,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
bl_description = "Operator is used to load a project .cache.blend to then link it to the IFC file."
bl_options = {"REGISTER", "UNDO"}
- query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ query: bpy.props.StringProperty()
"""See ``bim.link_ifc``."""
if TYPE_CHECKING:
@@ -2443,8 +2419,8 @@ class HideQueriedLinkedElement(bpy.types.Operator):
)
bl_options = {"REGISTER", "UNDO"}
- unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
- hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
+ unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"})
+ hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"})
if TYPE_CHECKING:
unhide_all: bool
@@ -2918,12 +2894,8 @@ class IFCFileHandlerOperator(bpy.types.Operator):
bl_label = "Import .ifc file"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
- directory: bpy.props.StringProperty(
- subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
- files: bpy.props.CollectionProperty(
- type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"}
- ) # pyright: ignore[reportRedeclaration]
+ directory: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"})
+ files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"})
if TYPE_CHECKING:
directory: str
@@ -2978,7 +2950,7 @@ class MeasureTool(bpy.types.Operator, PolylineOperator):
bl_label = "Measure Tool"
bl_options = {"REGISTER", "UNDO"}
- measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ measure_type: bpy.props.StringProperty()
if TYPE_CHECKING:
measure_type: str
@@ -3077,7 +3049,7 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
bl_label = "Measure Face Area Tool"
bl_options = {"REGISTER", "UNDO"}
- measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ measure_type: bpy.props.StringProperty()
if TYPE_CHECKING:
measure_type: str
@@ -3379,7 +3351,7 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator):
bl_idname = "bim.load_blend_metadata_and_ifc"
bl_label = "Load Blend Metadata and IFC"
bl_options = {"REGISTER", "UNDO"}
- filepath: bpy.props.StringProperty(name="IFC File Path", default="") # pyright: ignore[reportRedeclaration]
+ filepath: bpy.props.StringProperty(name="IFC File Path", default="")
if TYPE_CHECKING:
filepath: str
diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py
index 5ba0cf6f28..1408d2f786 100644
--- a/src/bonsai/bonsai/bim/module/project/prop.py
+++ b/src/bonsai/bonsai/bim/module/project/prop.py
@@ -345,7 +345,7 @@ class BIMProjectProperties(PropertyGroup):
),
default=False,
)
- should_cache: BoolProperty( # pyright: ignore[reportRedeclaration]
+ should_cache: BoolProperty(
name="Cache",
description=(
"Cache loaded geometry to .h5 file in your cache directory (see in preferences) "
diff --git a/src/bonsai/bonsai/bim/module/pset/operator.py b/src/bonsai/bonsai/bim/module/pset/operator.py
index d7755cf80a..ea1d39fb96 100644
--- a/src/bonsai/bonsai/bim/module/pset/operator.py
+++ b/src/bonsai/bonsai/bim/module/pset/operator.py
@@ -240,7 +240,7 @@ class CopyPropertyToSelection(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Copy Property To Selection"
bl_options = {"REGISTER", "UNDO"}
- name: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ name: bpy.props.StringProperty()
if TYPE_CHECKING:
name: str
@@ -280,10 +280,10 @@ class BIM_OT_add_property_to_edit(bpy.types.Operator):
bl_label = "Add Property to Edit"
bl_idname = "bim.add_property_to_edit"
bl_options = {"REGISTER", "UNDO"}
- option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ option: bpy.props.EnumProperty(
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
)
- index: bpy.props.IntProperty(default=-1) # pyright: ignore[reportRedeclaration]
+ index: bpy.props.IntProperty(default=-1)
if TYPE_CHECKING:
option: tool.Pset.BulkOperationType
@@ -307,9 +307,9 @@ class BIM_OT_remove_property_to_edit(bpy.types.Operator):
bl_label = "Remove Property from Editing"
bl_idname = "bim.remove_property_to_edit"
bl_options = {"REGISTER", "UNDO"}
- index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
- index2: bpy.props.IntProperty(default=-1) # pyright: ignore[reportRedeclaration]
- option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ index: bpy.props.IntProperty()
+ index2: bpy.props.IntProperty(default=-1)
+ option: bpy.props.EnumProperty(
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
)
@@ -336,7 +336,7 @@ class BIM_OT_bulk_edit_clear_list(bpy.types.Operator):
bl_label = "Clear List of Properties"
bl_idname = "bim.pset_bulk_edit_clear_list"
bl_options = {"REGISTER", "UNDO"}
- option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ option: bpy.props.EnumProperty(
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
)
diff --git a/src/bonsai/bonsai/bim/module/pset/prop.py b/src/bonsai/bonsai/bim/module/pset/prop.py
index 1777fa0f94..786e6a62a2 100644
--- a/src/bonsai/bonsai/bim/module/pset/prop.py
+++ b/src/bonsai/bonsai/bim/module/pset/prop.py
@@ -368,9 +368,9 @@ class GlobalPsetProperties(PropertyGroup):
qto_filter: StringProperty(name="Qto Filter", options={"TEXTEDIT_UPDATE"})
# Bulk operations.
- psets_to_delete: CollectionProperty(type=DeletePsetEntry) # pyright: ignore[reportRedeclaration]
- psets_to_rename: CollectionProperty(type=RenamePropertyEntry) # pyright: ignore[reportRedeclaration]
- psets_to_add_edit: CollectionProperty(type=AddEditPropertyEntry) # pyright: ignore[reportRedeclaration]
+ psets_to_delete: CollectionProperty(type=DeletePsetEntry)
+ psets_to_rename: CollectionProperty(type=RenamePropertyEntry)
+ psets_to_add_edit: CollectionProperty(type=AddEditPropertyEntry)
if TYPE_CHECKING:
pset_filter: str
diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py
index d5a6b9b1e6..f55dcf31b7 100644
--- a/src/bonsai/bonsai/bim/module/search/operator.py
+++ b/src/bonsai/bonsai/bim/module/search/operator.py
@@ -799,7 +799,7 @@ class SelectQueryElements(Operator):
bl_description = "Select elements matching an provided selector query"
bl_options = {"REGISTER", "UNDO"}
- query: StringProperty(name="Query") # pyright: ignore[reportRedeclaration]
+ query: StringProperty(name="Query")
if TYPE_CHECKING:
query: str
@@ -829,12 +829,12 @@ class SaveSearch(Operator, tool.Ifc.Operator):
# Extra item so it will be easy to select current text.
return [text] + SaveSearch.name_search_items
- name: StringProperty( # pyright: ignore[reportRedeclaration]
+ name: StringProperty(
name="Name",
search=get_name_search_items,
search_options={"SORT"},
)
- module: StringProperty() # pyright: ignore[reportRedeclaration]
+ module: StringProperty()
def update_use_all_ifcgroups(self, context: object = None) -> None:
ifc_file = tool.Ifc.get()
@@ -845,7 +845,7 @@ class SaveSearch(Operator, tool.Ifc.Operator):
}
self.name_search_items[:] = natsorted(groups)
- use_all_ifcgroups: BoolProperty( # pyright: ignore[reportRedeclaration]
+ use_all_ifcgroups: BoolProperty(
name="Use Any IfcGroup",
description=(
"By default we're targeting only IfcGroups with SEARCH ObjectType "
diff --git a/src/bonsai/bonsai/bim/module/sequence/operator.py b/src/bonsai/bonsai/bim/module/sequence/operator.py
index 2b4c317cd8..fb97d7152a 100644
--- a/src/bonsai/bonsai/bim/module/sequence/operator.py
+++ b/src/bonsai/bonsai/bim/module/sequence/operator.py
@@ -106,7 +106,7 @@ class ActivateStatusFilters(bpy.types.Operator):
bl_description = "Filter and display objects based on currently selected IFC statuses"
bl_options = {"REGISTER", "UNDO"}
- only_if_enabled: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ only_if_enabled: bpy.props.BoolProperty(
name="Only If Filters are Enabled",
description="Activate status filters only in case if they were enabled from the UI before.",
default=False,
@@ -137,7 +137,7 @@ class SelectStatusFilter(bpy.types.Operator):
bl_description = "Select elements with currently selected status"
bl_options = {"REGISTER", "UNDO"}
- status: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ status: bpy.props.StringProperty()
if TYPE_CHECKING:
status: tool.Sequence.ElementStatusUI
@@ -156,7 +156,7 @@ class AssignStatus(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Assign status to the selected elements.\n\nAlt+CLICK to unassign the status."
bl_options = {"REGISTER", "UNDO"}
- should_override_previous_status: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ should_override_previous_status: bpy.props.BoolProperty(
name="Override Previous Status",
description=(
"Whether assigning new status should override previous one.\n\n"
@@ -165,8 +165,8 @@ class AssignStatus(bpy.types.Operator, tool.Ifc.Operator):
),
default=True,
)
- status: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
- should_unassign_status: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ status: bpy.props.StringProperty()
+ should_unassign_status: bpy.props.BoolProperty(
options={"SKIP_SAVE"},
)
@@ -415,7 +415,7 @@ class CopyWorkSchedule(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Copy Work Schedule"
bl_description = "Create a duplicate of the provided work schedule."
bl_options = {"REGISTER", "UNDO"}
- work_schedule: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ work_schedule: bpy.props.IntProperty()
if TYPE_CHECKING:
work_schedule: int
diff --git a/src/bonsai/bonsai/bim/module/sequence/prop.py b/src/bonsai/bonsai/bim/module/sequence/prop.py
index abb1aa9f0c..97f274c421 100644
--- a/src/bonsai/bonsai/bim/module/sequence/prop.py
+++ b/src/bonsai/bonsai/bim/module/sequence/prop.py
@@ -412,7 +412,7 @@ WorkPlanEditingType = Literal["-", "ATTRIBUTES", "SCHEDULES", "WORK_SCHEDULE", "
class BIMWorkPlanProperties(PropertyGroup):
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
- editing_type: EnumProperty( # pyright: ignore[reportRedeclaration]
+ editing_type: EnumProperty(
items=[(i, i, "") for i in get_args(WorkPlanEditingType)],
)
work_plans: CollectionProperty(name="Work Plans", type=WorkPlan)
@@ -430,8 +430,8 @@ class BIMWorkPlanProperties(PropertyGroup):
class IFCStatus(PropertyGroup):
- name: StringProperty() # pyright: ignore[reportRedeclaration]
- is_visible: BoolProperty( # pyright: ignore[reportRedeclaration]
+ name: StringProperty()
+ is_visible: BoolProperty(
name="Is Visible", default=True, update=lambda x, y: (None, bpy.ops.bim.activate_status_filters())[0]
)
diff --git a/src/bonsai/bonsai/bim/module/spatial/operator.py b/src/bonsai/bonsai/bim/module/spatial/operator.py
index f1eb4ec4ee..cd6bc6cab2 100644
--- a/src/bonsai/bonsai/bim/module/spatial/operator.py
+++ b/src/bonsai/bonsai/bim/module/spatial/operator.py
@@ -220,7 +220,7 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Copy to Container"
bl_options = {"REGISTER", "UNDO"}
- container: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ container: bpy.props.IntProperty()
if TYPE_CHECKING:
container: int
diff --git a/src/bonsai/bonsai/bim/module/structural/operator.py b/src/bonsai/bonsai/bim/module/structural/operator.py
index 20b32c7a0c..825f52e954 100644
--- a/src/bonsai/bonsai/bim/module/structural/operator.py
+++ b/src/bonsai/bonsai/bim/module/structural/operator.py
@@ -167,7 +167,7 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.enable_editing_structural_boundary_condition"
bl_label = "Enable Editing Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
- boundary_condition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ boundary_condition: bpy.props.IntProperty()
if TYPE_CHECKING:
boundary_condition: int
@@ -186,7 +186,7 @@ class EditStructuralBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_structural_boundary_condition"
bl_label = "Edit Structural Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
- connection: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ connection: bpy.props.IntProperty()
if TYPE_CHECKING:
connection: int
@@ -917,7 +917,7 @@ class EnableEditingBoundaryCondition(bpy.types.Operator):
bl_idname = "bim.enable_editing_boundary_condition"
bl_label = "Enable Editing Boundary Condition"
bl_options = {"REGISTER", "UNDO"}
- boundary_condition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ boundary_condition: bpy.props.IntProperty()
if TYPE_CHECKING:
boundary_condition: int
diff --git a/src/bonsai/bonsai/bim/module/structural/shader.py b/src/bonsai/bonsai/bim/module/structural/shader.py
index 9688ce9f0e..b9b5a5c7bc 100644
--- a/src/bonsai/bonsai/bim/module/structural/shader.py
+++ b/src/bonsai/bonsai/bim/module/structural/shader.py
@@ -83,7 +83,7 @@ class DecorationShader:
PARALLEL DISTRIBUTED FORCE,
DISTRIBUTED MOMENT,
"""
- vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments]
+ vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
vert_out.smooth("VEC3", "forces")
vert_out.smooth("VEC3", "co")
@@ -203,7 +203,7 @@ class DecorationShader:
"""param: pattern: type of pattern
SINGLE FORCE,
SINGLE MOMENT"""
- vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty: ignore[too-many-positional-arguments]
+ vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
vert_out.smooth("VEC3", "co")
shader_info = gpu.types.GPUShaderCreateInfo()
@@ -253,7 +253,7 @@ class DecorationShader:
def get_planar_shader(self) -> gpu.types.GPUShader:
"""shader for planar loads"""
- vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty: ignore[too-many-positional-arguments]
+ vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
vert_out.smooth("VEC3", "co")
shader_info = gpu.types.GPUShaderCreateInfo()
diff --git a/src/bonsai/bonsai/bim/module/system/operator.py b/src/bonsai/bonsai/bim/module/system/operator.py
index e1f6f5e9e4..5fe47564c0 100644
--- a/src/bonsai/bonsai/bim/module/system/operator.py
+++ b/src/bonsai/bonsai/bim/module/system/operator.py
@@ -54,7 +54,7 @@ class AddSystem(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add System"
bl_options = {"REGISTER", "UNDO"}
- parent_system_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ parent_system_id: bpy.props.IntProperty()
if TYPE_CHECKING:
parent_system_id: int
diff --git a/src/bonsai/bonsai/bim/module/web/prop.py b/src/bonsai/bonsai/bim/module/web/prop.py
index 42bd272578..e9e5a33185 100644
--- a/src/bonsai/bonsai/bim/module/web/prop.py
+++ b/src/bonsai/bonsai/bim/module/web/prop.py
@@ -26,16 +26,16 @@ from bpy.types import PropertyGroup
class WebProperties(PropertyGroup):
- webserver_port: IntProperty( # pyright: ignore[reportRedeclaration]
+ webserver_port: IntProperty(
name="Webserver Port",
min=0,
max=65535,
)
- is_running: BoolProperty( # pyright: ignore[reportRedeclaration]
+ is_running: BoolProperty(
name="Webserver Running Status",
default=False,
)
- is_connected: BoolProperty( # pyright: ignore[reportRedeclaration]
+ is_connected: BoolProperty(
name="Connection Status",
default=False,
)
diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py
index 3216478132..f1ab24c7b0 100644
--- a/src/bonsai/bonsai/bim/operator.py
+++ b/src/bonsai/bonsai/bim/operator.py
@@ -159,9 +159,9 @@ class SelectURIAttribute(bpy.types.Operator, ImportHelper):
bl_label = "Select URI Attribute"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select a local file"
- attribute_data_path: bpy.props.StringProperty(name="Data Path") # pyright: ignore[reportRedeclaration]
+ attribute_data_path: bpy.props.StringProperty(name="Data Path")
"""Full data path to `Attribute`/string property."""
- use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
+ use_relative_path: bpy.props.BoolProperty(
name="Use Relative Path",
default=False,
)
@@ -601,7 +601,7 @@ class CreateMacBonsaiApp(bpy.types.Operator):
"ALT+click to uninstall Bonsai app if it was installed previously."
)
- uninstall: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration]
+ uninstall: bpy.props.BoolProperty(options={"SKIP_SAVE"})
if TYPE_CHECKING:
uninstall: bool
@@ -1667,7 +1667,7 @@ class BIM_OT_attribute_add_subitem(bpy.types.Operator):
bl_description = "Add subitem to the current attribute"
bl_options = {"REGISTER", "UNDO"}
- data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ data_path: bpy.props.StringProperty()
"""Full data path."""
if TYPE_CHECKING:
@@ -1691,9 +1691,9 @@ class BIM_OT_attribute_remove_subitem(bpy.types.Operator):
bl_description = "Add subitem to the current attribute"
bl_options = {"REGISTER", "UNDO"}
- data_path: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration]
+ data_path: bpy.props.StringProperty()
"""Full data path."""
- index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
+ index: bpy.props.IntProperty()
if TYPE_CHECKING:
data_path: str
diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py
index e5d495c62a..e10243d266 100644
--- a/src/bonsai/bonsai/bim/prop.py
+++ b/src/bonsai/bonsai/bim/prop.py
@@ -333,7 +333,7 @@ class Attribute(PropertyGroup):
filter_glob: StringProperty()
is_null: BoolProperty(name="Is Null", update=update_is_null)
is_selected: BoolProperty(name="Is Selected", default=False)
- subitems_values: CollectionProperty(type=StrProperty) # pyright: ignore[reportRedeclaration]
+ subitems_values: CollectionProperty(type=StrProperty)
# Attribute parameters.
is_optional: BoolProperty(name="Is Optional")
@@ -342,7 +342,7 @@ class Attribute(PropertyGroup):
value_max: FloatProperty(description="This is used to validate int_value and float_value")
value_max_constraint: BoolProperty(default=False, description="True if the numerical value has an upper bound")
special_type: StringProperty(name="Special Value Type", default="")
- use_explorer_ui: BoolProperty() # pyright: ignore[reportRedeclaration]
+ use_explorer_ui: BoolProperty()
metadata: StringProperty(name="Metadata", description="For storing some additional information about the attribute")
update: StringProperty(name="Update", description="Custom update function to be executed")
diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py
index 09eee51401..97980d92dd 100644
--- a/src/bonsai/bonsai/bim/ui.py
+++ b/src/bonsai/bonsai/bim/ui.py
@@ -665,7 +665,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
)
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
- should_always_cache: BoolProperty( # pyright: ignore[reportRedeclaration]
+ should_always_cache: BoolProperty(
name="Always Cache Geometry",
description="Whether to always cache geometry regardless of 'Cache' setting during Advanced Project Load.",
)
diff --git a/src/bonsai/bonsai/tool/ifcgit.py b/src/bonsai/bonsai/tool/ifcgit.py
index 9c7199e839..49e6440bae 100644
--- a/src/bonsai/bonsai/tool/ifcgit.py
+++ b/src/bonsai/bonsai/tool/ifcgit.py
@@ -286,11 +286,11 @@ class IfcGit:
if re.match("^Ifc", obj.name):
bpy.data.objects.remove(obj, do_unlink=True)
- bpy.data.orphans_purge(do_recursive=True) # ty:ignore[unknown-argument]
+ bpy.data.orphans_purge(do_recursive=True)
- from bonsai.bim.module.root.data import IfcClassData
- from bonsai.bim.module.model.data import AuthoringData
import bonsai.bim.handler
+ from bonsai.bim.module.model.data import AuthoringData
+ from bonsai.bim.module.root.data import IfcClassData
AuthoringData.type_thumbnails = {}
diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py
index 38882a5095..dc45b4e9bb 100644
--- a/src/bonsai/bonsai/tool/raycast.py
+++ b/src/bonsai/bonsai/tool/raycast.py
@@ -25,13 +25,12 @@ import bmesh
import bpy
import mathutils
import numpy as np
-from mathutils import Vector
-
from bpy_extras import view3d_utils
+from mathutils import Vector
import bonsai.core.tool
import bonsai.tool as tool
-from bpy_extras import view3d_utils
+
class Raycast(bonsai.core.tool.Raycast):
offset = 10
diff --git a/src/bonsai/docs/guides/development/code_style.rst b/src/bonsai/docs/guides/development/code_style.rst
index cdc506d5ab..96f3d2b9cc 100644
--- a/src/bonsai/docs/guides/development/code_style.rst
+++ b/src/bonsai/docs/guides/development/code_style.rst
@@ -7,7 +7,7 @@ Python code formatters
For Python code formatting, we use `Black code formatter `__,
black settings are stored in the repository's pyproject.toml.
-We have GitHub workflow `ci-black-formatting` to maintain black formatting across the repository.
+We have GitHub workflow `ci-lint` to maintain black formatting across the repository.
``black`` can be installed using ``pip install black`` and files can be formatted with the following example command:
diff --git a/src/bonsai/docs/guides/development/maintenance.rst b/src/bonsai/docs/guides/development/maintenance.rst
index e1728fd7a5..d0de81c757 100644
--- a/src/bonsai/docs/guides/development/maintenance.rst
+++ b/src/bonsai/docs/guides/development/maintenance.rst
@@ -13,7 +13,7 @@ When adding or removing a supported Python version, update the following:
* - File
- What to update
- * - ``.github/workflows/ci-black-formatting.yaml``
+ * - ``.github/workflows/ci-lint.yaml``
- ``MIN_IOS_PY_VERSION``
* - ``.github/workflows/ci-ifcopenshell-python-pypi.yml``
- ``pyver`` matrix
@@ -44,6 +44,8 @@ When a new Blender version is released and supported:
* - File
- What to update
+ * - ``.github/workflows/ci-bonsai.yml``
+ - ``pyver`` matrix
* - ``.github/workflows/ci-bonsai-daily.yml``
- Blender download URL
@@ -57,7 +59,7 @@ When Blender ships with a new Python version:
* - File
- What to update
- * - ``.github/workflows/ci-black-formatting.yaml``
+ * - ``.github/workflows/ci-lint.yaml``
- ``MIN_BLENDER_PY_VERSION``
* - ``src/bonsai/Makefile``
- ``SUPPORTED_PYVERSIONS``
diff --git a/src/bonsai/test/core/test_ifcgit.py b/src/bonsai/test/core/test_ifcgit.py
index 539e4a082c..4884497c8b 100644
--- a/src/bonsai/test/core/test_ifcgit.py
+++ b/src/bonsai/test/core/test_ifcgit.py
@@ -20,7 +20,7 @@
import pytest
import bonsai.core.ifcgit as subject
-from test.core.bootstrap import ifcgit, ifc
+from test.core.bootstrap import ifc, ifcgit
class MockOperator:
diff --git a/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp b/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp
index f07234a8f1..180226fb82 100644
--- a/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp
+++ b/src/ifcgeom/mapping/IfcPointByDistanceExpression.cpp
@@ -52,15 +52,6 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPointByDistanceExpression* i
if (inst->OffsetVertical().has_value()) {
auto offset_vertical = inst->OffsetVertical().get() * length_unit_;
o += offset_vertical * z;
-
- auto tmp1 = (z * offset_vertical).eval();
- auto tmp2 = (Eigen::Vector3d(0, 0, 1) * offset_vertical).eval();
- auto tmp3 = (tmp1 - tmp2).eval();
-
- std::ostringstream oss;
- oss << "local z: " << z.x() << "," << z.y() << "," << z.z() << "; delta: " << tmp3.x() << "," << tmp3.y() << "," << tmp3.z();
- auto osss = oss.str();
- std::wcout << osss.c_str() << std::endl;
}
if (inst->OffsetLongitudinal().has_value()) {
diff --git a/src/ifcmcp/ifcmcp/core.py b/src/ifcmcp/ifcmcp/core.py
index ed9f91c3c9..137cdbdce0 100644
--- a/src/ifcmcp/ifcmcp/core.py
+++ b/src/ifcmcp/ifcmcp/core.py
@@ -278,7 +278,12 @@ class IfcSession:
return info.info(model, element)
def ifc_select(self, query: str) -> list[dict[str, Any]]:
- """Filter elements using ifcopenshell selector syntax (e.g. 'IfcWall', 'IfcWindow')."""
+ """Filter elements using ifcopenshell selector syntax.
+
+ Examples: ``IfcWall``, ``IfcWall, IfcColumn``, ``! IfcWall``,
+ ``IfcWall, Name = "My Wall"``, ``type = "Concrete Wall"``,
+ ``material = "Concrete"``.
+ """
return select.select(self._require_model(), query)
def ifc_relations(self, element_id: int, traverse: str = "") -> dict[str, Any] | list[dict[str, Any]]:
@@ -536,7 +541,12 @@ class IfcSession:
{
"type": "function",
"name": "ifc_select",
- "description": "Select elements using ifcopenshell selector syntax (e.g. 'IfcWall').",
+ "description": (
+ "Select elements using ifcopenshell selector syntax. "
+ "Examples: 'IfcWall', 'IfcWall, IfcColumn', '! IfcWall', "
+ "'IfcWall, Name = \"My Wall\"', 'type = \"Concrete Wall\"', "
+ "'material = \"Concrete\"'."
+ ),
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py
index 1c52ebc49d..8a8a817336 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/element.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/element.py
@@ -469,7 +469,7 @@ def get_properties(
del data["HasProperties"]
results[prop_name] = data
if verbose:
- results[prop_name] = {"id": data["id"], "class": data["class"], "value": results[prop_name]}
+ results[prop_name] = {"id": data["id"], "class": data["type"], "value": results[prop_name]}
return results
diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py
index 5eb6034402..ffd5789f01 100644
--- a/src/ifcopenshell-python/test/util/test_element.py
+++ b/src/ifcopenshell-python/test/util/test_element.py
@@ -307,6 +307,35 @@ class TestGetPropertiesIFC4(test.bootstrap.IFC4):
}
}
+ def test_getting_complex_properties_verbose(self):
+ element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
+ pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="pset")
+ complex_property = self.file.create_entity("IfcComplexProperty", Name="prop", UsageName="usage_name")
+ ifcopenshell.api.pset.edit_pset(self.file, pset=complex_property, properties={"a": "b"})
+ pset.HasProperties = [complex_property]
+ properties = subject.get_properties(pset.HasProperties, verbose=True)
+ prop_value = properties["prop"]["value"]
+ nested_prop = prop_value["properties"]["a"]
+ assert properties == {
+ "prop": {
+ "id": complex_property.id(),
+ "class": "IfcComplexProperty",
+ "value": {
+ "UsageName": "usage_name",
+ "id": complex_property.id(),
+ "type": "IfcComplexProperty",
+ "properties": {
+ "a": {
+ "id": nested_prop["id"],
+ "class": "IfcPropertySingleValue",
+ "value": "b",
+ "value_type": "IfcLabel",
+ }
+ },
+ },
+ }
+ }
+
class TestGetElementsUsingPset(test.bootstrap.IFC4):
def test_run(self):
diff --git a/src/ifcquery/ifcquery/select.py b/src/ifcquery/ifcquery/select.py
index c28a159b90..f3ee1dceaa 100644
--- a/src/ifcquery/ifcquery/select.py
+++ b/src/ifcquery/ifcquery/select.py
@@ -26,7 +26,16 @@ import ifcopenshell.util.selector
def select(model: ifcopenshell.file, query: str) -> list[dict[str, Any]]:
- """Filter elements using selector syntax and return matching element summaries."""
+ """Filter elements using ifcopenshell selector syntax and return matching element summaries.
+
+ Examples:
+ - ``IfcWall`` — all walls
+ - ``IfcWall, IfcColumn`` — walls and columns
+ - ``! IfcWall`` — everything except walls
+ - ``IfcWall, Name = "My Wall"`` — walls with a specific name attribute
+ - ``type = "Concrete Wall"`` — elements assigned that type product
+ - ``material = "Concrete"`` — elements with that material
+ """
elements = ifcopenshell.util.selector.filter_elements(model, query)
results = []
for element in sorted(elements, key=lambda e: e.id()):
diff --git a/src/ifcsverchok/nodes/ifc/shape_builder/extrude.py b/src/ifcsverchok/nodes/ifc/shape_builder/extrude.py
index 1725f37aa2..350f4ac71f 100644
--- a/src/ifcsverchok/nodes/ifc/shape_builder/extrude.py
+++ b/src/ifcsverchok/nodes/ifc/shape_builder/extrude.py
@@ -32,7 +32,7 @@ class SvIfcSbExtrude(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.Sv
bl_idname = "SvIfcSbExtrude"
bl_label = "IFC Extrude"
- extrude_axis: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
+ extrude_axis: bpy.props.EnumProperty(
default="Z",
items=[
("X", "X", "Interpret curve as in XY plane and extrude along X+."),
diff --git a/src/ifctester/webapp/package-lock.json b/src/ifctester/webapp/package-lock.json
index 1244317fd2..90a6743443 100644
--- a/src/ifctester/webapp/package-lock.json
+++ b/src/ifctester/webapp/package-lock.json
@@ -33,7 +33,7 @@
"tailwindcss": "^4.0.0",
"tw-animate-css": "^1.3.2",
"typescript": "^5.8.3",
- "vite": "^6.4.1"
+ "vite": "^6.4.2"
}
},
"node_modules/@ampproject/remapping": {
@@ -3264,9 +3264,9 @@
"license": "MIT"
},
"node_modules/vite": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
- "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
+ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/src/ifctester/webapp/package.json b/src/ifctester/webapp/package.json
index 14b54ad8ed..f36baf96ec 100644
--- a/src/ifctester/webapp/package.json
+++ b/src/ifctester/webapp/package.json
@@ -30,7 +30,7 @@
"tailwindcss": "^4.0.0",
"typescript": "^5.8.3",
"tw-animate-css": "^1.3.2",
- "vite": "^6.4.1"
+ "vite": "^6.4.2"
},
"dependencies": {
"eventemitter3": "^5.0.1",
diff --git a/src/svgfill/src/arrange_polygons.cpp b/src/svgfill/src/arrange_polygons.cpp
index 0d5cfd96e3..ba8785a267 100644
--- a/src/svgfill/src/arrange_polygons.cpp
+++ b/src/svgfill/src/arrange_polygons.cpp
@@ -615,9 +615,6 @@ void eliminate_overlaps(DebugWriter& debug_writer, double OVERLAP_RESOLUTION_DIS
std::swap(poly1, poly2);
}
- std::cerr << "processing: " << edge.first << " " << edge.second << std::endl;
- std::cerr << "area before: " << poly1->area() << " " << poly2->area() << std::endl;
-
bool is_ = edge == std::make_pair(25, 27);
bool success = false;
@@ -657,8 +654,6 @@ void eliminate_overlaps(DebugWriter& debug_writer, double OVERLAP_RESOLUTION_DIS
}
}
- std::cerr << "area after: " << poly1->area() << " " << poly2->area() << std::endl;
-
if (!success) {
eliminated_polies.insert(swap ? edge.first : edge.second);
continue;
@@ -911,6 +906,681 @@ build_line_graph(const std::vector& input_polygons, SegmentLookup& se
return {line_graph, midpoint_to_segment, segment_to_input_facet, midpoint_to_edge_length};
}
+using DPoint = CGAL::Simple_cartesian::Point_2;
+using DDir = CGAL::Simple_cartesian::Vector_2;
+using DBox = std::array;
+
+struct CenterLineGraphData {
+ std::vector points;
+ std::vector points_double;
+ std::vector widths;
+ std::vector> edges;
+ std::vector> incident_edges;
+};
+
+struct LineRun {
+ Point_2 start_exact;
+ Point_2 end_exact;
+ DPoint start;
+ DPoint end;
+ DDir direction;
+ double avg_width;
+ double length;
+ size_t vertex_count;
+};
+
+struct RunBoxRecord {
+ size_t run_index;
+ DPoint start;
+ DPoint end;
+ DDir direction;
+ double width;
+ double length;
+ std::array corners;
+ DBox bbox;
+};
+
+struct MergedBoxRecord {
+ DPoint start;
+ DPoint end;
+ DDir direction;
+ DDir normal;
+ double avg_width;
+ double length;
+ size_t member_count;
+ std::vector members;
+ std::array corners;
+ DBox bbox;
+ Point_2 exact_start;
+ Point_2 exact_end;
+};
+
+struct BoxCluster {
+ std::vector members;
+ MergedBoxRecord box;
+};
+
+struct SnapCandidate {
+ size_t box_index;
+ double box_distance;
+ double line_distance;
+ Point_2 projection;
+};
+
+DDir unit(const DDir& a) {
+ auto n = std::sqrt(a.squared_length());
+ if (n < 1.e-9) {
+ return {0., 0.};
+ }
+ return a / n;
+}
+
+DDir perpendicular(const DDir& a) {
+ return DDir(-a.y(), a.x());
+}
+
+DDir canonicalize_like(const DDir& a, const DDir& ref) {
+ return (a * ref) < 0. ? -a : a;
+}
+
+DPoint to_double_point(const Point_2& p) {
+ return {CGAL::to_double(p.x()), CGAL::to_double(p.y())};
+}
+
+Point_2 to_exact_point(const DPoint& p) {
+ return Point_2(p.x(), p.y());
+}
+
+double point_line_distance(const DPoint& p, const DPoint& line_point, const DDir& line_dir) {
+ auto u = unit(line_dir);
+ auto delta = (p - line_point);
+ if (u.squared_length() < 1.e-18) {
+ return std::sqrt(delta.squared_length());
+ }
+ return std::abs(CGAL::determinant(u.x(), u.y(), delta.x(), delta.y()));
+}
+
+double angle_between_dirs_deg(const DDir& a, const DDir& b) {
+ auto u = unit(a);
+ auto v = unit(b);
+ auto c = std::abs(u * v);
+ if (c > 1.) {
+ c = 1.;
+ }
+ return std::acos(c) * 180. / 3.14159265358979323846;
+}
+
+std::array rectangle_corners(const DPoint& start, const DPoint& end, double width) {
+ auto u = unit(end - start);
+ if (u.squared_length() < 1.e-18) {
+ u = {1., 0.};
+ }
+ auto n = perpendicular(u);
+ auto ext = width;
+ auto p0 = start - u * ext;
+ auto p1 = end + u * ext;
+ auto w = n * (width / 2.);
+ return {p0 + w, p1 + w, p1 - w, p0 - w};
+}
+
+DBox aabb_from_points(const std::array& corners) {
+ DBox bbox{corners[0], corners[0]};
+ for (auto& p : corners) {
+ bbox[0] = {std::min(bbox[0].x(), p.x()), std::min(bbox[0].y(), p.y())};
+ bbox[1] = {std::max(bbox[1].x(), p.x()), std::max(bbox[1].y(), p.y())};
+ }
+ return bbox;
+}
+
+bool aabb_overlap(const DBox& a, const DBox& b, double eps = 1.e-9) {
+ return a[0].x() <= b[1].x() + eps &&
+ a[1].x() + eps >= b[0].x() &&
+ a[0].y() <= b[1].y() + eps &&
+ a[1].y() + eps >= b[0].y();
+}
+
+CenterLineGraphData make_center_line_graph_data(
+ const std::map>& line_graph,
+ const std::map& midpoint_to_edge_length)
+{
+ CenterLineGraphData graph;
+ std::map point_to_index;
+
+ auto ensure_point = [&](const Point_2& p) {
+ auto it = point_to_index.find(p);
+ if (it != point_to_index.end()) {
+ return it->second;
+ }
+ auto i = graph.points.size();
+ point_to_index[p] = i;
+ graph.points.push_back(p);
+ graph.points_double.push_back(to_double_point(p));
+ auto wt = midpoint_to_edge_length.find(p);
+ graph.widths.push_back(wt == midpoint_to_edge_length.end() ? 0. : wt->second);
+ graph.incident_edges.emplace_back();
+ return i;
+ };
+
+ for (auto& p : line_graph) {
+ ensure_point(p.first);
+ for (auto& q : p.second) {
+ ensure_point(q);
+ }
+ }
+
+ std::set> seen_edges;
+ for (auto& p : line_graph) {
+ auto i = ensure_point(p.first);
+ for (auto& q : p.second) {
+ auto j = ensure_point(q);
+ if (i == j) {
+ continue;
+ }
+ auto e = i < j ? std::make_pair(i, j) : std::make_pair(j, i);
+ if (seen_edges.insert(e).second) {
+ auto k = graph.edges.size();
+ graph.edges.push_back(e);
+ graph.incident_edges[e.first].push_back(k);
+ graph.incident_edges[e.second].push_back(k);
+ }
+ }
+ }
+
+ return graph;
+}
+
+double segment_width(const CenterLineGraphData& graph, const std::pair& edge) {
+ return 0.5 * (graph.widths[edge.first] + graph.widths[edge.second]);
+}
+
+bool edge_supports_same_line(
+ const DPoint& seed_a,
+ const DPoint& seed_b,
+ const DPoint& test_a,
+ const DPoint& test_b,
+ double angle_tol_deg = 3.,
+ double line_dist_tol = 0.15)
+{
+ auto d_seed = seed_b - seed_a;
+ auto d_test = test_b - test_a;
+ if (d_seed.squared_length() < 1.e-18 || d_test.squared_length() < 1.e-18) {
+ return false;
+ }
+ if (angle_between_dirs_deg(d_seed, d_test) > angle_tol_deg) {
+ return false;
+ }
+ return
+ point_line_distance(test_a, seed_a, d_seed) <= line_dist_tol &&
+ point_line_distance(test_b, seed_a, d_seed) <= line_dist_tol;
+}
+
+std::vector runs_from_graph(const CenterLineGraphData& graph, double angle_tol_deg = 3., double line_dist_tol = 0.15) {
+ std::vector visited(graph.edges.size(), false);
+ std::vector runs;
+
+ for (size_t seed_ei = 0; seed_ei < graph.edges.size(); ++seed_ei) {
+ if (visited[seed_ei]) {
+ continue;
+ }
+
+ const auto& seed_edge = graph.edges[seed_ei];
+ auto seed_a = graph.points_double[seed_edge.first];
+ auto seed_b = graph.points_double[seed_edge.second];
+ auto seed_dir = seed_b - seed_a;
+ if (seed_dir.squared_length() < 1.e-18) {
+ visited[seed_ei] = true;
+ continue;
+ }
+
+ std::vector queue = {seed_ei};
+ std::set component_edges;
+
+ while (!queue.empty()) {
+ auto ei = queue.back();
+ queue.pop_back();
+ if (!component_edges.insert(ei).second) {
+ continue;
+ }
+
+ const auto& edge = graph.edges[ei];
+ std::array vertices = {edge.first, edge.second};
+ for (auto v : vertices) {
+ for (auto ej : graph.incident_edges[v]) {
+ if (ej == ei || visited[ej] || component_edges.count(ej)) {
+ continue;
+ }
+ const auto& candidate = graph.edges[ej];
+ auto test_a = graph.points_double[candidate.first];
+ auto test_b = graph.points_double[candidate.second];
+ if (edge_supports_same_line(seed_a, seed_b, test_a, test_b, angle_tol_deg, line_dist_tol)) {
+ queue.push_back(ej);
+ }
+ }
+ }
+ }
+
+ for (auto ei : component_edges) {
+ visited[ei] = true;
+ }
+
+ std::set component_vertices;
+ auto ref = unit(seed_dir);
+ DDir direction_sum{0., 0.};
+ double total_length = 0.;
+ double weighted_width_sum = 0.;
+
+ for (auto ei : component_edges) {
+ const auto& edge = graph.edges[ei];
+ component_vertices.insert(edge.first);
+ component_vertices.insert(edge.second);
+
+ auto d = graph.points_double[edge.second] - graph.points_double[edge.first];
+ auto u = canonicalize_like(unit(d), ref);
+ direction_sum = direction_sum + u;
+
+ auto len = std::sqrt(d.squared_length());
+ total_length += len;
+ weighted_width_sum += len * segment_width(graph, edge);
+ }
+
+ auto run_direction = direction_sum.squared_length() < 1.e-18 ? ref : unit(direction_sum);
+
+ double min_t = std::numeric_limits::infinity();
+ double max_t = -std::numeric_limits::infinity();
+ size_t start_index = *component_vertices.begin();
+ size_t end_index = start_index;
+ for (auto vi : component_vertices) {
+ auto t = (graph.points_double[vi] - CGAL::ORIGIN) * run_direction;
+ if (t < min_t) {
+ min_t = t;
+ start_index = vi;
+ }
+ if (t > max_t) {
+ max_t = t;
+ end_index = vi;
+ }
+ }
+
+ auto avg_width = total_length < 1.e-9 ? segment_width(graph, seed_edge) : weighted_width_sum / total_length;
+
+ runs.push_back({
+ graph.points[start_index],
+ graph.points[end_index],
+ graph.points_double[start_index],
+ graph.points_double[end_index],
+ run_direction,
+ avg_width,
+ std::sqrt((graph.points_double[end_index] - graph.points_double[start_index]).squared_length()),
+ component_vertices.size()
+ });
+ }
+
+ return runs;
+}
+
+std::vector build_run_box_records(const std::vector& runs) {
+ std::vector records;
+ records.reserve(runs.size());
+ for (size_t i = 0; i < runs.size(); ++i) {
+ auto corners = rectangle_corners(runs[i].start, runs[i].end, runs[i].avg_width);
+ records.push_back({
+ i,
+ runs[i].start,
+ runs[i].end,
+ unit(runs[i].end - runs[i].start),
+ runs[i].avg_width,
+ runs[i].length,
+ corners,
+ aabb_from_points(corners)
+ });
+ }
+ return records;
+}
+
+template
+std::pair projected_interval_on_axis(const T& box, const DDir& axis_u) {
+ auto u = unit(axis_u);
+ auto ta = (box.start - CGAL::ORIGIN) * u;
+ auto tb = (box.end - CGAL::ORIGIN) * u;
+ return {std::min(ta, tb), std::max(ta, tb)};
+}
+
+double interval_overlap_length(const std::pair& a, const std::pair& b) {
+ return std::max(0., std::min(a.second, b.second) - std::max(a.first, b.first));
+}
+
+template
+double boxes_overlap_along_merge_axis(const T& a, const T& b) {
+ auto d1 = unit(a.end - a.start);
+ auto d2 = unit(b.end - b.start);
+ if (d1 * d2 < 0.) {
+ d2 = {-d2.x(), -d2.y()};
+ }
+ auto merge_axis = unit(d1 + d2);
+ if (merge_axis.squared_length() < 1.e-18) {
+ merge_axis = d1;
+ }
+
+ auto i1 = projected_interval_on_axis(a, merge_axis);
+ auto i2 = projected_interval_on_axis(b, merge_axis);
+ auto overlap = interval_overlap_length(i1, i2);
+ auto small_length = std::min(i1.second - i1.first, i2.second - i2.first);
+ if (small_length < 1.e-9) {
+ return false;
+ }
+ return overlap / small_length;
+}
+
+MergedBoxRecord merge_cluster_to_box(const std::vector& member_indices, const std::vector& records) {
+ auto ref = records[member_indices.front()].direction;
+ DDir direction_sum{0., 0.};
+ for (auto i : member_indices) {
+ auto u = canonicalize_like(records[i].direction, ref);
+ direction_sum = direction_sum + u * std::max(records[i].length, 1.e-9);
+ }
+
+ auto u = direction_sum.squared_length() < 1.e-18 ? ref : unit(direction_sum);
+ auto n = perpendicular(u);
+
+ double tmin = std::numeric_limits::infinity();
+ double tmax = -std::numeric_limits::infinity();
+ double smin = std::numeric_limits::infinity();
+ double smax = -std::numeric_limits::infinity();
+
+ for (auto i : member_indices) {
+ for (auto& corner : records[i].corners) {
+ auto t = (corner - CGAL::ORIGIN) * u;
+ auto s = (corner - CGAL::ORIGIN) * n;
+ tmin = std::min(tmin, t);
+ tmax = std::max(tmax, t);
+ smin = std::min(smin, s);
+ smax = std::max(smax, s);
+ }
+ }
+
+ auto width = smax - smin;
+ auto sc = (smin + smax) / 2.;
+ auto start = u * tmin + n * sc;
+ auto end = u * tmax + n * sc;
+ auto corners = rectangle_corners(CGAL::ORIGIN + start, CGAL::ORIGIN + end, width);
+
+ MergedBoxRecord box{
+ CGAL::ORIGIN + start,
+ CGAL::ORIGIN + end,
+ u,
+ n,
+ width,
+ std::sqrt((end - start).squared_length()),
+ member_indices.size(),
+ member_indices,
+ corners,
+ aabb_from_points(corners),
+ to_exact_point(CGAL::ORIGIN + start),
+ to_exact_point(CGAL::ORIGIN + end)
+ };
+ return box;
+}
+
+std::pair merge_score(const MergedBoxRecord& a, const MergedBoxRecord& b) {
+ auto ang = angle_between_dirs_deg(a.direction, b.direction);
+ auto center_a = ((a.start - CGAL::ORIGIN) + (a.end - CGAL::ORIGIN)) / 2.;
+ auto center_b = ((b.start - CGAL::ORIGIN) + (b.end - CGAL::ORIGIN)) / 2.;
+ return {ang, std::sqrt((center_b - center_a).squared_length())};
+}
+
+bool clusters_can_merge(const BoxCluster& a, const BoxCluster& b, double angle_tol_deg = 5., double axis_overlap_ratio_limit = 0.5) {
+ if (!aabb_overlap(a.box.bbox, b.box.bbox)) {
+ return false;
+ }
+ if (angle_between_dirs_deg(a.box.direction, b.box.direction) > angle_tol_deg) {
+ return false;
+ }
+ if (boxes_overlap_along_merge_axis(a.box, b.box) > axis_overlap_ratio_limit) {
+ auto a_center = CGAL::ORIGIN + ((a.box.start - CGAL::ORIGIN) + (a.box.end - CGAL::ORIGIN)) / 2.;
+ auto b_center = CGAL::ORIGIN + ((b.box.start - CGAL::ORIGIN) + (b.box.end - CGAL::ORIGIN)) / 2.;
+ auto a_dir = a.box.direction;
+ auto b_dir = b.box.direction;
+ auto dist = a.box.length < b.box.length ? point_line_distance(a_center, b_center, b_dir) : point_line_distance(b_center, a_center, a_dir);
+ auto ref = a.box.length < b.box.length ? a.box.avg_width : b.box.avg_width;
+ return dist < (ref / 4.);
+ }
+ return true;
+}
+
+std::vector merge_intersecting_parallel_boxes_iterative(const std::vector& runs) {
+ auto records = build_run_box_records(runs);
+ std::vector clusters;
+ clusters.reserve(records.size());
+ for (size_t i = 0; i < records.size(); ++i) {
+ clusters.push_back({{i}, merge_cluster_to_box({i}, records)});
+ }
+
+ while (true) {
+ std::optional> best_pair;
+ std::pair best_score;
+
+ for (size_t i = 0; i < clusters.size(); ++i) {
+ for (size_t j = i + 1; j < clusters.size(); ++j) {
+ if (!clusters_can_merge(clusters[i], clusters[j])) {
+ continue;
+ }
+ auto score = merge_score(clusters[i].box, clusters[j].box);
+ if (!best_pair || score < best_score) {
+ best_pair = std::make_pair(i, j);
+ best_score = score;
+ }
+ }
+ }
+
+ if (!best_pair) {
+ break;
+ }
+
+ auto i = best_pair->first;
+ auto j = best_pair->second;
+ std::vector members = clusters[i].members;
+ members.insert(members.end(), clusters[j].members.begin(), clusters[j].members.end());
+ auto merged = BoxCluster{members, merge_cluster_to_box(members, records)};
+
+ std::vector next_clusters;
+ next_clusters.reserve(clusters.size() - 1);
+ for (size_t k = 0; k < clusters.size(); ++k) {
+ if (k != i && k != j) {
+ next_clusters.push_back(std::move(clusters[k]));
+ }
+ }
+ next_clusters.push_back(std::move(merged));
+ clusters = std::move(next_clusters);
+ }
+
+ std::vector merged_boxes;
+ merged_boxes.reserve(clusters.size());
+ for (auto& cluster : clusters) {
+ merged_boxes.push_back(cluster.box);
+ }
+ return merged_boxes;
+}
+
+Point_2 project_point_to_line_exact(const Point_2& p, const MergedBoxRecord& box) {
+ auto d = box.exact_end - box.exact_start;
+ if (d.squared_length() == 0) {
+ return box.exact_start;
+ }
+ auto t = ((p - box.exact_start) * d) / d.squared_length();
+ return box.exact_start + d * t;
+}
+
+boost::optional intersect_infinite_lines_exact(const MergedBoxRecord& a, const MergedBoxRecord& b) {
+ if (a.exact_start == a.exact_end || b.exact_start == b.exact_end) {
+ return boost::none;
+ }
+ auto x = CGAL::intersection(CGAL::Line_2(a.exact_start, a.exact_end), CGAL::Line_2(b.exact_start, b.exact_end));
+ if (!x) {
+ return boost::none;
+ }
+ if (auto* xp = variant_get(&*x)) {
+ return *xp;
+ }
+ return boost::none;
+}
+
+double point_to_oriented_box_distance(const DPoint& p, const MergedBoxRecord& box) {
+ auto d = box.end - box.start;
+ auto L = std::sqrt(d.squared_length());
+ if (L < 1.e-9) {
+ return std::sqrt((p - box.start).squared_length());
+ }
+
+ auto u = d / L;
+ auto n = perpendicular(u);
+ auto rel = p - box.start;
+ auto t = rel * u;
+ auto s = rel * n;
+
+ auto tmin = -box.avg_width / 2.;
+ auto tmax = L + box.avg_width / 2.;
+ auto smin = -box.avg_width / 2.;
+ auto smax = box.avg_width / 2.;
+
+ double dt = 0.;
+ if (t < tmin) {
+ dt = tmin - t;
+ } else if (t > tmax) {
+ dt = t - tmax;
+ }
+
+ double ds = 0.;
+ if (s < smin) {
+ ds = smin - s;
+ } else if (s > smax) {
+ ds = s - smax;
+ }
+
+ return std::hypot(dt, ds);
+}
+
+std::map> snap_points_to_box_axes(
+ const CenterLineGraphData& graph,
+ const std::vector& boxes)
+{
+ std::vector snapped_points(graph.points.size());
+
+ for (size_t i = 0; i < graph.points.size(); ++i) {
+ if (boxes.empty()) {
+ snapped_points[i] = graph.points[i];
+ continue;
+ }
+
+ std::vector candidates;
+ candidates.reserve(boxes.size());
+ for (size_t j = 0; j < boxes.size(); ++j) {
+ candidates.push_back({
+ j,
+ point_to_oriented_box_distance(graph.points_double[i], boxes[j]),
+ point_line_distance(graph.points_double[i], boxes[j].start, boxes[j].direction),
+ project_point_to_line_exact(graph.points[i], boxes[j])
+ });
+ }
+
+ std::vector containing;
+ for (auto& candidate : candidates) {
+ if (candidate.box_distance <= 1.e-9) {
+ containing.push_back(candidate);
+ }
+ }
+
+ auto less = [](const SnapCandidate& a, const SnapCandidate& b) {
+ if (a.line_distance != b.line_distance) {
+ return a.line_distance < b.line_distance;
+ }
+ return a.box_distance < b.box_distance;
+ };
+
+ if (containing.size() >= 2) {
+ std::sort(containing.begin(), containing.end(), less);
+ auto& c1 = containing[0];
+ auto& c2 = containing[1];
+ if (angle_between_dirs_deg(boxes[c1.box_index].direction, boxes[c2.box_index].direction) > 8.) {
+ if (auto x = intersect_infinite_lines_exact(boxes[c1.box_index], boxes[c2.box_index])) {
+ snapped_points[i] = *x;
+ continue;
+ }
+ }
+ snapped_points[i] = c1.projection;
+ continue;
+ }
+
+ if (containing.size() == 1) {
+ snapped_points[i] = containing[0].projection;
+ continue;
+ }
+
+ auto best = *std::min_element(candidates.begin(), candidates.end(), [](const SnapCandidate& a, const SnapCandidate& b) {
+ if (a.box_distance != b.box_distance) {
+ return a.box_distance < b.box_distance;
+ }
+ return a.line_distance < b.line_distance;
+ });
+ snapped_points[i] = best.projection;
+ }
+
+ std::map> adjacency;
+ for (auto& edge : graph.edges) {
+ auto a = snapped_points[edge.first];
+ auto b = snapped_points[edge.second];
+ if (a == b) {
+ continue;
+ }
+ adjacency[a].insert(b);
+ adjacency[b].insert(a);
+ }
+
+ std::map> snapped_graph;
+ for (auto& p : adjacency) {
+ snapped_graph[p.first] = {p.second.begin(), p.second.end()};
+ }
+ return snapped_graph;
+}
+
+Graph2D join_segment_runs(
+ DebugWriter& debug,
+ const std::map>& line_graph,
+ const std::map& midpoint_to_edge_length)
+{
+ auto graph = make_center_line_graph_data(line_graph, midpoint_to_edge_length);
+ auto runs = runs_from_graph(graph);
+ runs.erase(std::remove_if(runs.begin(), runs.end(), [](const LineRun& run) {
+ return run.vertex_count <= 5;
+ }), runs.end());
+
+ std::vector run_polygons;
+ for (auto& r : runs) {
+ auto ps = rectangle_corners(r.start, r.end, r.avg_width);
+ std::array exact_corners;
+ std::transform(ps.begin(), ps.end(), exact_corners.begin(), [](const DPoint& p) {
+ return to_exact_point(p);
+ });
+ run_polygons.emplace_back(exact_corners.begin(), exact_corners.end());
+ }
+ debug.write_polygons(run_polygons, "initial_runs");
+ run_polygons.clear();
+
+ auto boxes = merge_intersecting_parallel_boxes_iterative(runs);
+
+ for (auto& r : boxes) {
+ auto ps = rectangle_corners(r.start, r.end, r.avg_width);
+ std::array exact_corners;
+ std::transform(ps.begin(), ps.end(), exact_corners.begin(), [](const DPoint& p) {
+ return to_exact_point(p);
+ });
+ run_polygons.emplace_back(exact_corners.begin(), exact_corners.end());
+ }
+ debug.write_polygons(run_polygons, "merged_boxes");
+
+ auto snapped_graph = snap_points_to_box_axes(graph, boxes);
+ return Graph2D(snapped_graph);
+}
+
std::set> find_triangles(const std::map>& line_graph) {
// Find triangles in this network often occuring at junctions in the corridor mesh
std::set> triangles;
@@ -1249,8 +1919,6 @@ std::list> extend_end_vertices_based_on_input(
// create ray incoming -> M
CGAL::Ray_2 ray(incoming, M - incoming);
- std::cerr << "Extending end vertex " << M << " along ray " << ray << " to boundary of input polygon" << std::endl;
-
// intersect ray with boundary
boost::optional> closest_segment;
boost::optional> closest_intersection_point;
@@ -1261,7 +1929,6 @@ std::list> extend_end_vertices_based_on_input(
if (x) {
if (auto* xp = variant_get>(&*x)) {
auto dist = ((*xp) - M).squared_length();
- std::cerr << " - found " << *xp << " on segment " << seg << " with distance " << std::sqrt(CGAL::to_double(dist)) << std::endl;
if (dist < sq_distance_along_ray) {
if (dist < (max_projection_distance * max_projection_distance)) {
closest_segment = seg;
@@ -1329,9 +1996,11 @@ std::list> extend_end_vertices_based_on_input(
auto Pp = seg.supporting_line().projection(M);
if (seg.has_on(Pp)) {
auto d = CGAL::squared_distance(Pp, M);
- if (d < closest_distance) {
- closest_distance = d;
- closest_point = Pp;
+ if (d < (max_projection_distance * max_projection_distance)) {
+ if (d < closest_distance) {
+ closest_distance = d;
+ closest_point = Pp;
+ }
}
}
}
@@ -1651,13 +2320,10 @@ void clean_noisy_paths(DebugWriter& debug_output, Arrangement_2& arr, SegmentLoo
return (best + 0.01) / own_length;
};
- std::cerr << "badnesses:";
std::map badnesses;
for (auto& e : edges) {
badnesses[e] = edge_badness(e);
- std::cerr << " (" << e.source().x() << "," << e.source().y() << ") - (" << e.target().x() << "," << e.target().y() << "): " << badnesses[e] << ";";
}
- std::cerr << std::endl;
{
std::vector tmp;
@@ -1670,8 +2336,6 @@ void clean_noisy_paths(DebugWriter& debug_output, Arrangement_2& arr, SegmentLoo
threshold = 4.0 * med;
}
- std::cerr << "badness threshold: " << threshold << std::endl;
-
std::set bad_edges;
for (auto& p : badnesses) {
if (p.second > threshold) {
@@ -1835,14 +2499,6 @@ void clean_noisy_paths(DebugWriter& debug_output, Arrangement_2& arr, SegmentLoo
decltype(to_remove) to_remove_this_path;
decltype(to_insert) to_insert_this_path;
- std::cerr << "Processing bad path:";
- for (size_t i = 0; i < path.size() - 1; ++i) {
- auto& a = path[i];
- auto& b = path[i + 1];
- std::cerr << " (" << a.x() << "," << a.y() << ") - (" << b.x() << "," << b.y() << ");";
- }
- std::cerr << std::endl;
-
for (size_t i = 0; i < path.size() - 1; ++i) {
auto& a = path[i];
auto& b = path[i + 1];
@@ -1852,7 +2508,7 @@ void clean_noisy_paths(DebugWriter& debug_output, Arrangement_2& arr, SegmentLoo
auto x = collapse_path(path);
if (!x) {
- std::cerr << "Unable to collapse path, skipping" << std::endl;
+ // std::cerr << "Unable to collapse path, skipping" << std::endl;
continue;
}
@@ -1866,16 +2522,14 @@ void clean_noisy_paths(DebugWriter& debug_output, Arrangement_2& arr, SegmentLoo
double new_length = std::sqrt(CGAL::to_double((path.front() - *x).squared_length())) + std::sqrt(CGAL::to_double((path.back() - *x).squared_length()));
if (new_length > orig_length * 2 || orig_length > new_length * 2) {
- std::cerr << "Collapsing path would increase length too much, skipping" << std::endl;
+ // std::cerr << "Collapsing path would increase length too much, skipping" << std::endl;
continue;
}
- std::cerr << "new_length: " << new_length << " orig_length: " << orig_length << std::endl;
-
for (size_t i = 0; i < path.size(); ++i) {
auto& v = path[i];
if (CGAL::squared_distance(v, *x) < 1.e-5) {
- std::cerr << "Collapsing path would create near-duplicate vert to previous path, skipping" << std::endl;
+ // std::cerr << "Collapsing path would create near-duplicate vert to previous path, skipping" << std::endl;
continue;
}
}
@@ -2059,7 +2713,6 @@ void clean_noisy_bounds(DebugWriter& debug_output, Arrangement_2& arr, SegmentLo
size_t facet_index = 0;
for (auto it = arr.faces_begin(); it != arr.faces_end(); ++it, ++facet_index) {
- std::cout << "facet_index " << facet_index << std::endl;
if (!it->is_unbounded()) {
std::set> to_remove;
std::vector> to_insert;
@@ -2078,17 +2731,14 @@ void clean_noisy_bounds(DebugWriter& debug_output, Arrangement_2& arr, SegmentLo
++circ;
} while (circ != it->outer_ccb());
- std::cerr << "badnesses:";
std::vector badnesses;
for (auto& e : segs) {
badnesses.push_back(edge_badness(e));
- std::cerr << " (" << e.source().x() << "," << e.source().y() << ") - (" << e.target().x() << "," << e.target().y() << "): " << badnesses.back() << ";";
}
- std::cerr << std::endl;
auto bit = std::min_element(badnesses.begin(), badnesses.end());
if (*bit > threshold) {
- std::cerr << "All edges are good, skipping" << std::endl;
+ // std::cerr << "All edges are good, skipping" << std::endl;
continue;
}
@@ -2096,19 +2746,15 @@ void clean_noisy_bounds(DebugWriter& debug_output, Arrangement_2& arr, SegmentLo
auto N = circular_distance(it_pair.first, it_pair.second, badnesses);
if (N == 0) {
- std::cerr << "Unable to find run of bad edges, skipping" << std::endl;
+ // std::cerr << "Unable to find run of bad edges, skipping" << std::endl;
continue;
}
std::vector> incoming_paths;
- std::cout << "range " << std::distance(badnesses.cbegin(), it_pair.first) << " to " << std::distance(badnesses.cbegin(), it_pair.second) << " length " << N << std::endl;
-
auto jt = it_pair.first;
for (std::size_t k = 0; k < N; ++k, next_circular(jt, badnesses)) {
- std::cout << " at " << std::distance(badnesses.cbegin(), jt) << " badness: " << *jt << std::endl;
-
auto he = halfedges[std::distance(badnesses.cbegin(), jt)];
to_remove.insert({he->source()->point(), he->target()->point()});
debug_output.write_segment(he->source()->point(), he->target()->point(), "arr_bad_bound facet_" + std::to_string(facet_index));
@@ -2175,12 +2821,9 @@ void clean_noisy_bounds(DebugWriter& debug_output, Arrangement_2& arr, SegmentLo
CGAL::Ray_2 r1((*a)->point(), (*b)->point());
CGAL::Ray_2 r2((*d)->point(), (*c)->point());
- std::cout << "a: (" << (*a)->point().x() << "," << (*a)->point().y() << ") b: (" << (*b)->point().x() << "," << (*b)->point().y() << ") c: (" << (*c)->point().x() << "," << (*c)->point().y() << ") d: (" << (*d)->point().x() << "," << (*d)->point().y() << ")" << std::endl;
-
auto x = CGAL::intersection(r1, r2);
if (x) {
if (auto* xp = variant_get>(&*x)) {
- std::cout << "ray xp: (" << xp->x() << "," << xp->y() << ")" << std::endl;
to_insert.emplace_back((*b)->point(), *xp);
to_insert.emplace_back((*c)->point(), *xp);
@@ -2194,7 +2837,6 @@ void clean_noisy_bounds(DebugWriter& debug_output, Arrangement_2& arr, SegmentLo
auto x = CGAL::intersection(r1, r2);
if (x) {
if (auto* xp = variant_get>(&*x)) {
- std::cout << "line xp: (" << xp->x() << "," << xp->y() << ")" << std::endl;
to_insert.emplace_back((*b)->point(), *xp);
to_insert.emplace_back((*c)->point(), *xp);
@@ -2495,64 +3137,44 @@ void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std
}
}
- // Write a JSON structure with center line topology with the midpoint_to_edge_length as per-point data
- {
- std::ofstream ofs("center_line_topology.json");
- ofs << "{\n";
- ofs << " \"vertices\": [\n";
- bool first_vertex = true;
- for (auto& p : line_graph) {
- if (!first_vertex) {
- ofs << ",\n";
- }
- first_vertex = false;
- ofs << " {\n";
- ofs << " \"point\": [" << p.first.x() << ", " << p.first.y() << "],\n";
- ofs << " \"width\":" << midpoint_to_edge_length.find(p.first)->second << ",\n";
- ofs << " \"connected_to\": [\n";
- bool first_connected = true;
- for (auto& q : p.second) {
- if (!first_connected) {
- ofs << ",\n";
- }
- first_connected = false;
- ofs << " [" << q.x() << ", " << q.y() << "]";
- }
- ofs << "\n ]\n";
- ofs << " }";
- }
- ofs << "\n ]\n";
- ofs << "}\n";
- }
-
t0.stop();
t0 = timer.start("center line cleaning");
-
- auto triangles = find_triangles(line_graph);
- // For every triangle found in the network we eliminate one edge to break the cycle
- // The edge we eliminate is the edge with the greatest angle with any of it's neighbours
- auto eliminated_segments = eliminate_triangles(line_graph);
+ Graph2D G;
+ if (settings.line_cleaning_algo == 0) {
+ G = join_segment_runs(debug_output, line_graph, midpoint_to_edge_length);
+ Arrangement_2 arr;
+ G.to_arrangement(arr);
+ Graph2D G2;
+ G2.from_arrangement(arr);
+ eliminate_colinear_vertices(G2);
+ G = G2;
+ for (auto it = G.edges_begin(); it != G.edges_end(); ++it) {
+ debug_output.write_segment(it->first, it->second, "network_2");
+ }
+ } else {
+ auto eliminated_segments = eliminate_triangles(line_graph);
- Graph2D G2(line_graph);
- for (auto& e : eliminated_segments) {
- debug_output.write_segment(e.first, e.second, "eliminated");
- G2.remove_edge(e.first, e.second);
- }
+ Graph2D G2(line_graph);
+ for (auto& e : eliminated_segments) {
+ debug_output.write_segment(e.first, e.second, "eliminated");
+ G2.remove_edge(e.first, e.second);
+ }
- auto G = G2.weld_vertices();
+ G = G2.weld_vertices();
- for (auto it = G.edges_begin(); it != G.edges_end(); ++it) {
- debug_output.write_segment(it->first, it->second, "network_2");
- }
+ for (auto it = G.edges_begin(); it != G.edges_end(); ++it) {
+ debug_output.write_segment(it->first, it->second, "network_2");
+ }
- eliminate_colinear_vertices(G);
+ eliminate_colinear_vertices(G);
- edge_slide(G);
+ edge_slide(G);
- for (auto it = G.edges_begin(); it != G.edges_end(); ++it) {
- debug_output.write_segment(it->first, it->second, "network_3");
+ for (auto it = G.edges_begin(); it != G.edges_end(); ++it) {
+ debug_output.write_segment(it->first, it->second, "network_3");
+ }
}
t0.stop();
@@ -2629,7 +3251,7 @@ void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std
fuse_corridor_halves_with_input(arr, G, segment_lookup, input_polygons, debug_output);
}
- if (settings.perform_cleanup) {
+ if (settings.perform_cleanup && settings.line_cleaning_algo != 0) {
remove_colinear_vertices(arr);
double threshold;
clean_noisy_paths(debug_output, arr, segment_lookup, threshold);
diff --git a/src/svgfill/src/graph_2d.h b/src/svgfill/src/graph_2d.h
index da2b5014ec..d19418ff62 100644
--- a/src/svgfill/src/graph_2d.h
+++ b/src/svgfill/src/graph_2d.h
@@ -346,6 +346,13 @@ public:
}
}
+ template
+ void from_arrangement(T& arr) {
+ for (auto it = arr.edges_begin(); it != arr.edges_end(); ++it) {
+ insert(it->source()->point(), it->target()->point());
+ }
+ }
+
void assert_symmetric() {
#ifdef SVGFILL_DEBUG
#if 0
diff --git a/src/svgfill/src/svgfill.h b/src/svgfill/src/svgfill.h
index fab4dd0142..2588636a8d 100644
--- a/src/svgfill/src/svgfill.h
+++ b/src/svgfill/src/svgfill.h
@@ -126,6 +126,9 @@ namespace svgfill {
// 0: outer perimiter and corridor center lines
// 1: input polygons, corridor center lines and segments connecting corridor center lines to input polygons
int topology_reconstruction_algo = 0;
+ // 0: join segment runs
+ // 1: local badness reduction
+ int line_cleaning_algo = 0;
bool perform_cleanup = true;
double subdivision_factor = 16.;
};