diff --git a/choco/bonsai/choco_release.py b/choco/bonsai/choco_release.py index 681e6c58e5..ea53798c5d 100644 --- a/choco/bonsai/choco_release.py +++ b/choco/bonsai/choco_release.py @@ -13,6 +13,7 @@ import hashlib import os import pathlib import re +import subprocess from typing import NoReturn from urllib import request @@ -20,7 +21,7 @@ from github import Github def get_repo_tag_names() -> list[str]: - git_return = os.popen("git tag -l").read() + git_return = subprocess.check_output("git tag -l", text=True) tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name] print(f"{len(tag_names)} tag_names found in repo") return tag_names @@ -78,6 +79,10 @@ def get_release_zip(tag: str) -> tuple[str, str]: raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.") +def run(command: str) -> None: + subprocess.check_output(command) + + start = datetime.datetime.now() URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender" @@ -97,7 +102,7 @@ should_release = False target_release_tag = "" TARGET_OS = "windows-x64" -git_status = os.popen("git status").read() +git_status = subprocess.check_output("git status", text=True) print(git_status) for tag_name in get_repo_tag_names(): @@ -147,7 +152,7 @@ blenderbim_build_version = target_release_tag.replace("blenderbim-", "") # url_blenderbim_py3x_win_zip release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag) -os.popen(f"wget {url_blenderbim_py3x_win_zip} --no-verbose").read() +subprocess.check_call(f"wget {url_blenderbim_py3x_win_zip} --no-verbose") # sha256sum_blenderbim_py310_win_zip sha256sum_blenderbim_py3x_win_zip = get_file_sha256_hash(release_zip_file_name) @@ -201,13 +206,13 @@ print("[INFO] inserting dynamic chocolatey package parameters successful") print("\n_____ build choco.exe with mono") choco_version = "1.1.0" -os.popen(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet").read() -os.popen(f"tar -xzf {choco_version}.tar.gz").read() +run(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet") +run(f"tar -xzf {choco_version}.tar.gz") print("choco tar unpack successful") os.chdir("choco-1.1.0") -os.popen("./build.sh").read() +run("./build.sh") -os.popen("cp -r build_output/chocolatey /opt/chocolatey").read() +run("cp -r build_output/chocolatey /opt/chocolatey") os.chdir(BLENDERBIM_DIR) if pathlib.Path("/opt/chocolatey/choco.exe").exists(): @@ -215,11 +220,15 @@ if pathlib.Path("/opt/chocolatey/choco.exe").exists(): print("\n_____ build choco pack") -os.popen("mono /opt/chocolatey/choco.exe pack --allow-unofficial").read() -os.popen('mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial').read() +run("mono /opt/chocolatey/choco.exe pack --allow-unofficial") +run( + 'mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial' +) print("\n_____ build choco push") -os.popen('mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose').read() +run( + 'mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose' +) print(f"choco push of version: {target_release_tag} successful!") print(f"it took: {datetime.datetime.now() - start}") diff --git a/pyproject.toml b/pyproject.toml index 7234815c08..895e74679e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,17 @@ ignore = [ "UP032", # Replace .format with f-string ] +[tool.ty.rules] +# We only use `ty` currently to check for deprecated methods. +all = "ignore" +deprecated = "error" + +[tool.ty.src] +exclude = [ + "src/ifc2ca/templates", + "src/svgfill/3rdparty", +] + [tool.poe.tasks] ruff-main = "ruff check --extend-exclude nix/build-all.py" @@ -87,6 +98,8 @@ ruff.sequence = ["ruff-main", "ruff-old"] black = "black ." +ty = "ty check" + format.sequence = ["black", "ruff-main", "ruff-old"] cmake-format = "gersemi . --in-place" diff --git a/src/bonsai/Makefile b/src/bonsai/Makefile index 3cbfd8bdbf..f21f1b42ac 100644 --- a/src/bonsai/Makefile +++ b/src/bonsai/Makefile @@ -232,8 +232,7 @@ endif cd build/bonsai/bim/data/brick/ && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl # Required for hipped roof generation - # TODO: Use official repo once https://github.com/prochitecture/bpypolyskel/pull/22 is merged. - cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/Andrej730/bpypolyskel.git@pyproject_toml" --no-deps -w wheels/ + cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/prochitecture/bpypolyskel" --no-deps -w wheels/ # folder for executable files mkdir -p build/bonsai/libs/bin diff --git a/src/bonsai/bonsai/bim/export_ifc.py b/src/bonsai/bonsai/bim/export_ifc.py index 633d6292f9..f28c106e95 100644 --- a/src/bonsai/bonsai/bim/export_ifc.py +++ b/src/bonsai/bonsai/bim/export_ifc.py @@ -72,9 +72,7 @@ class IfcExporter: def set_header(self): self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file) - self.file.header.file_name.time_stamp = ( - datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat() - ) + self.file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat() self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) self.file.header.file_name.originating_system = "{} {}".format( self.get_application_name(), tool.Blender.get_bonsai_version() diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 6019c480b9..57c182c02e 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -27,7 +27,6 @@ import ifcopenshell.api.pset import ifcopenshell.util.element from bpy.props import ( BoolProperty, - BoolVectorProperty, CollectionProperty, EnumProperty, FloatProperty, diff --git a/src/bonsai/bonsai/bim/module/georeference/prop.py b/src/bonsai/bonsai/bim/module/georeference/prop.py index 41035398e9..da92edf4e6 100644 --- a/src/bonsai/bonsai/bim/module/georeference/prop.py +++ b/src/bonsai/bonsai/bim/module/georeference/prop.py @@ -267,6 +267,8 @@ class BIMGeoreferenceProperties(PropertyGroup): x_axis_ordinate: str x_axis_is_null: bool + model_is_georeferenced: bool + model_crs: str model_origin: str model_origin_si: str model_project_north: str diff --git a/src/bonsai/bonsai/bim/module/project/__init__.py b/src/bonsai/bonsai/bim/module/project/__init__.py index 67710db8ba..e83da49c5b 100644 --- a/src/bonsai/bonsai/bim/module/project/__init__.py +++ b/src/bonsai/bonsai/bim/module/project/__init__.py @@ -76,7 +76,7 @@ classes = ( operator.UnlinkIfc, operator.UnloadLink, workspace.ExploreHotkey, - workspace.GenerateUVMap, + operator.GenerateUVMap, prop.LibraryBreadcrumb, prop.LibraryElement, prop.FilterCategory, diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 9d0e2ff8d4..e674cb56f4 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -178,9 +178,18 @@ 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"}) - append_all: bpy.props.BoolProperty(default=False) - use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) + 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] + + if TYPE_CHECKING: + filter_glob: str + append_all: bool + use_relative_path: bool reload_previous_file = False @@ -558,7 +567,11 @@ class AppendEntireLibrary(bpy.types.Operator, tool.Ifc.Operator): 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") + + query: bpy.props.StringProperty(name="Query") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + query: str @classmethod def poll(cls, context): @@ -587,9 +600,16 @@ 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() - prop_index: bpy.props.IntProperty() - assume_unique_by_name: bpy.props.BoolProperty(name="Assume Unique By Name", default=True, options={"SKIP_SAVE"}) + 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] + + if TYPE_CHECKING: + definition: int + prop_index: int + assume_unique_by_name: bool file: ifcopenshell.file @@ -939,24 +959,28 @@ 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"}) - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"}) - is_advanced: bpy.props.BoolProperty( + 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] 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( + use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] 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( + should_start_fresh_session: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] 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( + import_without_ifc_data: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] name="Import Without IFC Data", description=( "Import IFC objects as Blender objects without any IFC metadata and authoring capabilities." @@ -964,9 +988,20 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper): ), default=False, ) - use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"}) + use_detailed_tooltip: bpy.props.BoolProperty( + default=False, options={"HIDDEN"} + ) # pyright: ignore[reportRedeclaration] filename_ext = ".ifc" + if TYPE_CHECKING: + filepath: str + filter_glob: str + is_advanced: bool + use_relative_path: bool + should_start_fresh_session: bool + import_without_ifc_data: bool + use_detailed_tooltip: bool + @classmethod def description(cls, context, properties): tooltip = cls.bl_description @@ -1265,7 +1300,10 @@ 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) + should_select: bpy.props.BoolProperty(name="Should Select", default=True) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + should_select: bool def execute(self, context): props = tool.Project.get_project_props() @@ -1289,6 +1327,14 @@ 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] + name="Query", + description=( + "Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n" + "Default query - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement." + ), + ) + filename_ext = ".ifc" if TYPE_CHECKING: @@ -1298,20 +1344,25 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator): filter_glob: str use_relative_path: bool use_cache: bool + query: str def draw(self, context): + assert self.layout pprops = tool.Project.get_project_props() row = self.layout.row() row.prop(self, "use_relative_path") row = self.layout.row() row.prop(self, "use_cache") row = self.layout.row() - row.prop(pprops, "false_origin_mode") + row.label(text="False Origin Mode:") + row = self.layout.row() + row.prop(pprops, "false_origin_mode", text="") if pprops.false_origin_mode == "MANUAL": row = self.layout.row() row.prop(pprops, "false_origin") row = self.layout.row() row.prop(pprops, "project_north") + self.layout.prop(self, "query", placeholder="IfcElement") def _execute(self, context): start = time.time() @@ -1344,7 +1395,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator): new.ifc_definition_id = reference.id() new.name = filepath new.filepath = filepath - bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache) + bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query) class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator): @@ -1352,7 +1403,11 @@ class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Unlink IFC" bl_options = {"REGISTER", "UNDO"} bl_description = "Remove the selected file from the link list" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def _execute(self, context): props = tool.Project.get_project_props() @@ -1372,7 +1427,11 @@ class UnloadLink(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Unload Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Unload the selected linked file" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def _execute(self, context): link = tool.Project.get_project_props().links[self.link_index] @@ -1397,10 +1456,12 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator): 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] if TYPE_CHECKING: link_index: int use_cache: bool + query: str def _execute(self, context): self.link = tool.Project.get_project_props().links[self.link_index] @@ -1442,8 +1503,20 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator): def link_ifc(self) -> Union[set[str], None]: blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend") h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5") + json_filepath = self.filepath_.with_suffix(".ifc.cache.json") - if not self.use_cache and blend_filepath.exists(): + def should_clear_cache() -> bool: + if not self.use_cache: + return True + if not blend_filepath.exists(): + return False + data = json.loads(json_filepath.read_text()) + # Empty 'query' - model loaded without custom query. + # Missing 'query' - model was loaded before custom queries were introduced in Bonsai. + query = data.get("query", "") + return query != self.query + + if should_clear_cache(): os.remove(blend_filepath) if not blend_filepath.exists(): @@ -1471,7 +1544,7 @@ def run(): pprops.project_north = "{pprops.project_north}" # Use absolute path to be safe from cwd changes. try: - bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}") + bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)}) except RuntimeError as e: # Operator failed (returned CANCELLED with error report) print(f"Failed to load linked project: {{e}}") @@ -1557,7 +1630,11 @@ class ReloadLink(bpy.types.Operator): bl_label = "Reload Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Reload the selected file" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def execute(self, context): bpy.ops.bim.unload_link(link_index=self.link_index) @@ -1569,7 +1646,11 @@ class ToggleLinkSelectability(bpy.types.Operator): bl_label = "Toggle Link Selectability" bl_options = {"REGISTER", "UNDO"} bl_description = "Toggle selectability" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def execute(self, context): props = tool.Project.get_project_props() @@ -1739,7 +1820,11 @@ class SelectLinkHandle(bpy.types.Operator): bl_label = "Select Link Handle" bl_options = {"REGISTER", "UNDO"} bl_description = "Select link empty object handle" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def execute(self, context): props = tool.Project.get_project_props() @@ -1797,11 +1882,28 @@ 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"}) - 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) + 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] + + if TYPE_CHECKING: + filter_glob: str + json_version: str + json_compact: bool + should_save_as: bool + use_relative_path: bool @classmethod def poll(cls, context): @@ -1951,6 +2053,12 @@ 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] + """See ``bim.link_ifc``.""" + + if TYPE_CHECKING: + query: str + file: ifcopenshell.file meshes: dict[str, bpy.types.Mesh] # Material names is derived from diffuse as in 'r-g-b-a'. @@ -2000,14 +2108,17 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): tool.Loader.settings.context_settings = tool.Loader.create_settings() tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True) - self.elements = set(self.file.by_type("IfcElement")) - if self.file.schema in ("IFC2X3", "IFC4"): - self.elements |= set(self.file.by_type("IfcProxy")) - if self.file.schema == "IFC2X3": - self.elements |= set(self.file.by_type("IfcSpatialStructureElement")) + if self.query: + self.elements = ifcopenshell.util.selector.filter_elements(self.file, self.query) else: - self.elements |= set(self.file.by_type("IfcSpatialElement")) - self.elements -= set(self.file.by_type("IfcFeatureElement")) + self.elements = set(self.file.by_type("IfcElement")) + if self.file.schema in ("IFC2X3", "IFC4"): + self.elements |= set(self.file.by_type("IfcProxy")) + if self.file.schema == "IFC2X3": + self.elements |= set(self.file.by_type("IfcSpatialStructureElement")) + else: + self.elements |= set(self.file.by_type("IfcSpatialElement")) + self.elements -= set(self.file.by_type("IfcFeatureElement")) if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin: tool.Loader.set_manual_blender_offset(self.file) @@ -2032,6 +2143,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): "false_origin_mode": pprops.false_origin_mode, "false_origin": pprops.false_origin, "project_north": pprops.project_north, + "query": self.query, } with open(self.json_filepath, "w") as f: json.dump(data, f) @@ -2446,7 +2558,7 @@ class EnableCulling(bpy.types.Operator): self.total_mousemoves = 0 self.cullable_objects = [] - def modal(self, context, event): + def modal(self, context, event) -> set["rna_enums.OperatorReturnItems"]: if not LinksData.enable_culling: for obj in bpy.context.visible_objects: if obj.type == "MESH" and obj.name.startswith("Ifc"): @@ -2477,7 +2589,7 @@ class EnableCulling(bpy.types.Operator): return {"PASS_THROUGH"} - def is_view_changed(self, context): + def is_view_changed(self, context: bpy.types.Context) -> bool: view_matrix = context.region_data.view_matrix projection_matrix = context.region_data.window_matrix vp_matrix = projection_matrix @ view_matrix @@ -2492,7 +2604,7 @@ class EnableCulling(bpy.types.Operator): return True return False - def is_object_in_view(self, obj, context, camera_position): + def is_object_in_view(self, obj: bpy.types.Object, context: bpy.types.Context, camera_position: Vector) -> bool: # Get the view matrix and the projection matrix from the active viewport view_matrix = context.region_data.view_matrix projection_matrix = context.region_data.window_matrix @@ -2519,7 +2631,7 @@ class EnableCulling(bpy.types.Operator): return False return True - def invoke(self, context, event): + def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set["rna_enums.OperatorReturnItems"]: LinksData.enable_culling = True self.cullable_objects = [] for obj in bpy.context.visible_objects: @@ -2806,8 +2918,16 @@ 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"}) - files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"}) + 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] + + if TYPE_CHECKING: + directory: str + files: list[bpy.types.OperatorFileListElement] def invoke(self, context, event): # Keeping code in .invoke() as we'll probably add some @@ -2858,7 +2978,10 @@ class MeasureTool(bpy.types.Operator, PolylineOperator): bl_label = "Measure Tool" bl_options = {"REGISTER", "UNDO"} - measure_type: bpy.props.StringProperty() + measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + measure_type: str @classmethod def poll(cls, context): @@ -2954,7 +3077,10 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator): bl_label = "Measure Face Area Tool" bl_options = {"REGISTER", "UNDO"} - measure_type: bpy.props.StringProperty() + measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + measure_type: str @classmethod def poll(cls, context): @@ -3058,7 +3184,10 @@ class ClearMeasurement(bpy.types.Operator): @classmethod def poll(cls, context): polyline_props = tool.Model.get_polyline_props() - return len(polyline_props.measurement_polyline) > 0 + if len(polyline_props.measurement_polyline) > 0: + return True + cls.poll_message_set("No measurement to clear.") + return False def execute(self, context): polyline_props = tool.Model.get_polyline_props() @@ -3162,7 +3291,7 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator): super().invoke(context, event) return {"RUNNING_MODAL"} - def cancel_tool(self, context): + def cancel_tool(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]: context.workspace.status_text_set(text=None) if hasattr(self, "tool_state"): self.tool_state.plane_method = None @@ -3170,7 +3299,7 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator): tool.Blender.update_viewport() return {"CANCELLED"} - def handle_custom_instructions(self, context): + def handle_custom_instructions(self, context: bpy.types.Context) -> None: if len(self.selected_points) == 0: instruction_text = "Click First Point on Image" elif len(self.selected_points) == 1: @@ -3185,14 +3314,14 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator): context.workspace.status_text_set(text=instruction_text) - def calculate_distance(self): + def calculate_distance(self) -> None: if len(self.selected_points) == 2: point1 = self.selected_points[0] point2 = self.selected_points[1] distance_3d = (point2 - point1).length self.calculated_distance = distance_3d / self.unit_scale - def apply_scaling(self, context): + def apply_scaling(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]: if len(self.selected_points) != 2: self.report({"ERROR"}, "Two points must be selected") return {"CANCELLED"} @@ -3250,7 +3379,10 @@ 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="") + filepath: bpy.props.StringProperty(name="IFC File Path", default="") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + filepath: str def execute(self, context): ifc_file = self.filepath @@ -3284,3 +3416,19 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator): bpy.app.handlers.load_post.append(load_handler) bpy.ops.wm.open_mainfile(filepath=metadata_path) return {"FINISHED"} + + +class GenerateUVMap(bpy.types.Operator): + bl_idname = "bim.generate_uv_map" + bl_label = "Generate UV Map" + bl_description = "Generate UV map for selected mesh." + bl_options = {"REGISTER", "UNDO", "INTERNAL"} + + def execute(self, context): + obj = context.active_object + if not obj or not isinstance(obj.data, bpy.types.Mesh): + self.report({"ERROR"}, "No valid mesh selected.") + return {"CANCELLED"} + tool.Loader.load_generated_uv_map(obj.data) + self.report({"INFO"}, "Generated UV map for selected mesh.") + return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py index 98122100d2..dfe8e44987 100644 --- a/src/bonsai/bonsai/bim/module/project/ui.py +++ b/src/bonsai/bonsai/bim/module/project/ui.py @@ -496,7 +496,7 @@ class BIM_PT_links(Panel): row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index else: row.operator("bim.load_link", text="", icon="LINKED").link_index = index - row.operator("bim.unlink_ifc", text="", icon="X").link_index = index + row.operator("bim.unlink_ifc", text="", icon="X").link_index = index self.layout.template_list("BIM_UL_links", "", self.props, "links", self.props, "active_link_index") if LinksData.enable_culling: diff --git a/src/bonsai/bonsai/bim/module/project/workspace.py b/src/bonsai/bonsai/bim/module/project/workspace.py index 89467cbbee..bd60f4975a 100644 --- a/src/bonsai/bonsai/bim/module/project/workspace.py +++ b/src/bonsai/bonsai/bim/module/project/workspace.py @@ -71,24 +71,26 @@ class ExploreTool(bpy.types.WorkSpaceTool): row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_M") - row = layout.row(align=True) op = row.operator("bim.explore_hotkey", text="Measure Tool", icon="CON_DISTLIMIT") op.hotkey = "S_M" row = layout.row(align=True) row.prop(prop, "measurement_type", text="Measure Type", expand=True, icon_only=True, emboss=True) - row = layout.row(align=True) op = row.operator("bim.clear_measurement", text="", icon="X") row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_S") - row = layout.row(align=True) op = row.operator("bim.explore_hotkey", text="Image Scaling Tool", icon="IMAGE_PLANE") op.hotkey = "S_S" - op.description = "Scale Image Annotation. Allows to scale an IfcReferenceImage. Select image, select tool. Check lower left corner instructions to select two points and provide real distance between them" + op.description = ( + "Scale Image Annotation.\n\n" + "Allows to scale an IfcReferenceImage.\n\n" + "Select image, select tool. " + "Check lower left corner instructions to select two points and provide real distance between them" + ) - op = row.operator("bim.generate_uv_map", text="Generate UV Map", icon="UV") - op.description = "Generate UV map for selected mesh." + row = layout.row(align=True) + row.operator("bim.generate_uv_map", icon="UV") class ExploreHotkey(bpy.types.Operator): @@ -156,20 +158,3 @@ class ExploreHotkey(bpy.types.Operator): def hotkey_A_H(self) -> None: bpy.ops.bim.hide_queried_linked_element(unhide_all=True) - - -class GenerateUVMap(bpy.types.Operator): - bl_idname = "bim.generate_uv_map" - bl_label = "Generate UV Map" - bl_options = {"REGISTER", "UNDO", "INTERNAL"} - - description: bpy.props.StringProperty() - - def execute(self, context): - obj = context.active_object - if not obj or not hasattr(obj, "data") or not hasattr(obj.data, "polygons"): - self.report({"ERROR"}, "No valid mesh selected.") - return {"CANCELLED"} - tool.Loader.load_generated_uv_map(obj.data) - self.report({"INFO"}, "Generated UV map for selected mesh.") - return {"FINISHED"} \ No newline at end of file diff --git a/src/bonsai/test/tool/test_project.py b/src/bonsai/test/tool/test_project.py index ae0a187080..e9e11f4656 100644 --- a/src/bonsai/test/tool/test_project.py +++ b/src/bonsai/test/tool/test_project.py @@ -365,7 +365,7 @@ class TestLoadingIfcSqlite(NewFile): sql_type="SQLite", ) patcher.patch() - tmp_file = Path(tempfile.mktemp(suffix=".ifcsqlite")) + tmp_file = Path(tempfile.mkstemp(suffix=".ifcsqlite")[1]) ifcpatch.write(patcher.get_output(), tmp_file) elements_with_meshes = [ diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py b/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py index e122ba766b..a28f6dfa46 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py @@ -53,9 +53,7 @@ def create_file(version: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> ifcope """ file = ifcopenshell.file(schema=version) file.header.file_name.name = "/dev/null" # Hehehe - file.header.file_name.time_stamp = ( - datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat() - ) + file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat() file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) file.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version) file.header.file_name.authorization = "Nobody" diff --git a/src/ifcpatch/ifcpatch/recipes/PurgeData.py b/src/ifcpatch/ifcpatch/recipes/PurgeData.py index 3c34927a63..0ea968f48a 100644 --- a/src/ifcpatch/ifcpatch/recipes/PurgeData.py +++ b/src/ifcpatch/ifcpatch/recipes/PurgeData.py @@ -57,13 +57,7 @@ class Patcher: def patch(self): self.file.header.file_name.name = "Rabbit" - self.file.header.file_name.time_stamp = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .astimezone() - .replace(microsecond=0) - .isoformat() - ) + self.file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat() self.file.header.file_name.preprocessor_version = "Rabbit" self.file.header.file_name.originating_system = "Rabbit" diff --git a/src/ifcpatch/test/test_ifcpatch.py b/src/ifcpatch/test/test_ifcpatch.py index 4d7e98e55a..d0976965b6 100644 --- a/src/ifcpatch/test/test_ifcpatch.py +++ b/src/ifcpatch/test/test_ifcpatch.py @@ -52,10 +52,10 @@ class Test: assert output.by_type("IfcProject")[0].GlobalId == project.GlobalId assert output.by_type("IfcWall")[0].GlobalId == wall.GlobalId - output_path = Path(tempfile.mktemp()) + output_path = Path(tempfile.mkstemp()[1]) try: - assert not output_path.exists() + assert output_path.stat().st_size == 0 ifcpatch.write(patcher.get_output(), output_path) - assert output_path.exists() + assert output_path.stat().st_size != 0 finally: output_path.unlink() diff --git a/src/opencdeserver/api/app/security/secure.py b/src/opencdeserver/api/app/security/secure.py index fec837b868..e332c94647 100644 --- a/src/opencdeserver/api/app/security/secure.py +++ b/src/opencdeserver/api/app/security/secure.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from database.neo4j import db from fastapi import Depends, HTTPException, Security, status @@ -33,10 +33,8 @@ credentials_exception = HTTPException( def create_access_token(data: dict, expires_delta: timedelta | None = None): payload = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) + expires_delta = expires_delta or timedelta(minutes=15) + expire = datetime.now(timezone.utc) + expires_delta payload.update({"expires": str(expire)}) encoded_jwt = jwt.encode(payload, secrets["security_secret_key"], algorithm=os.environ["SECURITY_ALGORITHM"]) return encoded_jwt