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.; };