Merge branch 'v0.8.0' into ifctester-improvements

This commit is contained in:
Sayan Jyoti Das
2026-03-17 19:49:09 +05:30
15 changed files with 253 additions and 110 deletions
+19 -10
View File
@@ -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}")
+13
View File
@@ -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"
+1 -2
View File
@@ -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
+1 -3
View File
@@ -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()
@@ -27,7 +27,6 @@ import ifcopenshell.api.pset
import ifcopenshell.util.element
from bpy.props import (
BoolProperty,
BoolVectorProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
@@ -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
@@ -76,7 +76,7 @@ classes = (
operator.UnlinkIfc,
operator.UnloadLink,
workspace.ExploreHotkey,
workspace.GenerateUVMap,
operator.GenerateUVMap,
prop.LibraryBreadcrumb,
prop.LibraryElement,
prop.FilterCategory,
+198 -50
View File
@@ -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"}
+1 -1
View File
@@ -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:
@@ -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"}
+1 -1
View File
@@ -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 = [
@@ -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"
+1 -7
View File
@@ -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"
+3 -3
View File
@@ -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()
+3 -5
View File
@@ -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