mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Merge branch 'v0.8.0' into ifcmax/initial-refresh
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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("")
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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()
|
||||
+39
-1
@@ -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},
|
||||
)
|
||||
|
||||
+6
-4
@@ -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"},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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="-",
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) "
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@ Python code formatters
|
||||
For Python code formatting, we use `Black code formatter <https://pypi.org/project/black/>`__,
|
||||
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:
|
||||
|
||||
|
||||
@@ -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``
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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"}},
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()):
|
||||
|
||||
@@ -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+."),
|
||||
|
||||
Generated
+4
-4
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<size_t, size_t>(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<Polygon_2>& input_polygons, SegmentLookup& se
|
||||
return {line_graph, midpoint_to_segment, segment_to_input_facet, midpoint_to_edge_length};
|
||||
}
|
||||
|
||||
using DPoint = CGAL::Simple_cartesian<double>::Point_2;
|
||||
using DDir = CGAL::Simple_cartesian<double>::Vector_2;
|
||||
using DBox = std::array<DPoint, 2>;
|
||||
|
||||
struct CenterLineGraphData {
|
||||
std::vector<Point_2> points;
|
||||
std::vector<DPoint> points_double;
|
||||
std::vector<double> widths;
|
||||
std::vector<std::pair<size_t, size_t>> edges;
|
||||
std::vector<std::vector<size_t>> 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<DPoint, 4> corners;
|
||||
DBox bbox;
|
||||
};
|
||||
|
||||
struct MergedBoxRecord {
|
||||
DPoint start;
|
||||
DPoint end;
|
||||
DDir direction;
|
||||
DDir normal;
|
||||
double avg_width;
|
||||
double length;
|
||||
size_t member_count;
|
||||
std::vector<size_t> members;
|
||||
std::array<DPoint, 4> corners;
|
||||
DBox bbox;
|
||||
Point_2 exact_start;
|
||||
Point_2 exact_end;
|
||||
};
|
||||
|
||||
struct BoxCluster {
|
||||
std::vector<size_t> 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<DPoint, 4> 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<DPoint, 4>& 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<Point_2, std::vector<Point_2>>& line_graph,
|
||||
const std::map<Point_2, double>& midpoint_to_edge_length)
|
||||
{
|
||||
CenterLineGraphData graph;
|
||||
std::map<Point_2, size_t> 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<std::pair<size_t, size_t>> 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<size_t, size_t>& 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<LineRun> runs_from_graph(const CenterLineGraphData& graph, double angle_tol_deg = 3., double line_dist_tol = 0.15) {
|
||||
std::vector<bool> visited(graph.edges.size(), false);
|
||||
std::vector<LineRun> 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<size_t> queue = {seed_ei};
|
||||
std::set<size_t> 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<size_t, 2> 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<size_t> 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<double>::infinity();
|
||||
double max_t = -std::numeric_limits<double>::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<RunBoxRecord> build_run_box_records(const std::vector<LineRun>& runs) {
|
||||
std::vector<RunBoxRecord> 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 <typename T>
|
||||
std::pair<double, double> 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<double, double>& a, const std::pair<double, double>& b) {
|
||||
return std::max(0., std::min(a.second, b.second) - std::max(a.first, b.first));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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<size_t>& member_indices, const std::vector<RunBoxRecord>& 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<double>::infinity();
|
||||
double tmax = -std::numeric_limits<double>::infinity();
|
||||
double smin = std::numeric_limits<double>::infinity();
|
||||
double smax = -std::numeric_limits<double>::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<double, double> 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<MergedBoxRecord> merge_intersecting_parallel_boxes_iterative(const std::vector<LineRun>& runs) {
|
||||
auto records = build_run_box_records(runs);
|
||||
std::vector<BoxCluster> 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<std::pair<size_t, size_t>> best_pair;
|
||||
std::pair<double, double> 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<size_t> 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<BoxCluster> 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<MergedBoxRecord> 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<Point_2> 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<K>(a.exact_start, a.exact_end), CGAL::Line_2<K>(b.exact_start, b.exact_end));
|
||||
if (!x) {
|
||||
return boost::none;
|
||||
}
|
||||
if (auto* xp = variant_get<Point_2>(&*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<Point_2, std::vector<Point_2>> snap_points_to_box_axes(
|
||||
const CenterLineGraphData& graph,
|
||||
const std::vector<MergedBoxRecord>& boxes)
|
||||
{
|
||||
std::vector<Point_2> 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<SnapCandidate> 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<SnapCandidate> 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<Point_2, std::set<Point_2>> 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<Point_2, std::vector<Point_2>> snapped_graph;
|
||||
for (auto& p : adjacency) {
|
||||
snapped_graph[p.first] = {p.second.begin(), p.second.end()};
|
||||
}
|
||||
return snapped_graph;
|
||||
}
|
||||
|
||||
Graph2D<K> join_segment_runs(
|
||||
DebugWriter& debug,
|
||||
const std::map<Point_2, std::vector<Point_2>>& line_graph,
|
||||
const std::map<Point_2, double>& 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<Polygon_2> run_polygons;
|
||||
for (auto& r : runs) {
|
||||
auto ps = rectangle_corners(r.start, r.end, r.avg_width);
|
||||
std::array<Point_2, 4> 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<Point_2, 4> 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<K>(snapped_graph);
|
||||
}
|
||||
|
||||
std::set<Triangle<K>> find_triangles(const std::map<Point_2, std::vector<Point_2>>& line_graph) {
|
||||
// Find triangles in this network often occuring at junctions in the corridor mesh
|
||||
std::set<Triangle<K>> triangles;
|
||||
@@ -1249,8 +1919,6 @@ std::list<std::pair<Point_2, Point_2>> extend_end_vertices_based_on_input(
|
||||
// create ray incoming -> M
|
||||
CGAL::Ray_2<K> 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<CGAL::Segment_2<K>> closest_segment;
|
||||
boost::optional<CGAL::Point_2<K>> closest_intersection_point;
|
||||
@@ -1261,7 +1929,6 @@ std::list<std::pair<Point_2, Point_2>> extend_end_vertices_based_on_input(
|
||||
if (x) {
|
||||
if (auto* xp = variant_get<CGAL::Point_2<K>>(&*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<std::pair<Point_2, Point_2>> 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<Segment_2, double, Segment_2_less> 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<double> 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<Segment_2, Segment_2_less> 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<std::pair<Point_2, Point_2>> to_remove;
|
||||
std::vector<std::pair<Point_2, Point_2>> 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<double> 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<std::vector<Point_2>> 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<K> r1((*a)->point(), (*b)->point());
|
||||
CGAL::Ray_2<K> 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<CGAL::Point_2<K>>(&*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<CGAL::Point_2<K>>(&*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<K> 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<K> 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<K> 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<K> 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);
|
||||
|
||||
@@ -346,6 +346,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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
|
||||
|
||||
@@ -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.;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user