From 88ba6514e82b1536db8a44e852c0d15a1b8f5ae2 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Fri, 19 May 2023 00:27:37 +0100 Subject: [PATCH 01/65] Git remote fetch and push operations (#3096) --- .../blenderbim/bim/module/ifcgit/__init__.py | 2 ++ .../blenderbim/bim/module/ifcgit/data.py | 21 ++++++++++++ .../blenderbim/bim/module/ifcgit/operator.py | 32 ++++++++++++++++++ .../blenderbim/bim/module/ifcgit/prop.py | 17 ++++++++++ .../blenderbim/bim/module/ifcgit/ui.py | 16 +++++++-- src/blenderbim/docs/users/git_support.rst | 33 +++++++++++++++++++ 6 files changed, 118 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py b/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py index eba6e3af33..5fde698280 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py @@ -28,7 +28,9 @@ classes = ( operator.DiscardUncommitted, operator.DisplayRevision, operator.DisplayUncommitted, + operator.Fetch, operator.Merge, + operator.Push, operator.RefreshGit, operator.SwitchRevision, prop.IfcGitTag, diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/data.py b/src/blenderbim/blenderbim/bim/module/ifcgit/data.py index d9d99fefd3..4373aa8df5 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/data.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/data.py @@ -19,7 +19,10 @@ class IfcGitData: def load(cls): cls.data = { "repo": cls.repo(), + "remotes": cls.remotes(), "branch_names": cls.branch_names(), + "remote_names": cls.remote_names(), + "remote_urls": cls.remote_urls(), "path_ifc": cls.path_ifc(), "branches_by_hexsha": cls.branches_by_hexsha(), "tags_by_hexsha": cls.tags_by_hexsha(), @@ -42,10 +45,28 @@ class IfcGitData: return tool.IfcGit.repo_from_path(path_ifc) return None + @classmethod + def remotes(cls): + if cls.repo(): + return cls.repo().remotes + return None + @classmethod def branch_names(cls): return [] + @classmethod + def remote_names(cls): + return [] + + @classmethod + def remote_urls(cls): + result = {} + if cls.repo(): + for remote in cls.repo().remotes: + result[remote.name] = remote.url + return result + @classmethod def path_ifc(cls): path_ifc = tool.Ifc.get_path() diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py b/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py index 1df3063755..38dfe2c681 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py @@ -246,3 +246,35 @@ class Merge(bpy.types.Operator): return {"FINISHED"} else: return {"CANCELLED"} + + +class Push(bpy.types.Operator): + """Pushes the working branch to selected remote""" + + bl_label = "Push working branch" + bl_idname = "ifcgit.push" + bl_options = {"REGISTER"} + + def execute(self, context): + + props = context.scene.IfcGitProperties + repo = IfcGitData.data["repo"] + remote = repo.remotes[props.select_remote] + remote.push() + return {"FINISHED"} + + +class Fetch(bpy.types.Operator): + """Fetches from the selected remote""" + + bl_label = "Fetch from remote" + bl_idname = "ifcgit.fetch" + bl_options = {"REGISTER"} + + def execute(self, context): + + props = context.scene.IfcGitProperties + repo = IfcGitData.data["repo"] + remote = repo.remotes[props.select_remote] + remote.fetch() + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py b/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py index 88fcd68aaa..7a1655be24 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py @@ -21,9 +21,25 @@ def git_branches(self, context): IfcGitData.data["branch_names"].remove("main") IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"] + if IfcGitData.data["remotes"]: + props = context.scene.IfcGitProperties + IfcGitData.data["branch_names"] += [r.name for r in IfcGitData.data["remotes"][props.select_remote].refs] + return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]] +def git_remotes(self, context): + """remotes enum""" + + IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]]) + + if "origin" in IfcGitData.data["remote_names"]: + IfcGitData.data["remote_names"].remove("origin") + IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"] + + return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]] + + def update_revlist(self, context): """wrapper to trigger update of the revision list""" @@ -109,6 +125,7 @@ class IfcGitProperties(PropertyGroup): subtype="DIR_PATH", ) display_branch: EnumProperty(items=git_branches, update=update_revlist) + select_remote: EnumProperty(items=git_remotes) ifcgit_filter: EnumProperty( items=[ ("all", "All", "All revisions"), diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py index f9b67cfc7c..e9a8ba5dd8 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py @@ -154,13 +154,23 @@ class IFCGIT_PT_panel(bpy.types.Panel): # TODO # item.operator("ifcgit.delete_tag", icon="PANEL_CLOSE") - row = layout.row() + box = layout.box() + row = box.row() row.prop(props, "new_tag_name") - row = layout.row() + row = box.row() row.prop(props, "new_tag_message") - row = layout.row() + row = box.row() row.operator("ifcgit.add_tag", icon="GREASEPENCIL") + if IfcGitData.data["remotes"]: + row = layout.row() + row.prop(props, "select_remote", text="Select remote") + urls = IfcGitData.data["remote_urls"] + row.label(text=urls[props.select_remote]) + row = layout.row() + row.operator("ifcgit.push", icon="EXPERIMENTAL") + row.operator("ifcgit.fetch", icon="IMPORT") + class COMMIT_UL_List(bpy.types.UIList): """List of Git commits""" diff --git a/src/blenderbim/docs/users/git_support.rst b/src/blenderbim/docs/users/git_support.rst index 0379750d4f..5ddc8ef235 100644 --- a/src/blenderbim/docs/users/git_support.rst +++ b/src/blenderbim/docs/users/git_support.rst @@ -156,6 +156,39 @@ optional message text. Similar to commit messages, tag messages should be 50 characters or less, though there is no practical limit. +Remote operations +----------------- + +Git is a *distributed revision control system*, your local repository can be a +version of a remote repository and vice-versa. This is conceptually similar to +local branching except this remote repository could belong to someone else or +could be hosted by an online Git-forge service. + +Your repository can have multiple remote repositories registered, each +can have potentially multiple branches. + +BlenderBIM allows you to make a local *clone* of a remote repository. You will +need to provide a URL *origin* to fetch, and an empty local folder to become +the local repository. + +The *Fetch* operator retrieves new data from the remote repository. This isn't +automatically merged, each branch fetched from the remote repository appears as +a branch that can be browsed, switched-to or merged just like a local branch. +These remote branches have prefixed names, eg. `origin/main`. + +Once you have committed changes to your local repository, the *Push* operator +tries to update the remote branch using changes from the selected local branch. + +.. Warning:: + + Remote repositories can be accessed in multiple ways; ssh, ftp or https + protocols, for example, can require authentication. This authentication may + expect you to generate and upload ssh keys, store API tokens, save + username/password pairs, or use some other form of credential. + BlenderBIM can't configure these credentials for you, follow the + configuration instructions provided by your online service before trying + actions that require authentication. + Using other Git tools --------------------- From d98054234c69b09c5d1f87a9ee16772355d3b919 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 10:51:27 +0500 Subject: [PATCH 02/65] Update drawing styles on changing Underlay checkbox #3172 --- src/blenderbim/blenderbim/bim/module/drawing/operator.py | 3 ++- src/blenderbim/blenderbim/bim/module/drawing/prop.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 05e3297276..42e637651f 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1606,7 +1606,8 @@ class ActivateDrawingStyle(bpy.types.Operator, Operator): active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index if active_drawing_style_index >= len(scene.DocProperties.drawing_styles): - return + self.report({"ERROR"}, "Could not find active drawing style") + return {"CANCELLED"} self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index] diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 71e9ec8c9b..91e2382425 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -178,6 +178,9 @@ def update_schedule_name(self, context): def update_has_underlay(self, context): update_layer(self, context, "HasUnderlay", self.has_underlay) + if self.has_underlay: + bpy.ops.bim.reload_drawing_styles() + bpy.ops.bim.activate_drawing_style() def update_has_linework(self, context): From 32fc147a7cbb7d055c7f4372ee8bad56f968f557 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 11:48:35 +0500 Subject: [PATCH 03/65] Fixed error on rebuilding sheet with underlays Also added description and more clear name for `bim.open_sheet` Error was: ``` Error: Python: Traceback (most recent call last): File "\addons\blenderbim\bim\module\drawing\operator.py", line 82, in execute IfcStore.execute_ifc_operator(self, context) File "\addons\blenderbim\bim\ifc.py", line 410, in execute_ifc_operator result = getattr(operator, "_execute")(context) File "\addons\blenderbim\bim\module\drawing\operator.py", line 1177, in _execute del raster_references[reference.Location] TypeError: list indices must be integers or slices, not str ``` --- src/blenderbim/blenderbim/bim/module/drawing/operator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 42e637651f..78717e6c41 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1055,7 +1055,7 @@ class AddSheet(bpy.types.Operator, Operator): class OpenSheet(bpy.types.Operator, Operator): bl_idname = "bim.open_sheet" - bl_label = "Open Sheet" + bl_label = "Open Sheet Layout" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): @@ -1138,6 +1138,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, Operator): class CreateSheets(bpy.types.Operator, Operator): bl_idname = "bim.create_sheets" bl_label = "Create Sheets" + bl_description = "Build a sheet from the sheet layout" bl_options = {"REGISTER", "UNDO"} @classmethod @@ -1174,7 +1175,7 @@ class CreateSheets(bpy.types.Operator, Operator): has_sheet_reference = True elif reference.Description == "RASTER": if reference.Location in raster_references: - del raster_references[reference.Location] + raster_references.remove(reference.Location) else: tool.Ifc.run("document.remove_reference", reference=reference) From d1d6b00f92ba93817ea7ea68197a0a8c4c7cecaf Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 12:00:00 +0500 Subject: [PATCH 04/65] Test that drawing styles are loaded --- src/blenderbim/test/tool/test_drawing.py | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/blenderbim/test/tool/test_drawing.py b/src/blenderbim/test/tool/test_drawing.py index 5aa2a96860..1c0c38526d 100644 --- a/src/blenderbim/test/tool/test_drawing.py +++ b/src/blenderbim/test/tool/test_drawing.py @@ -753,3 +753,30 @@ class TestUpdateTextValue(NewFile): # test first literal value is unchanged assert props.literals[0].attributes["Literal"].string_value == "Literal" assert ifc.by_type("IfcTextLiteralWithExtent")[0].Literal == "Literal" + + +class TestDrawingStyles(NewFile): + def setup_project_with_drawing(self): + bpy.ops.bim.create_project() + bpy.ops.bim.load_drawings() + bpy.ops.bim.add_drawing() + ifc = tool.Ifc.get() + drawing = ifc.by_type("IfcAnnotation")[0] + bpy.ops.bim.activate_drawing(drawing=drawing.id()) + self.drawing_styles = bpy.context.scene.DocProperties.drawing_styles + + def test_drawing_styles_not_loaded_if_underlay_is_inactive(self): + self.setup_project_with_drawing() + assert len(self.drawing_styles) == 0 + + def test_drawing_styles_loaded_on_underlay_enabled(self): + self.setup_project_with_drawing() + bpy.context.scene.camera.data.BIMCameraProperties.has_underlay = True + assert len(self.drawing_styles) == 3 + + def test_drawing_styles_reload(self): + self.setup_project_with_drawing() + bpy.ops.bim.reload_drawing_styles() + assert len(self.drawing_styles) == 3 + + \ No newline at end of file From db18a81035441248b38dfd0ac1d03b03f845585a Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 12:33:56 +0500 Subject: [PATCH 05/65] Added default Model/Annotation/PLAN_VIEW to support e68fdb2db --- src/blenderbim/blenderbim/core/project.py | 3 + src/blenderbim/test/core/test_project.py | 104 +++++----------------- 2 files changed, 23 insertions(+), 84 deletions(-) diff --git a/src/blenderbim/blenderbim/core/project.py b/src/blenderbim/blenderbim/core/project.py index fc5ccd1d66..920cd5716f 100644 --- a/src/blenderbim/blenderbim/core/project.py +++ b/src/blenderbim/blenderbim/core/project.py @@ -56,6 +56,9 @@ def create_project(ifc, project, schema=None, template=None): project.run_context_add_context( context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent=model ) + project.run_context_add_context( + context_type="Model", context_identifier="Annotation", target_view="PLAN_VIEW", parent=model + ) project.run_context_add_context( context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent=model ) diff --git a/src/blenderbim/test/core/test_project.py b/src/blenderbim/test/core/test_project.py index b062bcec4e..199bd455cd 100644 --- a/src/blenderbim/test/core/test_project.py +++ b/src/blenderbim/test/core/test_project.py @@ -26,18 +26,7 @@ class TestCreateProject: ifc.get().should_be_called().will_return("ifc") subject.create_project(ifc, project, schema="IFC4", template=None) - def test_create_an_ifc4_project(self, ifc, project): - ifc.get().should_be_called().will_return(None) - ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc") - ifc.set("ifc").should_be_called() - - project.create_empty("My Project").should_be_called().will_return("project") - project.create_empty("My Site").should_be_called().will_return("site") - project.create_empty("My Building").should_be_called().will_return("building") - project.create_empty("My Storey").should_be_called().will_return("storey") - project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called() - project.run_unit_assign_scene_units().should_be_called() - + def check_contexts(self, project): project.run_context_add_context( context_type="Model", context_identifier="", target_view="", parent=0 ).should_be_called().will_return("model") @@ -59,6 +48,9 @@ class TestCreateProject: project.run_context_add_context( context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent="model" ).should_be_called() + project.run_context_add_context( + context_type="Model", context_identifier="Annotation", target_view="PLAN_VIEW", parent="model" + ).should_be_called() project.run_context_add_context( context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent="model" ).should_be_called() @@ -75,6 +67,20 @@ class TestCreateProject: context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan" ).should_be_called() + def test_create_an_ifc4_project(self, ifc, project): + ifc.get().should_be_called().will_return(None) + ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc") + ifc.set("ifc").should_be_called() + + project.create_empty("My Project").should_be_called().will_return("project") + project.create_empty("My Site").should_be_called().will_return("site") + project.create_empty("My Building").should_be_called().will_return("building") + project.create_empty("My Storey").should_be_called().will_return("storey") + project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called() + project.run_unit_assign_scene_units().should_be_called() + + self.check_contexts(project) + project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called() project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called() project.run_root_assign_class(obj="storey", ifc_class="IfcBuildingStorey", context="body").should_be_called() @@ -104,42 +110,7 @@ class TestCreateProject: project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called() project.run_unit_assign_scene_units().should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="", target_view="", parent=0 - ).should_be_called().will_return("model") - project.run_context_add_context( - context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent="model" - ).should_be_called().will_return("body") - project.run_context_add_context( - context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Annotation", target_view="SECTION_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Plan", context_identifier="", target_view="", parent=0 - ).should_be_called().will_return("plan") - project.run_context_add_context( - context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent="plan" - ).should_be_called() - project.run_context_add_context( - context_type="Plan", context_identifier="Body", target_view="PLAN_VIEW", parent="plan" - ).should_be_called() - project.run_context_add_context( - context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan" - ).should_be_called() + self.check_contexts(project) project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called() project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called() @@ -179,42 +150,7 @@ class TestCreateProject: project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called() project.run_unit_assign_scene_units().should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="", target_view="", parent=0 - ).should_be_called().will_return("model") - project.run_context_add_context( - context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent="model" - ).should_be_called().will_return("body") - project.run_context_add_context( - context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Annotation", target_view="SECTION_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent="model" - ).should_be_called() - project.run_context_add_context( - context_type="Plan", context_identifier="", target_view="", parent=0 - ).should_be_called().will_return("plan") - project.run_context_add_context( - context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent="plan" - ).should_be_called() - project.run_context_add_context( - context_type="Plan", context_identifier="Body", target_view="PLAN_VIEW", parent="plan" - ).should_be_called() - project.run_context_add_context( - context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan" - ).should_be_called() + self.check_contexts(project) project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called() project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called() From 9c367d1702d38e778698dd09166b7941e68bdb24 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 15:27:58 +0500 Subject: [PATCH 06/65] fixed neg values in format_distance & section/plan level #3175 --- .../blenderbim/bim/module/drawing/helper.py | 5 ++-- .../bim/module/drawing/svgwriter.py | 23 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py index dfc2df727c..edc6ce4a60 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py @@ -154,13 +154,14 @@ def format_distance( # Separate ft and inches # Unless Inches are the specified Length Unit if unit_length != "INCHES": - feet = math.floor(decInches / inPerFoot) + feet = int(decInches / inPerFoot) # remove decimal decInches -= feet * inPerFoot else: feet = 0 # Separate Fractional Inches - inches = math.floor(decInches) + decInches = abs(decInches) # ignore the sign for inches + inches = math.floor(decInches) # remove decimal if inches != 0: frac = round(base * (decInches - inches)) else: diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index fdb8ce1277..bcbf5dc8f7 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -225,17 +225,24 @@ class SvgWriter: ) ) # TODO: allow metric to be configurable - rl = (matrix_world @ points[0].co.xyz).z + rl_value = (matrix_world @ points[0].co.xyz).z if bpy.context.scene.unit_settings.system == "IMPERIAL": - rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places) + rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places) else: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - rl /= unit_scale + rl = rl_value / unit_scale rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl) rl *= unit_scale rl = "{:.3f}m".format(rl) text_style = SvgWriter.get_box_alignment_parameters("bottom-left") - self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style)) + self.svg.add( + self.svg.text( + "RL {}{}".format("" if rl_value < 0 else "+", rl), + insert=tuple(text_position), + class_="SECTIONLEVEL", + **text_style, + ) + ) if tag: self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style)) @@ -787,12 +794,12 @@ class SvgWriter: ) ) # TODO: allow metric to be configurable - rl = (matrix_world @ points[0].co).z + rl_value = (matrix_world @ points[0].co).z if bpy.context.scene.unit_settings.system == "IMPERIAL": - rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places) + rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places) else: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - rl /= unit_scale + rl = rl_value / unit_scale rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl) rl *= unit_scale rl = "{:.3f}m".format(rl) @@ -801,7 +808,7 @@ class SvgWriter: text_style = SvgWriter.get_box_alignment_parameters(box_alignment) self.svg.add( self.svg.text( - "RL +{}".format(rl), + "RL {}{}".format("" if rl_value < 0 else "+", rl), insert=tuple(text_position), class_="PLANLEVEL", **text_style, From 53e58848f37c57cdaade34e8d47e25f62541596e Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 15:40:58 +0500 Subject: [PATCH 07/65] Reset active drawing id if we removed the active drawing Previously there was an error if you remove all drawings: ``` Traceback (most recent call last): File "\addons\blenderbim\bim\module\drawing\ui.py", line 182, in draw DrawingsData.load() File "\addons\blenderbim\bim\module\drawing\data.py", line 106, in load "active_drawing_pset_data": cls.active_drawing_pset_data(), File "\addons\blenderbim\bim\module\drawing\data.py", line 134, in active_drawing_pset_data drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id) File "\addons\blenderbim\libs\site\packages\ifcopenshell\file.py", line 326, in by_id return self[id] File "\addons\blenderbim\libs\site\packages\ifcopenshell\file.py", line 314, in __getitem__ return entity_instance(self.wrapped_data.by_id(key), self) File "\addons\blenderbim\libs\site\packages\ifcopenshell\ifcopenshell_wrapper.py", line 4517, in by_id return _ifcopenshell_wrapper.file_by_id(self, id) RuntimeError: Instance #1 077 not found ``` --- src/blenderbim/blenderbim/bim/module/drawing/operator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 78717e6c41..734e5f2b01 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1410,10 +1410,14 @@ class RemoveDrawing(bpy.types.Operator, Operator): drawings = [tool.Ifc.get().by_id(self.drawing)] print("Removing drawings: {}".format([d for d in drawings])) + removed_drawings = [drawing.id() for drawing in drawings] for drawing in drawings: core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing) + active_drawing_id = context.scene.DocProperties.active_drawing_id + if active_drawing_id in removed_drawings: + context.scene.DocProperties.active_drawing_id = 0 class ReloadDrawingStyles(bpy.types.Operator): bl_idname = "bim.reload_drawing_styles" From 2198201e0b097a8539dd519d4c16894538a48743 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 19 May 2023 17:28:58 +0500 Subject: [PATCH 08/65] Detect BBIM railings at import to import as meshes not curves #3144 Sometimes railing could consist just of one IFCSWEPTDISKSOLID then it will be considered native swept disk solid and imported as a curve. But when user create BBIM railing they edit it as a mesh and curve might look a bit different. Therefore we detect it now at import to make sure it's going to be reprsented the same way as it was saved. --- src/blenderbim/blenderbim/bim/import_ifc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 77f75dc17b..1399f001a0 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -381,7 +381,7 @@ class IfcImporter: representations = self.get_transformed_body_representations(element.Representation.Representations) # Single swept disk solids (e.g. rebar) are better natively represented as beveled curves - if self.is_native_swept_disk_solid(representations): + if self.is_native_swept_disk_solid(element, representations): self.native_data[element.GlobalId] = { "representations": representations, "representation": self.get_body_representation(element.Representation.Representations), @@ -409,7 +409,11 @@ class IfcImporter: } return True - def is_native_swept_disk_solid(self, representations): + def is_native_swept_disk_solid(self, element, representations): + # detect BBIM Railings to represent them with meshes and not curves + if tool.Pset.get_element_pset(element, "BBIM_Railing"): + return False + for representation in representations: items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :( if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"): From b8990dd0a499aca1c55b781e5a84ecc3f69d9658 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Sat, 20 May 2023 09:25:33 +0100 Subject: [PATCH 09/65] Fix merging of remote branches (#3096) You can now 'clone' a remote repository and 'push' to it. There is no 'pull' functionality because this can fail badly, so there is a 'fetch' which retrieves all remote branches without merging (and never fails). The user can then select the remote branch in the branch pull-down and merge it. --- src/blenderbim/blenderbim/tool/ifcgit.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index f37d9289ba..ac77318933 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -191,6 +191,13 @@ class IfcGit: result[branch.commit.hexsha].append(branch) else: result[branch.commit.hexsha] = [branch] + if repo.remotes: + for remote in repo.remotes: + for ref in remote.refs: + if ref.commit.hexsha in result: + result[ref.commit.hexsha].append(ref) + else: + result[ref.commit.hexsha] = [ref] return result @classmethod From d98b0766c2271a92e6d5c23b2d0245569b0c258c Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Sun, 21 May 2023 09:11:26 +0100 Subject: [PATCH 10/65] Allow missing/broken GitPython `import git` can fail badly with a broken xcode installation, so we let blenderbim run anyway --- src/blenderbim/blenderbim/tool/ifcgit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index ac77318933..2cd8ca9cb1 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -3,7 +3,10 @@ import re # allows git import even if git executable isn't found os.environ["GIT_PYTHON_REFRESH"] = "quiet" -import git +try: + import git +except: + print("Warning: GitPython not available.") import bpy from blenderbim.bim.ifc import IfcStore import blenderbim.tool as tool From 4184e25c6814f054022539d041a9dca7935b0db0 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:33:16 +0100 Subject: [PATCH 11/65] Easier grouping of materials by allowing material attributes editing from the IfcMaterial subpanel in the scene properties tab --- .../bim/module/material/__init__.py | 5 +++- .../bim/module/material/operator.py | 30 +++++++++++++++++++ .../blenderbim/bim/module/material/prop.py | 3 ++ .../blenderbim/bim/module/material/ui.py | 19 +++++++++--- src/blenderbim/blenderbim/core/material.py | 15 ++++++++++ src/blenderbim/blenderbim/core/tool.py | 5 ++++ src/blenderbim/blenderbim/tool/material.py | 26 ++++++++++++++++ 7 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index 4a2b06b8dc..bb67ee7a96 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -31,13 +31,16 @@ classes = ( operator.ContractMaterialCategory, operator.CopyMaterial, operator.DisableEditingAssignedMaterial, + operator.DisableEditingMaterial, + operator.DisableEditingMaterials, operator.DisableEditingMaterialSetItem, operator.DisableEditingMaterialSetItemProfile, - operator.DisableEditingMaterials, operator.EditAssignedMaterial, + operator.EditMaterial, operator.EditMaterialSetItem, operator.EditMaterialSetItemProfile, operator.EnableEditingAssignedMaterial, + operator.EnableEditingMaterial, operator.EnableEditingMaterialSetItem, operator.EnableEditingMaterialSetItemProfile, operator.ExpandMaterialCategory, diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 5992a33b5c..b33d31b9a1 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -59,6 +59,36 @@ class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator): core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material)) +class EnableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.enable_editing_material" + bl_label = "Enable Editing Material" + bl_options = {"REGISTER", "UNDO"} + material: bpy.props.IntProperty() + + def _execute(self, context): + core.enable_editing_material(tool.Material, material=tool.Ifc.get().by_id(self.material)) + + +class EditMaterial(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.edit_material" + bl_label = "Edit Material" + bl_options = {"REGISTER", "UNDO"} + material: bpy.props.IntProperty() + + def _execute(self, context): + core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material)) + + +class DisableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.disable_editing_material" + bl_label = "Disable Editing Material" + bl_options = {"REGISTER", "UNDO"} + material: bpy.props.IntProperty() + + def _execute(self, context): + core.disable_editing_material(tool.Material) + + class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_parameterized_profile" bl_label = "Assign Parameterized Profile" diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 9abce3f47c..16c00f6978 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -131,6 +131,9 @@ class BIMMaterialProperties(PropertyGroup): materials: CollectionProperty(name="Materials", type=Material) active_material_index: IntProperty(name="Active Material Index") profiles: EnumProperty(items=get_profiles, name="Profiles") + active_material_id: IntProperty(name="Active Material ID") + material_attributes: CollectionProperty(name="Material Attributes", type=Attribute) + editing_material_type = StringProperty(name="Editing Material Type") class BIMObjectMaterialProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index a413c426b3..a1b63274bc 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -57,13 +57,21 @@ class BIM_PT_materials(Panel): row.alignment = "RIGHT" if self.props.material_type == "IfcMaterial": - row.operator("bim.add_material", text="", icon="ADD") if self.props.materials and self.props.active_material_index < len(self.props.materials): material = self.props.materials[self.props.active_material_index] if material.ifc_definition_id: - op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") - op.material = material.ifc_definition_id - row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id + if self.props.active_material_id: + row.operator("bim.edit_material", text="", icon="CHECKMARK").material = material.ifc_definition_id + row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id + self.draw_editable_material_attributes_ui() + else: + row.operator("bim.add_material", text="", icon="ADD") + op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") + op.material = material.ifc_definition_id + row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id + row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id + else: + row.operator("bim.add_material", text="", icon="ADD") else: row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type if self.props.materials and self.props.active_material_index < len(self.props.materials): @@ -76,6 +84,9 @@ class BIM_PT_materials(Panel): self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index") + def draw_editable_material_attributes_ui(self): + blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout) + class BIM_PT_material(Panel): bl_label = "IFC Material" bl_idname = "BIM_PT_material" diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py index f22698ee34..ca0c872199 100644 --- a/src/blenderbim/blenderbim/core/material.py +++ b/src/blenderbim/blenderbim/core/material.py @@ -72,3 +72,18 @@ def disable_editing_materials(material): def select_by_material(material_tool, material=None): material_tool.select_elements(material_tool.get_elements_by_material(material)) + +def enable_editing_material(material_tool, material): + material_tool.load_material_attributes(material) + material_tool.enable_editing_material(material) + +def edit_material(ifc, material_tool, material): + attributes = material_tool.get_material_attributes() + ifc.run("material.edit_material", material=material, attributes=attributes) + material_tool.disable_editing_material() + material_type = material_tool.get_active_material_type() + material_tool.import_material_definitions(material_type) + material_tool.enable_editing_materials() + +def disable_editing_material(material_tool): + material_tool.disable_editing_material() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 5c1a55aa7e..b2080f2125 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -432,14 +432,19 @@ class Loader: class Material: def add_default_material_object(cls): pass def delete_object(cls, obj): pass + def disable_editing_material(cls): pass def disable_editing_materials(cls): pass + def enable_editing_material(cls, material): pass def enable_editing_materials(cls): pass def get_active_material_type(cls): pass + def get_active_material_type(cls): pass def get_elements_by_material(cls, material): pass + def get_material_attributes(cls): pass def get_name(cls, obj): pass def import_material_definitions(cls, material_type): pass def is_editing_materials(cls): pass def is_material_used_in_sets(cls, material): pass + def load_material_attributes(cls, material): pass def select_elements(cls, elements): pass diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py index 60bf3e32e6..27aaab96f5 100644 --- a/src/blenderbim/blenderbim/tool/material.py +++ b/src/blenderbim/blenderbim/tool/material.py @@ -107,3 +107,29 @@ class Material(blenderbim.core.tool.Material): obj = tool.Ifc.get_object(element) if obj: obj.select_set(True) + + @classmethod + def get_active_material_type(cls): + return bpy.context.scene.BIMMaterialProperties.material_type + + @classmethod + def load_material_attributes(cls, material): + props = bpy.context.scene.BIMMaterialProperties + props.material_attributes.clear() + blenderbim.bim.helper.import_attributes2(material, props.material_attributes) + + @classmethod + def enable_editing_material(cls, material): + props = bpy.context.scene.BIMMaterialProperties + props.active_material_id = material.id() + props.editing_material_type = "ATTRIBUTES" + + @classmethod + def get_material_attributes(cls): + return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMMaterialProperties.material_attributes) + + @classmethod + def disable_editing_material(cls): + props = bpy.context.scene.BIMMaterialProperties + props.active_material_id = 0 + props.editing_material_type = "" From a4a6dce6defbddd02ed1b1aeba0070540aec7cec Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:42:50 +0100 Subject: [PATCH 12/65] BBIM D: Copy cost items, export Cost Schedules individually, start improving Cost Schedule UI --- .../blenderbim/bim/module/cost/__init__.py | 1 + .../blenderbim/bim/module/cost/operator.py | 28 +++-- .../blenderbim/bim/module/cost/prop.py | 1 + .../blenderbim/bim/module/cost/ui.py | 104 ++++++++++------- src/blenderbim/blenderbim/core/cost.py | 13 ++- src/blenderbim/blenderbim/tool/cost.py | 9 +- src/ifc5d/ifc5d/ifc5Dspreadsheet.py | 7 +- .../ifcopenshell/api/cost/copy_cost_item.py | 110 ++++++++++++++++++ .../api/material/edit_material.py | 29 +++++ 9 files changed, 243 insertions(+), 59 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index b05e41a5ad..e11a6ef850 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -76,6 +76,7 @@ classes = ( operator.LoadCostItemTaskQuantities, operator.LoadCostItemResourceQuantities, operator.ChangeParentCostItem, + operator.CopyCostItem, prop.CostItem, prop.CostItemQuantity, prop.CostItemType, diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 29c2fc1141..c76178e320 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -97,10 +97,9 @@ class AddSummaryCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Add Cost Item" bl_options = {"REGISTER", "UNDO"} bl_description = "Add a summary cost item" - cost_schedule: bpy.props.IntProperty() def _execute(self, context): - core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule)) + core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Cost.get_active_cost_schedule()) class AddCostItem(bpy.types.Operator, tool.Ifc.Operator): @@ -114,6 +113,16 @@ class AddCostItem(bpy.types.Operator, tool.Ifc.Operator): core.add_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item)) +class CopyCostItem(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.copy_cost_item" + bl_label = "Copy Cost Item" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Copy a cost item" + + def _execute(self, context): + core.copy_cost_item(tool.Ifc, tool.Cost) + + class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.expand_cost_item" bl_label = "Expand Cost Item" @@ -214,8 +223,9 @@ class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator): core.unassign_cost_item_type( tool.Ifc, tool.Cost, - self.cost_item, - products=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [], + tool.Spatial, + cost_item=tool.Ifc.get().by_id(self.cost_item), + product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [], ) return {"FINISHED"} @@ -491,7 +501,7 @@ class AddCostColumn(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} name: bpy.props.StringProperty() - def _execute(self, context): + def execute(self, context): core.add_cost_column(tool.Cost, self.name) return {"FINISHED"} @@ -619,10 +629,12 @@ class ExportCostSchedules(bpy.types.Operator): bl_label = "Export Cost Schedule" bl_options = {"REGISTER", "UNDO"} bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file" + cost_schedule: bpy.props.IntProperty() format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", ""))) def execute(self, context): - core.export_cost_schedules(tool.Cost, format=self.format) + cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None + core.export_cost_schedules(tool.Cost, format=self.format, cost_schedule=cost_schedule) return {"FINISHED"} def invoke(self, context, event): @@ -667,7 +679,9 @@ class LoadProductCostItems(bpy.types.Operator): return True def execute(self, context): - core.load_product_cost_items(tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)) + core.load_product_cost_items( + tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id) + ) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index cdd5e08e92..eddc816e14 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -213,3 +213,4 @@ class BIMCostProperties(PropertyGroup): name="Show Nested Tasks", default=False, update=update_active_cost_item_resources ) change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent) + show_cost_item_operators: BoolProperty(name="Show Cost Item Operators", default=False) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index f0aa85c2e5..db39cc51f1 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -43,43 +43,54 @@ class BIM_PT_cost_schedules(Panel): self.props = context.scene.BIMCostProperties row = self.layout.row() - if CostSchedulesData.data["total_cost_schedules"]: - row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT") - row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT") - else: - row.label(text="No Cost Schedules Found found.", icon="COMMUNITY") - - row = self.layout.row() - row.prop(self.props, "cost_schedule_predefined_types") - row.operator("bim.add_cost_schedule", icon="ADD", text="Add") + if not self.props.active_cost_schedule_id: + if CostSchedulesData.data["total_cost_schedules"]: + row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT") + row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT") + else: + row.label(text="No Cost Schedules Found found.", icon="COMMUNITY") + row = self.layout.row() + row.prop(self.props, "cost_schedule_predefined_types") + row.operator("bim.add_cost_schedule", icon="ADD", text="Add") for schedule in CostSchedulesData.data["schedules"]: self.draw_cost_schedule_ui(schedule) def draw_cost_schedule_ui(self, cost_schedule): row = self.layout.row(align=True) - row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON") - if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]: - op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned") + row.label(text="Currently editing: {}".format(cost_schedule["name"]), icon="LINENUMBERS_ON") + grid = self.layout.grid_flow(columns=2, even_columns=True) + col = grid.column() + row1 = col.row(align=True) + row1.alignment = "LEFT" + row1.label(text="Schedule tools") + row1 = col.row(align=True) + row1.alignment = "RIGHT" + row1.operator("bim.export_cost_schedules", text="Export", icon="EXPORT").cost_schedule = cost_schedule["id"] + row2 = col.row(align=True) + row2.alignment = "RIGHT" + op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned") op.cost_schedule = cost_schedule["id"] - row.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned") + row2.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned") - row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY") + col = grid.column() + row1 = col.row(align=True) + row1.alignment = "LEFT" + row1.label(text="Settings") + row1 = col.row(align=True) + row1.alignment = "RIGHT" + row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY") if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES": row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK") - elif self.props.is_editing == "COST_ITEMS": - row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule["id"] - row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL") - elif self.props.active_cost_schedule_id: - row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"] + row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL") else: + row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON") row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"] row.operator( "bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL" ).cost_schedule = cost_schedule["id"] row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"] - if self.props.active_cost_schedule_id == cost_schedule["id"]: if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES": self.draw_editable_cost_schedule_ui() @@ -101,30 +112,39 @@ class BIM_PT_cost_schedules(Panel): row = self.layout.row(align=True) row.alignment = "RIGHT" ifc_definition_id = None + row = self.layout.row(align=True) + row.label(text="Cost Item Tools") + row = self.layout.row(align=True) + row.alignment = "RIGHT" + row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD") + row.operator("bim.expand_all_tasks", text="Expand All") + row.operator("bim.contract_all_tasks", text="Contract All") + row = self.layout.row(align=True) + row.alignment = "RIGHT" if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items): ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id if ifc_definition_id: - row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED") - row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA") - if not CostSchedulesData.data["is_editing_rates"]: - op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES") + row.prop(self.props, "show_cost_item_operators", text="Edit", icon="DOWNARROW_HLT") + row.operator("bim.add_cost_item", text="Add", icon="ADD").cost_item = ifc_definition_id + row.operator("bim.copy_cost_item", text="Copy", icon="ADD") + row.operator("bim.remove_cost_item", text="Delete", icon="X").cost_item = ifc_definition_id + if self.props.show_cost_item_operators: + row = self.layout.row(align=True) + row.alignment = "RIGHT" + row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED") + row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA") + if not CostSchedulesData.data["is_editing_rates"]: + op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES") + op.cost_item = ifc_definition_id + op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC") op.cost_item = ifc_definition_id - - op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC") - op.cost_item = ifc_definition_id - - row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id - - if self.props.active_cost_item_id == ifc_definition_id: - if self.props.cost_item_editing_type == "ATTRIBUTES": - row.operator("bim.edit_cost_item", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL") - else: - op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL") - op.cost_item = ifc_definition_id - - row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id - + if self.props.active_cost_item_id == ifc_definition_id: + if self.props.cost_item_editing_type == "ATTRIBUTES": + row.operator("bim.edit_cost_item", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL") + else: + op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL") + op.cost_item = ifc_definition_id self.layout.template_list( "BIM_UL_cost_items", "", @@ -698,9 +718,7 @@ class BIM_PT_Costing_Tools(Panel): def draw(self, context): self.props = context.scene.BIMCostProperties row = self.layout.row() - row.operator( - "bim.load_product_cost_items", icon="FILE_REFRESH" - ) + row.operator("bim.load_product_cost_items", icon="FILE_REFRESH") row = self.layout.row() row.template_list( "BIM_UL_product_cost_items", diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index 94f911099f..f995ad12c0 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -236,8 +236,8 @@ def calculate_cost_item_resource_value(ifc, cost_item): ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item) -def export_cost_schedules(cost, format): - cost.export_cost_schedules(format) +def export_cost_schedules(cost, format, cost_schedule=None): + cost.export_cost_schedules(format, cost_schedule) def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type): @@ -276,4 +276,11 @@ def change_parent_cost_item(ifc, cost, new_parent): if cost_item : ifc.run("nest.change_nest", item=cost_item, new_parent=new_parent) cost.disable_editing_cost_item_parent() - cost.load_cost_schedule_tree() \ No newline at end of file + cost.load_cost_schedule_tree() + +def copy_cost_item(ifc, cost): + cost_item = cost.get_highlighted_cost_item() + if cost_item: + cost_item = ifc.run("cost.copy_cost_item", cost_item=cost_item) + cost.disable_editing_cost_item_parent() + cost.load_cost_schedule_tree() diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index 2ebdcd7f1f..181815eb18 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -498,24 +498,23 @@ class Cost(blenderbim.core.tool.Cost): props.is_cost_update_enabled = True @classmethod - def export_cost_schedules(cls, format=None): + def export_cost_schedules(cls, format=None, cost_schedule=None): path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", "cost_schedules") if format == "CSV": from ifc5d.ifc5Dspreadsheet import Ifc5DCsvWriter - if not os.path.exists(path): os.makedirs(path) - writer = Ifc5DCsvWriter(file=tool.Ifc.get(), output=path) + writer = Ifc5DCsvWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule) writer.write() elif format == "ODS": from ifc5d.ifc5Dspreadsheet import Ifc5DOdsWriter - writer = Ifc5DOdsWriter(file=tool.Ifc.get(), output=path) + writer = Ifc5DOdsWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule) writer.write() elif format == "XLSX": from ifc5d.ifc5Dspreadsheet import Ifc5DXlsxWriter - writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path) + writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule ) writer.write() @classmethod diff --git a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py index 546f2bd2c1..3fa9e9b1c5 100644 --- a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py +++ b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py @@ -195,12 +195,13 @@ class IfcDataGetter: class Ifc5Dwriter: - def __init__(self, file=None, output=None): + def __init__(self, file=None, output=None, cost_schedule=None): self.output = output if isinstance(file, str): self.file = ifcopenshell.open(file) else: self.file = file + self.cost_schedule = cost_schedule self.cost_schedules = [] self.sheet_data = {} self.column_indexes = [] @@ -220,6 +221,10 @@ class Ifc5Dwriter: self.used_names = [] for i in range(26): self.column_indexes.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % 26]) + if self.cost_schedule: + for cost_schedule in self.cost_schedules: + if cost_schedule.id() != self.cost_schedule.id(): + self.cost_schedules.remove(cost_schedule) for cost_schedule in self.cost_schedules: sheet_id = cost_schedule.id() self.sheet_data[sheet_id] = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py new file mode 100644 index 0000000000..55c4660b9f --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py @@ -0,0 +1,110 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, cost_item=None): + """Copies all cost items and related relationships + + The following relationships are also duplicated: + + * The copy will have the same attributes and property sets as the original cost item + * The copy will be assigned to the parent cost schedule + * The copy will have duplicated nested cost items + + :param cost_item: The cost item to be duplicated + :type cost_item: ifcopenshell.entity_instance.entity_instance + :return: The duplicated cost item or the list of duplicated cost items if the latter has children + :rtype: ifcopenshell.entity_instance.entity_instance or list of ifcopenshell.entity_instance.entity_instance + + Example: + .. code:: python + + # We have a cost item + cost_item = CostItem(name="Design new feature", deadline="2023-03-01") + + # And now we have two + duplicated_cost_item = project.duplicate_cost_item(cost_item) + + + """ + self.file = file + self.settings = {"cost_item": cost_item} + + def execute(self): + self.new_cost_items = [] + self.duplicate_cost_item(self.settings["cost_item"]) + + def duplicate_cost_item(self, cost_item): + new_cost_item = ifcopenshell.util.element.copy_deep(self.file, cost_item) + self.new_cost_items.append(new_cost_item) + self.copy_indirect_attributes(cost_item, new_cost_item) + return new_cost_item + + def copy_indirect_attributes(self, from_element, to_element): + for inverse in self.file.get_inverse(from_element): + if inverse.is_a("IfcRelDefinesByProperties"): + inverse = ifcopenshell.util.element.copy(self.file, inverse) + inverse.RelatedObjects = [to_element] + pset = ifcopenshell.util.element.copy_deep( + self.file, inverse.RelatingPropertyDefinition + ) + inverse.RelatingPropertyDefinition = pset + elif inverse.is_a("IfcRelNests") and inverse.RelatingObject == from_element: + nested_cost_items = [e for e in inverse.RelatedObjects] + if nested_cost_items: + new_cost_items = [] + for cost_item in nested_cost_items: + new_cost_item = self.duplicate_cost_item(cost_item) + new_cost_items.append(new_cost_item) + inverse = ifcopenshell.util.element.copy(self.file, inverse) + inverse.RelatingObject = to_element + inverse.RelatedObjects = new_cost_items + for cost_item in new_cost_items: + ifcopenshell.api.run( + "nest.unassign_object", self.file, related_object=cost_item + ) + rel = ifcopenshell.api.run( + "nest.assign_object", + self.file, + related_object=cost_item, + relating_object=to_element, + ) + # elif inverse.is_a("IfcRelAssignsToProduct"): + # continue + # to_element.IsDefinedBy = inverse.RelatingOrder + # elif inverse.is_a("IfcRelAssignsToProcess"): + # to_element.IsDefinedBy = inverse.RelatingProcess + # elif inverse.is_a("IfcRelAssignsToResource"): + # continue + # elif inverse.is_a("IfcRelAssignsToControl"): + # continue + # elif inverse.is_a("IfcRelDefinesByObject"): + # continue + else: + for i, value in enumerate(inverse): + if value == from_element: + new_inverse = ifcopenshell.util.element.copy(self.file, inverse) + new_inverse[i] = to_element + elif isinstance(value, (tuple, list)) and from_element in value: + new_value = list(value) + new_value.append(to_element) + inverse[i] = new_value \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py new file mode 100644 index 0000000000..c712af15ac --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py @@ -0,0 +1,29 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + + +class Usecase: + def __init__(self, file, material=None, attributes=None): + """Edits the attributes of an IfcMaterial""" + + self.file = file + self.settings = {"material": material, "attributes": attributes or {}} + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["material"], name, value) From eb54f5a0cb8754102ab0eaa348a061ac367ad5f3 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:45:04 +0100 Subject: [PATCH 13/65] ios api task fix: remove orphan IfcRelNests when deleting tasks --- .../ifcopenshell/api/sequence/remove_task.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py index 485d2c23ea..a7e7708b08 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py @@ -76,6 +76,13 @@ class Usecase: ) elif not inverse.RelatedObjects: self.file.remove(inverse) + elif self.settings["task"] in inverse.RelatedObjects: + related_objects = list(inverse.RelatedObjects) + related_objects.remove(self.settings["task"]) + if not related_objects: + self.file.remove(inverse) + else: + inverse.RelatedObjects = related_objects elif inverse.is_a("IfcRelAssignsToControl"): if ( inverse.RelatingControl == self.settings["task"] From 27a4976b9de689e1a4fcb41266f0cfba59e32221 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:45:28 +0100 Subject: [PATCH 14/65] remove annoying print statement --- src/ifcopenshell-python/ifcopenshell/util/date.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index d99e23f632..8f7c45c054 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -186,7 +186,6 @@ def string_to_duration(duration_string): def parse_duration(value): - print("parsing duration", value) if not value: return None if isinstance(value, str): From 11a38af901621d4afb0f60b4d3d18d84c3e1820f Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:48:34 +0100 Subject: [PATCH 15/65] BBIMD: enable deleting sequence relationships from list of predecessors/successors --- src/blenderbim/blenderbim/bim/module/sequence/ui.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index ca667dc6bf..79e7191b6a 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -380,6 +380,7 @@ class BIM_PT_work_schedules(Panel): row.operator("bim.add_summary_task", text="Add Summary Task", icon="ADD").work_schedule = work_schedule_id row.operator("bim.expand_all_tasks", text="Expand All") row.operator("bim.contract_all_tasks", text="Contract All") + row = self.layout.row(align=True) self.draw_task_operators() self.layout.template_list( "BIM_UL_tasks", @@ -438,6 +439,11 @@ class BIM_PT_work_schedules(Panel): op.lag_time = sequence["TimeLag"] op = row.operator("bim.enable_editing_sequence_attributes", text="Edit Sequence", icon="GREASEPENCIL") op.sequence = sequence["id"] + if process_type == "RelatingProcess": + op = row.operator("bim.unassign_predecessor", text="", icon="X") + elif process_type == "RelatedProcess": + op = row.operator("bim.unassign_successor", text="", icon="X") + op.task = task["id"] def draw_editable_sequence_attributes_ui(self): blenderbim.bim.helper.draw_attributes(self.props.sequence_attributes, self.layout) From a8554d9e56dbf87e26d44bcb51d7b255f70c3e0f Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:52:05 +0100 Subject: [PATCH 16/65] small fix for work schedule derived start/end dates --- src/blenderbim/blenderbim/tool/sequence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index fe6d4d136b..1fd9b465e7 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -154,7 +154,7 @@ class Sequence(blenderbim.core.tool.Sequence): if column_type == "IfcTask": return task.get_info(task)[name] or "" elif column_type == "IfcTaskTime" and task.TaskTime: - return task.TaskTime.get_info(task)[name] + return task.TaskTime.get_info(task)[name] if task.TaskTime.get_info(task)[name] else "" return task.Identification or "" def natural_sort_key(i, _nsre=re.compile("([0-9]+)")): @@ -212,7 +212,7 @@ class Sequence(blenderbim.core.tool.Sequence): item.calendar = "" item.derived_calendar = calendar.Name or "Unnamed" if calendar else "" - if task.TaskTime: + if task.TaskTime and (task.TaskTime.ScheduleStart or task.TaskTime.ScheduleFinish or task.TaskTime.ScheduleDuration): task_time = task.TaskTime item.start = ( canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleStart)) From d2d5db9bf6fb987bf91a129ad70a58bd069e44f3 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 12:59:54 +0100 Subject: [PATCH 17/65] Fix selecting unassigned work/cost schedule products --- src/blenderbim/blenderbim/core/cost.py | 2 +- src/blenderbim/blenderbim/core/sequence.py | 2 +- src/blenderbim/blenderbim/core/tool.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index f995ad12c0..b4dddf8f07 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -249,7 +249,7 @@ def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type): def select_unassigned_products(ifc, cost, spatial): - spatial.deselect_all() + spatial.deselect_objects() products = ifc.get().by_type("IfcElement") cost_schedule = cost.get_active_cost_schedule() selection = [product for product in products if not cost.has_cost_assignments(product, cost_schedule)] diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index f25bf70c7e..4a2b625cf1 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -443,7 +443,7 @@ def select_work_schedule_products(sequence, spatial, work_schedule=None): def select_unassigned_work_schedule_products(ifc, sequence, spatial): - spatial.deselect_all() + spatial.deselect_objects() products = ifc.get().by_type("IfcElement") work_schedule = sequence.get_active_work_schedule() schedule_products = sequence.get_work_schedule_products(work_schedule) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index b2080f2125..29eb554456 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -165,8 +165,7 @@ class Cost: def expand_cost_item_rate(cls, cost_item): pass def expand_cost_item(cls, cost_item): pass def expand_cost_items(cls): pass - def export_cost_schedules(cls, format): pass - def export_cost_schedules(cls, format): pass + def export_cost_schedules(cls, format, cost_schedule): pass def format_unit(cls, unit): pass def get_active_cost_item(cls): pass def get_active_cost_schedule(cls): pass From f97752c3681bc7c336609674e8e2ce2936cda687 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 21 May 2023 13:25:15 +0100 Subject: [PATCH 18/65] fix contracting/expanding schedule of rates --- src/blenderbim/blenderbim/core/cost.py | 2 +- src/blenderbim/blenderbim/tool/cost.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index b4dddf8f07..e96541e52b 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -130,7 +130,7 @@ def assign_cost_value(ifc, cost_item, cost_rate): ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate) def load_schedule_of_rates(cost, schedule_of_rates): - cost.load_schedule_of_rates(schedule_of_rates) + cost.load_schedule_of_rates_tree(schedule_of_rates) def unassign_cost_item_quantity(ifc, cost, cost_item, products): ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products) diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index 181815eb18..5079729cec 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -452,7 +452,7 @@ class Cost(blenderbim.core.tool.Cost): contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) contracted_cost_item_rates.remove(cost_item) props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) - cls.load_schedule_of_rates(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates))) + cls.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates))) @classmethod def contract_cost_item_rate(cls, cost_item): @@ -460,7 +460,7 @@ class Cost(blenderbim.core.tool.Cost): contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) contracted_cost_item_rates.append(cost_item) props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) - cls.load_schedule_of_rates(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates))) + cls.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates))) @classmethod def create_new_cost_item_li(cls, props_collection, cost_item, level_index, type="cost_rate"): From 1b2ab4e3f0df3c980c9c49ca337c18d750319ed8 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sun, 21 May 2023 15:03:06 -0500 Subject: [PATCH 19/65] partially addresses #3103. --- .../blenderbim/bim/module/pset/operator.py | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 2fa01640eb..dd61f0db4d 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -248,6 +248,7 @@ class EditPset(bpy.types.Operator, Operator): def _execute(self, context): self.file = IfcStore.get_file() + objects = tool.Blender.get_selected_objects() props = get_pset_props(context, self.obj, self.obj_type) ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type) element = tool.Ifc.get().by_id(ifc_definition_id) @@ -275,28 +276,40 @@ class EditPset(bpy.types.Operator, Operator): e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected ] - if pset.is_a() in ("IfcPropertySet", "IfcMaterialProperties", "IfcProfileProperties"): - ifcopenshell.api.run( - "pset.edit_pset", - self.file, - pset=pset, - name=props.active_pset_name, - properties=properties, - pset_template=blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name), - ) - else: - for key, value in properties.items(): - if isinstance(value, float): - properties[key] = round(value, 4) - ifcopenshell.api.run( - "pset.edit_qto", - self.file, - qto=pset, - name=props.active_pset_name, - properties=properties, - ) - if tool.Cost.has_schedules(): - tool.Cost.update_cost_items(pset=pset) + + for obj in objects: + element = blenderbim.tool.Ifc.get_entity(obj) + pset_list = element.IsDefinedBy + copy_of_properties = properties.copy() + + for rel in pset_list: + rel_name = rel.RelatingPropertyDefinition.Name + if rel.is_a("IfcRelDefinesByProperties") and rel_name == props.active_pset_name: + rel_ID = rel.RelatingPropertyDefinition.id() + rel_pset = rel.RelatingPropertyDefinition + if rel_pset.is_a() in ("IfcPropertySet", "IfcMaterialProperties", "IfcProfileProperties"): + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=rel_pset, + name=props.active_pset_name, + properties=copy_of_properties, + pset_template=blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name), + ) + else: + for key, value in copy_of_properties.items(): + if isinstance(value, float): + copy_of_properties[key] = round(value, 4) + ifcopenshell.api.run( + "pset.edit_qto", + self.file, + qto=rel_pset, + name=props.active_pset_name, + properties=copy_of_properties, + ) + if tool.Cost.has_schedules(): + tool.Cost.update_cost_items(pset=rel_pset) + bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type) tool.Blender.update_viewport() From 628b6830db7e3718332acf8d923674050e34ea35 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 22 May 2023 15:47:28 +0500 Subject: [PATCH 20/65] Fixed error loading .ifc and activating view for drawing with underlay --- src/blenderbim/blenderbim/bim/module/drawing/operator.py | 3 +++ src/blenderbim/blenderbim/bim/module/drawing/prop.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 734e5f2b01..bc4c90af90 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1342,6 +1342,8 @@ class ActivateDrawing(bpy.types.Operator): dprops = bpy.context.scene.DocProperties core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing) dprops.active_drawing_id = self.drawing + # reset DrawingsData to reload_drawing_styles work correctly + DrawingsData.is_loaded = False dprops.drawing_styles.clear() if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"): bpy.ops.bim.reload_drawing_styles() @@ -1423,6 +1425,7 @@ class ReloadDrawingStyles(bpy.types.Operator): bl_idname = "bim.reload_drawing_styles" bl_label = "Reload Drawing Styles" bl_options = {"REGISTER", "UNDO"} + bl_description = "Reload drawing styles for the active camera" def execute(self, context): if not DrawingsData.is_loaded: diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 91e2382425..11d90762f3 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -178,7 +178,8 @@ def update_schedule_name(self, context): def update_has_underlay(self, context): update_layer(self, context, "HasUnderlay", self.has_underlay) - if self.has_underlay: + # making sure that camera is active + if self.has_underlay and (context.active_object and context.active_object.data == self.id_data): bpy.ops.bim.reload_drawing_styles() bpy.ops.bim.activate_drawing_style() From 629aff1e503920bbe06f51992504b9b22c5d2e9c Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 22 May 2023 16:37:31 +0500 Subject: [PATCH 21/65] Added a descriptive error on attempt of deleting last material layer --- src/blenderbim/blenderbim/bim/module/material/operator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index b33d31b9a1..9a962c3ea1 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -368,7 +368,8 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)): if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1: - return + self.report({"ERROR"}, "Cannot remove material layer - IfcMaterialLayerSet should alawys have atleast 1 layer") + return {"ERROR"} ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer)) From 535e974cc334afd4f99abb820853ff49c5964b90 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Mon, 22 May 2023 22:06:08 -0500 Subject: [PATCH 22/65] bulk assignment of objects to an aggregate. --- .../bim/module/aggregate/operator.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index dd3d767377..e8b7eb623d 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -43,13 +43,17 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator): related_object: bpy.props.IntProperty() def _execute(self, context): - core.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)), - related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)), - ) + for obj in bpy.context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + core.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)), + related_obj=obj, + ) class BIM_OT_unassign_object(bpy.types.Operator, Operator): From 7dcccf9ea5973433d17f2db1daf431bca3f486ac Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 23 May 2023 15:59:15 +0500 Subject: [PATCH 23/65] UI problem with creating empty material layer set causing issues #3169 Now it's also possible to select some material that will be used for they first layer of material layer set. --- .../blenderbim/bim/module/material/operator.py | 18 +++++++++++++----- .../blenderbim/bim/module/material/ui.py | 7 ++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 9a962c3ea1..fad5d8f985 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -189,17 +189,23 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator): material=material, ) assigned_material = ifcopenshell.util.element.get_material(element) - if assigned_material.is_a("IfcMaterialLayerSet"): - if not assigned_material.MaterialLayers: + if assigned_material.is_a() in ("IfcMaterialLayerSet", "IfcMaterialLayerSetUsage"): + if assigned_material.is_a("IfcMaterialLayerSet"): + layer_set = assigned_material + else: + layer_set = assigned_material.ForLayerSet + + if not layer_set.MaterialLayers: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) layer = ifcopenshell.api.run( "material.add_layer", tool.Ifc.get(), - layer_set=assigned_material, - material=tool.Ifc.get().by_type("IfcMaterial")[0], + layer_set=layer_set, + material=material, ) thickness = 0.1 # Arbitrary metric thickness for now layer.LayerThickness = thickness / unit_scale + elif assigned_material.is_a("IfcMaterialProfileSet"): if not assigned_material.MaterialProfiles: named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName] @@ -368,7 +374,9 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)): if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1: - self.report({"ERROR"}, "Cannot remove material layer - IfcMaterialLayerSet should alawys have atleast 1 layer") + self.report( + {"ERROR"}, "Cannot remove material layer - IfcMaterialLayerSet should alawys have atleast 1 layer" + ) return {"ERROR"} ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer)) diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index a1b63274bc..be85ca2e06 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -155,7 +155,12 @@ class BIM_PT_object_material(Panel): row = self.layout.row(align=True) prop_with_search(row, self.props, "material_type", text="") - if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList": + if self.props.material_type in ( + "IfcMaterial", + "IfcMaterialList", + "IfcMaterialLayerSet", + "IfcMaterialLayerSetUsage", + ): prop_with_search(row, self.props, "material", text="") row.operator("bim.assign_material", icon="ADD", text="") From 7cae099fb4167ae89c711248b1a64e1d52ead94a Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 23 May 2023 16:20:06 +0500 Subject: [PATCH 24/65] Default material layer thickness for adding new layers Same as when you create IfcMaterialLayerSet --- src/blenderbim/blenderbim/bim/module/material/operator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index fad5d8f985..2390537890 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -330,7 +330,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() - ifcopenshell.api.run( + layer = ifcopenshell.api.run( "material.add_layer", self.file, **{ @@ -339,6 +339,10 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator): }, ) + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + thickness = 0.1 # Arbitrary metric thickness for now + layer.LayerThickness = thickness / unit_scale + class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.reorder_material_set_item" From c367989ebda3ee94abed13460729adb6e9e09c9f Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 23 May 2023 18:25:16 +0500 Subject: [PATCH 25/65] Roof modifier - more descriptive errors on invalid footprints #3171 --- .../blenderbim/bim/module/model/roof.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py index 239a9218d2..7df1d3bf4e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/roof.py +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -87,11 +87,29 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator): return {"CANCELLED"} bm = tool.Blender.get_bmesh_for_mesh(obj.data) + op_status, error_message = is_valid_roof_footprint(bm) + if error_message: + self.report(op_status, error_message) + return {"CANCELLED"} + generate_hiped_roof_bmesh(bm, self.mode, self.height, self.angle) tool.Blender.apply_bmesh(obj.data, bm) return {"FINISHED"} +def is_valid_roof_footprint(bm): + # should be bmesh to support edit mode + bm.verts.ensure_lookup_table() + base_z = bm.verts[0].co.z + all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]]) + if not all_verts_same_level: + return ( + {"ERROR"}, + "\nAll roof footprint vertices should have same Z-level.\nCurrently Z-level doesn't completely match", + ) + return ({"FINISHED"}, "") + + def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutate_current_bmesh=True): """return bmesh with gable roof geometry @@ -584,6 +602,11 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): # copying to make sure not to mutate the edit mode bmesh bm = tool.Blender.get_bmesh_for_mesh(obj.data) main_bm = bm.copy() + op_status, error_message = is_valid_roof_footprint(main_bm) + if error_message: + print("Error: %s" % error_message) + return main_bm + main_bm.edges.layers.int.new("BBIM_preview") si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) @@ -637,6 +660,12 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) props = obj.BIMRoofProperties + bm = tool.Blender.get_bmesh_for_mesh(obj.data) + op_status, error_message = is_valid_roof_footprint(bm) + if error_message: + self.report(op_status, error_message) + return {"CANCELLED"} + roof_data = props.get_general_kwargs() path_data = get_path_data(obj) roof_data["path_data"] = path_data @@ -646,6 +675,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): update_bbim_roof_pset(element, roof_data) refresh() # RoofData has to be updated before run update_roof_modifier_bmesh update_roof_modifier_bmesh(context) + update_roof_modifier_ifc_data(context) if bpy.context.object.mode == "EDIT": bpy.ops.object.mode_set(mode="OBJECT") From 92405d819bb0b0f00ed481d6564e273c6b537933 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Tue, 23 May 2023 22:45:41 +0100 Subject: [PATCH 26/65] BlenderBIM set gitattributes and autocrlf Force Git to always treat IFC files as text. Store IFC in Git with LF line-endings, but checked-out files are CRLF on Windows. --- .../blenderbim/bim/module/ifcgit/operator.py | 4 +-- src/blenderbim/blenderbim/tool/ifcgit.py | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py b/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py index 38dfe2c681..1253c5bbf5 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py @@ -137,7 +137,7 @@ class CommitChanges(bpy.types.Operator): class AddTag(bpy.types.Operator): """Tag selected revision""" - bl_label = "Add tag" + bl_label = "Tag selected revision" bl_idname = "ifcgit.add_tag" bl_options = {"REGISTER"} @@ -260,7 +260,7 @@ class Push(bpy.types.Operator): props = context.scene.IfcGitProperties repo = IfcGitData.data["repo"] remote = repo.remotes[props.select_remote] - remote.push() + remote.push(refspec=IfcGitData.data["repo"].active_branch.name) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index 2cd8ca9cb1..641d0ad295 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -16,10 +16,24 @@ class IfcGit: @classmethod def init_repo(cls, path_dir): IfcGitRepo.repo = git.Repo.init(path_dir) + autocrlf = "input" + if os.name == "nt": + autocrlf = "true" + IfcGitRepo.repo.config_writer().set_value("core", "autocrlf", autocrlf) + cls.config_info_attributes(IfcGitRepo.repo) @classmethod def clone_repo(cls, remote_url, local_folder): - IfcGitRepo.repo = git.Repo.clone_from(remote_url, local_folder) + autocrlf = "input" + if os.name == "nt": + autocrlf = "true" + IfcGitRepo.repo = git.Repo.clone_from( + url=remote_url, + to_path=local_folder, + allow_unsafe_options=True, + multi_options=["--config core.autocrlf=" + autocrlf], + ) + cls.config_info_attributes(IfcGitRepo.repo) return IfcGitRepo.repo @classmethod @@ -355,6 +369,15 @@ class IfcGit: config_writer.set_value(section, "cmd", "ifcmerge $BASE $LOCAL $REMOTE $MERGED") config_writer.set_value(section, "trustExitCode", True) + @classmethod + def config_info_attributes(cls, repo): + """Set IFC files as text in .git/info/attributes""" + path_attributes = os.path.join(repo.git_dir, "info", "attributes") + if not os.path.exists(path_attributes): + with open(path_attributes, "w") as f: + f.write("*.ifc text\n") + f.write("*.IFC text") + @classmethod def execute_merge(cls, path_ifc, operator): props = bpy.context.scene.IfcGitProperties From ff7b101b717db72b1d94640cf9c33f93b0b896e1 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sat, 13 May 2023 20:32:44 +0200 Subject: [PATCH 27/65] "Select instances" button is now grayed out when the type doesn't have any instance --- src/blenderbim/blenderbim/bim/module/type/ui.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index d2a1d8b2ae..35f8b0705f 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -59,7 +59,9 @@ class BIM_PT_type(Panel): oprops = context.active_object.BIMObjectProperties row = self.layout.row(align=True) row.label(text=f"{TypeData.data['total_instances']} Typed Objects") - row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="") + select_type_objects_row = row.row(align=True) + select_type_objects_row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="") + select_type_objects_row.enabled = int(TypeData.data["total_instances"]) > 0 def draw_product_ui(self, context): props = context.active_object.BIMTypeProperties From 3b07c086a8dbf072f98c194524a3a9484e5d3ab2 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sat, 13 May 2023 20:33:33 +0200 Subject: [PATCH 28/65] You can now duplicate a type from the properties panel + change the "paste" icon for the "duplicate" icon --- src/blenderbim/blenderbim/bim/module/model/ui.py | 2 +- src/blenderbim/blenderbim/bim/module/type/ui.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index d3b1515e82..5e3a0239d9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -117,7 +117,7 @@ class LaunchTypeManager(bpy.types.Operator): op = row.operator("bim.select_type", icon="OBJECT_DATA", text="") op.relating_type = relating_type["id"] - op = row.operator("bim.duplicate_type", icon="COPYDOWN", text="") + op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="") op.element = relating_type["id"] op = row.operator("bim.remove_type", icon="X", text="") op.element = relating_type["id"] diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 35f8b0705f..1ade5b1ecb 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -62,6 +62,8 @@ class BIM_PT_type(Panel): select_type_objects_row = row.row(align=True) select_type_objects_row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="") select_type_objects_row.enabled = int(TypeData.data["total_instances"]) > 0 + op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="") + op.element = context.active_object.BIMObjectProperties.ifc_definition_id def draw_product_ui(self, context): props = context.active_object.BIMTypeProperties From 1ce254cc9af0c0fbba1dec375f0e06e2778d2a0f Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sat, 13 May 2023 20:34:44 +0200 Subject: [PATCH 29/65] Selecting types and instances now show a helpful tooltip when the object is not selectable (due to being hidden or in a hidden collection) --- .../blenderbim/bim/module/type/operator.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index dc41a89912..7889ab4bfe 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -143,8 +143,10 @@ class SelectType(bpy.types.Operator): element = tool.Ifc.get().by_id(self.relating_type) obj = tool.Ifc.get_object(element) if obj: - context.view_layer.objects.active = obj - obj.select_set(True) + if obj in context.selectable_objects: + tool.Blender.select_and_activate_single_object(context, obj) + else: + self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.") return {"FINISHED"} @@ -177,10 +179,17 @@ class SelectTypeObjects(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() relating_type = bpy.data.objects.get(self.relating_type) if self.relating_type else context.active_object + at_least_one_selectable_typed_object = False for element in ifcopenshell.util.element.get_types(tool.Ifc.get_entity(relating_type)): obj = tool.Ifc.get_object(element) - if obj: + if obj and obj in context.selectable_objects: obj.select_set(True) + at_least_one_selectable_typed_object = True + if at_least_one_selectable_typed_object: + context.active_object.select_set(False) + context.view_layer.objects.active = context.selected_objects[0] + else: + self.report({"INFO"}, "Typed objects can't be selected : They may be hidden or in an excluded collection.") return {"FINISHED"} @@ -451,6 +460,10 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) new.Name += " Copy" bpy.ops.bim.load_type_thumbnails(ifc_class=new.is_a()) + if obj in context.selectable_objects: + tool.Blender.select_and_activate_single_object(context, new_obj) + else: + self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.") return {"FINISHED"} From 2b78871c014fe9c8264a76d5105bd121af49b011 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sat, 13 May 2023 20:35:52 +0200 Subject: [PATCH 30/65] The type manager now switches to the type when clicking on the "select type" button. Duplicating a type also changes the active type in the type manager --- src/blenderbim/blenderbim/bim/module/type/operator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index 7889ab4bfe..99d4f97936 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -147,6 +147,8 @@ class SelectType(bpy.types.Operator): tool.Blender.select_and_activate_single_object(context, obj) else: self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.") + context.scene.BIMModelProperties.ifc_class = element.is_a() + context.scene.BIMModelProperties.relating_type_id = str(self.relating_type) return {"FINISHED"} @@ -464,6 +466,8 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): tool.Blender.select_and_activate_single_object(context, new_obj) else: self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.") + context.scene.BIMModelProperties.ifc_class = new.is_a() + context.scene.BIMModelProperties.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} From 72169d06ad46fad3bed9b17ae43381c6ff1bd056 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sun, 14 May 2023 10:09:27 +0200 Subject: [PATCH 31/65] Fix Relating type enum not correctly initializing --- src/blenderbim/blenderbim/bim/module/model/prop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 0e6773431d..f503715938 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -57,7 +57,7 @@ def get_type_predefined_type(self, context): def update_ifc_class(self, context): bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class) - AuthoringData.data["relating_type_id"] = AuthoringData.relating_types() + AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id() AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail() @@ -82,7 +82,7 @@ def update_type_page(self, context): class BIMModelProperties(PropertyGroup): ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class) relating_type_id: bpy.props.EnumProperty( - items=get_relating_type_id, name="Construction Type", update=update_relating_type_id + items=get_relating_type_id, name="Relating Type", update=update_relating_type_id ) icon_id: bpy.props.IntProperty() updating: bpy.props.BoolProperty(default=False) From f8113d48577e37532a9a7cbe2682cc98a9163283 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Mon, 22 May 2023 20:56:45 +0200 Subject: [PATCH 32/65] You can now eyedrop an object in the 3D viewport to fill in its class in the type selector --- .../blenderbim/bim/module/type/operator.py | 2 ++ .../blenderbim/bim/module/type/prop.py | 35 ++++++++++++++++++- .../blenderbim/bim/module/type/ui.py | 7 ++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index 99d4f97936..a87f25ffb6 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -52,6 +52,7 @@ class AssignType(bpy.types.Operator, Operator): for obj in related_objects: core.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=type) oprops = obj.BIMObjectProperties + obj.BIMTypeProperties.relating_type_object = None class UnassignType(bpy.types.Operator): @@ -130,6 +131,7 @@ class DisableEditingType(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object obj.BIMTypeProperties.is_editing_type = False + obj.BIMTypeProperties.relating_type_object = None return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index 1b41f3af59..8e5c28e4bb 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -21,6 +21,7 @@ import ifcopenshell.util.type from blenderbim.bim.module.type.data import TypeData from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.ifc import IfcStore +import blenderbim.tool as tool from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -50,9 +51,41 @@ def update_relating_type_class(self, context): TypeData.is_loaded = False +def update_relating_type_from_object(self, context): + if self.relating_type_object is None: + return + element = tool.Ifc.get_entity(self.relating_type_object) + if not element: + return + element_type = ifcopenshell.util.element.get_type(element) + if not element_type: + return + self.relating_type = str(element_type.id()) + + +def is_object_class_applicable(self, obj): + if not TypeData.is_loaded: + TypeData.load() + element = tool.Ifc.get_entity(obj) + if not element: + return + element_type = ifcopenshell.util.element.get_type(element) + if element_type is None: + return False + return str(element_type.is_a()) in [r_t_c[0] for r_t_c in TypeData.data["relating_type_classes"]] + + class BIMTypeProperties(PropertyGroup): is_editing_type: BoolProperty(name="Is Editing Type") relating_type_class: EnumProperty( - items=get_relating_type_class, name="Relating Type Class", update=update_relating_type_class + items=get_relating_type_class, + name="Relating Type Class", + update=update_relating_type_class, ) relating_type: EnumProperty(items=get_relating_type, name="Relating Type") + relating_type_object: PointerProperty( + type=bpy.types.Object, + name="Copy Class", + update=update_relating_type_from_object, + poll=is_object_class_applicable, + ) diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index 1ade5b1ecb..d7feaf34bb 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -66,21 +66,24 @@ class BIM_PT_type(Panel): op.element = context.active_object.BIMObjectProperties.ifc_definition_id def draw_product_ui(self, context): + layout = self.layout props = context.active_object.BIMTypeProperties oprops = context.active_object.BIMObjectProperties if props.is_editing_type: - row = self.layout.row(align=True) + row = layout.row(align=True) + row_object = layout.row(align=True) row.prop(props, "relating_type_class", text="") if type_prop.get_relating_type(None, context): prop_with_search(row, props, "relating_type", text="") row.operator("bim.assign_type", icon="CHECKMARK", text="") + row_object.prop(props, "relating_type_object", icon="COPYDOWN") else: row.label(text="No Types Found") row.operator("bim.disable_editing_type", icon="CANCEL", text="") else: - row = self.layout.row(align=True) + row = layout.row(align=True) if TypeData.data["relating_type"]: row.label(text=TypeData.data["relating_type"]["name"]) op = row.operator("bim.select_type", icon="OBJECT_DATA", text="") From 8f41ae0c1b865e529a13c275dd2723d70ce4822e Mon Sep 17 00:00:00 2001 From: Gorgious Date: Tue, 23 May 2023 21:39:16 +0200 Subject: [PATCH 33/65] Using the eyedropper on a type automatically sets the type and validates assign_type. However it does display an annoying message saying the value was not set, I don't know how to get rid of it. --- src/blenderbim/blenderbim/bim/module/type/prop.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index 8e5c28e4bb..5f0afb42aa 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -52,15 +52,17 @@ def update_relating_type_class(self, context): def update_relating_type_from_object(self, context): - if self.relating_type_object is None: + obj = self.relating_type_object + if obj is None: return - element = tool.Ifc.get_entity(self.relating_type_object) + element = tool.Ifc.get_entity(obj) if not element: return element_type = ifcopenshell.util.element.get_type(element) if not element_type: return self.relating_type = str(element_type.id()) + bpy.ops.bim.assign_type() def is_object_class_applicable(self, obj): From 610ec7d8ad4e85103eea5eaf90f5b493ed0aa296 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 24 May 2023 19:33:14 +1000 Subject: [PATCH 34/65] Fix #3086. --- src/blenderbim/blenderbim/tool/drawing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index ee0b4f3590..8abe940481 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -217,8 +217,8 @@ class Drawing(blenderbim.core.tool.Drawing): @classmethod def delete_drawing_elements(cls, elements): for element in elements: - ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element) obj = tool.Ifc.get_object(element) + ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element) if obj: obj_data = obj.data bpy.data.objects.remove(obj) From d72ac7827b02f3fd8d39dd8e902508b6a14d67d0 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 24 May 2023 21:31:41 +1000 Subject: [PATCH 35/65] Fix #3193. Revert "partially addresses #3103." This reverts commit 1b2ab4e3f0df3c980c9c49ca337c18d750319ed8. --- .../blenderbim/bim/module/pset/operator.py | 57 +++++++------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index dd61f0db4d..2fa01640eb 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -248,7 +248,6 @@ class EditPset(bpy.types.Operator, Operator): def _execute(self, context): self.file = IfcStore.get_file() - objects = tool.Blender.get_selected_objects() props = get_pset_props(context, self.obj, self.obj_type) ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, self.obj, self.obj_type) element = tool.Ifc.get().by_id(ifc_definition_id) @@ -276,40 +275,28 @@ class EditPset(bpy.types.Operator, Operator): e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected ] - - for obj in objects: - element = blenderbim.tool.Ifc.get_entity(obj) - pset_list = element.IsDefinedBy - copy_of_properties = properties.copy() - - for rel in pset_list: - rel_name = rel.RelatingPropertyDefinition.Name - if rel.is_a("IfcRelDefinesByProperties") and rel_name == props.active_pset_name: - rel_ID = rel.RelatingPropertyDefinition.id() - rel_pset = rel.RelatingPropertyDefinition - if rel_pset.is_a() in ("IfcPropertySet", "IfcMaterialProperties", "IfcProfileProperties"): - ifcopenshell.api.run( - "pset.edit_pset", - self.file, - pset=rel_pset, - name=props.active_pset_name, - properties=copy_of_properties, - pset_template=blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name), - ) - else: - for key, value in copy_of_properties.items(): - if isinstance(value, float): - copy_of_properties[key] = round(value, 4) - ifcopenshell.api.run( - "pset.edit_qto", - self.file, - qto=rel_pset, - name=props.active_pset_name, - properties=copy_of_properties, - ) - if tool.Cost.has_schedules(): - tool.Cost.update_cost_items(pset=rel_pset) - + if pset.is_a() in ("IfcPropertySet", "IfcMaterialProperties", "IfcProfileProperties"): + ifcopenshell.api.run( + "pset.edit_pset", + self.file, + pset=pset, + name=props.active_pset_name, + properties=properties, + pset_template=blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name), + ) + else: + for key, value in properties.items(): + if isinstance(value, float): + properties[key] = round(value, 4) + ifcopenshell.api.run( + "pset.edit_qto", + self.file, + qto=pset, + name=props.active_pset_name, + properties=properties, + ) + if tool.Cost.has_schedules(): + tool.Cost.update_cost_items(pset=pset) bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type) tool.Blender.update_viewport() From 394b2003b3604b114a92533f56afbbe484d96b08 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 24 May 2023 11:40:28 +0500 Subject: [PATCH 36/65] Small error message on aggregating objects --- .../blenderbim/bim/module/aggregate/operator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index e8b7eb623d..f17727ac59 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -47,13 +47,18 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator): element = tool.Ifc.get_entity(obj) if not element: continue - core.assign_object( + relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)) + result = core.assign_object( tool.Ifc, tool.Aggregate, tool.Collector, - relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)), + relating_obj=relating_obj, related_obj=obj, ) + if not result: + self.report({"ERROR"}, f"Objects {relating_obj} and {obj} cannot be aggregated") + + return {"FINISHED"} class BIM_OT_unassign_object(bpy.types.Operator, Operator): From e22f4dd001e562c525b30f2c0717676a1b7ce732 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 24 May 2023 11:48:17 +0500 Subject: [PATCH 37/65] Array Modifier - support typing units in props Example - https://i.imgur.com/a0r0vUs.png Also accidentally fixed input_cursor_array not working properly for projects with non-meters units. --- .../blenderbim/bim/module/model/array.py | 17 +++++++++++------ .../blenderbim/bim/module/model/prop.py | 6 +++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/array.py b/src/blenderbim/blenderbim/bim/module/model/array.py index 42851123f3..e5cc88bf1a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/array.py +++ b/src/blenderbim/blenderbim/bim/module/model/array.py @@ -86,9 +86,10 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator): data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item] props = obj.BIMArrayProperties props.count = data["count"] - props.x = data["x"] - props.y = data["y"] - props.z = data["z"] + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + props.x = data["x"] * si_conversion + props.y = data["y"] * si_conversion + props.z = data["z"] * si_conversion props.use_local_space = data.get("use_local_space", False) props.sync_children = data.get("sync_children", False) props.method = data.get("method", "OFFSET") @@ -106,15 +107,16 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator): obj = context.active_object element = tool.Ifc.get_entity(obj) props = obj.BIMArrayProperties + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") data = json.loads(pset["Data"]) data[self.item] = { "children": data[self.item]["children"], "count": props.count, - "x": props.x, - "y": props.y, - "z": props.z, + "x": props.x / si_conversion, + "y": props.y / si_conversion, + "z": props.z / si_conversion, "use_local_space": props.use_local_space, "sync_children": props.sync_children, "method": props.method, @@ -187,6 +189,7 @@ class SelectArrayParent(bpy.types.Operator): obj.select_set(True) return {"FINISHED"} + class Input3DCursorXArray(bpy.types.Operator): bl_idname = "bim.input_cursor_x_array" bl_label = "Get 3d Cursor X Input for Array" @@ -202,6 +205,7 @@ class Input3DCursorXArray(bpy.types.Operator): props.x = cursor.location.x - obj.location.x return {"FINISHED"} + class Input3DCursorYArray(bpy.types.Operator): bl_idname = "bim.input_cursor_y_array" bl_label = "Get 3d Cursor Y Input for Array" @@ -217,6 +221,7 @@ class Input3DCursorYArray(bpy.types.Operator): props.y = cursor.location.y - obj.location.y return {"FINISHED"} + class Input3DCursorZArray(bpy.types.Operator): bl_idname = "bim.input_cursor_z_array" bl_label = "Get 3d Cursor Z Input for Array" diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index f503715938..c0a235ee62 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -155,9 +155,9 @@ class BIMArrayProperties(PropertyGroup): default=-1, description="Currently edited array index. -1 if not in array editing mode." ) count: bpy.props.IntProperty(name="Count", default=0, min=0) - x: bpy.props.FloatProperty(name="X", default=0) - y: bpy.props.FloatProperty(name="Y", default=0) - z: bpy.props.FloatProperty(name="Z", default=0) + x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE") + y: bpy.props.FloatProperty(name="Y", default=0, subtype="DISTANCE") + z: bpy.props.FloatProperty(name="Z", default=0, subtype="DISTANCE") use_local_space: bpy.props.BoolProperty( name="Use Local Space", description="Use local space for array items offset instead of world space", From 659cbe69c343a6a3c9324c28fa98a4e03a6eae80 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 24 May 2023 12:08:01 +0500 Subject: [PATCH 38/65] Stair Modifier - support typing units in props Example - https://i.imgur.com/XQsVge1.png --- .../blenderbim/bim/module/model/prop.py | 60 ++++++++++++------- .../blenderbim/bim/module/model/stair.py | 32 ++-------- 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index c0a235ee62..1a7e0aa370 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -176,6 +176,7 @@ class BIMArrayProperties(PropertyGroup): class BIMStairProperties(PropertyGroup): + non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type") stair_types = ( ("CONCRETE", "Concrete", ""), ("WOOD/STEEL", "Wood / Steel", ""), @@ -184,17 +185,17 @@ class BIMStairProperties(PropertyGroup): stair_added_previously: bpy.props.BoolProperty(default=False) is_editing: bpy.props.IntProperty(default=-1) - width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01) - height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01) + width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE") + height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE") number_of_treads: bpy.props.IntProperty(name="Number of treads", default=6, soft_min=1) - tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01) - tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01) - base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0) - top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0) + tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE") + tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE") + base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0, subtype="DISTANCE") + top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0, subtype="DISTANCE") has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True) stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE") - def get_props_kwargs(self): + def get_props_kwargs(self, convert_to_project_units=False): stair_kwargs = { "stair_type": self.stair_type, "width": self.width, @@ -204,27 +205,42 @@ class BIMStairProperties(PropertyGroup): } if self.stair_type == "CONCRETE": - stair_kwargs.update( - { - "base_slab_depth": self.base_slab_depth, - "top_slab_depth": self.top_slab_depth, - "has_top_nib": self.has_top_nib, - "tread_depth": self.tread_depth, - } - ) - return stair_kwargs + concrete_props = { + "base_slab_depth": self.base_slab_depth, + "top_slab_depth": self.top_slab_depth, + "has_top_nib": self.has_top_nib, + "tread_depth": self.tread_depth, + } + stair_kwargs.update(concrete_props) elif self.stair_type == "WOOD/STEEL": - stair_kwargs.update( - { - "tread_depth": self.tread_depth, - } - ) - return stair_kwargs + wood_steel_props = { + "tread_depth": self.tread_depth, + } + stair_kwargs.update(wood_steel_props) elif self.stair_type == "GENERIC": + pass + + if not convert_to_project_units: return stair_kwargs + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + for prop_name in stair_kwargs: + if prop_name in self.non_si_units_props: + continue + prop_value = stair_kwargs[prop_name] + stair_kwargs[prop_name] = prop_value / si_conversion + return stair_kwargs + + def set_props_kwargs_from_ifc_data(self, kwargs): + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + for prop_name in kwargs: + prop_value = kwargs[prop_name] + if prop_name not in self.non_si_units_props: + prop_value = prop_value * si_conversion + setattr(self, prop_name, prop_value) + class BIMSverchokProperties(PropertyGroup): node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree) diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index 5e60e595fb..b89f086505 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -214,14 +214,6 @@ def generate_stair_2d_profile( def update_stair_modifier(context): obj = context.active_object props_kwargs = obj.BIMStairProperties.get_props_kwargs() - - si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - for prop_name in props_kwargs: - if prop_name in ("is_editing", "number_of_treads", "has_top_nib", "stair_type"): - continue - prop_value = props_kwargs[prop_name] - props_kwargs[prop_name] = prop_value * si_conversion - vertices, edges, faces = generate_stair_2d_profile(**props_kwargs) obj = context.object @@ -360,14 +352,7 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "Object has to be IfcStairFlight/IfcStairFlightType to add a stair.") return {"CANCELLED"} - # need to make sure all default props will have correct units - if not props.stair_added_previously: - convert_property_group_from_si( - props, - skip_props=("stair_added_previously", "is_editing", "number_of_treads", "has_top_nib", "stair_type"), - ) - - stair_data = props.get_props_kwargs() + stair_data = props.get_props_kwargs(convert_to_project_units=True) pset = tool.Pset.get_element_pset(element, "BBIM_Stair") if not pset: pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="BBIM_Stair") @@ -393,8 +378,7 @@ class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator): data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data")) props = obj.BIMStairProperties # restore previous settings since editing was canceled - for prop_name in data: - setattr(props, prop_name, data[prop_name]) + props.set_props_kwargs_from_ifc_data(data) update_stair_modifier(context) props.is_editing = -1 @@ -412,7 +396,7 @@ class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) props = obj.BIMStairProperties - data = props.get_props_kwargs() + data = props.get_props_kwargs(convert_to_project_units=True) props.is_editing = -1 update_stair_modifier(context) @@ -436,15 +420,7 @@ class EnableEditingStair(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data")) # required since we could load pset from .ifc and BIMStairProperties won't be set - for prop_name in data: - setattr(props, prop_name, data[prop_name]) - - # need to make sure all props that weren't used before - # will have correct units - skip_props = ("stair_added_previously", "is_editing", "number_of_treads", "has_top_nib", "stair_type") - skip_props += tuple(data.keys()) - convert_property_group_from_si(props, skip_props=skip_props) - + props.set_props_kwargs_from_ifc_data(data) props.is_editing = 1 return {"FINISHED"} From 4768ea23e4fccbcaab797c5ea66a9f5cb97a67da Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 24 May 2023 16:20:20 +0500 Subject: [PATCH 39/65] More descriptive exception for reassign_class #3192 Added it because there was no error handling here anyway and later there was another UnboundLocalError: for attribute in declaration.all_attributes(): UnboundLocalError: local variable 'declaration' referenced before assignment --- src/ifcopenshell-python/ifcopenshell/util/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index 41c717ebe2..a5d545b513 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -68,7 +68,7 @@ def reassign_class(ifc_file, element, new_class): try: declaration = schema.declaration_by_name(new_class) except: - print(f"Class of {element} could not be changed to {new_class} as the class does not exist") + raise Exception(f"Class of {element} could not be changed to {new_class} as the class does not exist") info = element.get_info() From 0cdd9a0cde73937988ff93641dee3668811c7610 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 24 May 2023 17:40:18 +0500 Subject: [PATCH 40/65] Window Modifier - support typing units in props Example - https://i.imgur.com/C99RRs9.png --- .../blenderbim/bim/module/model/prop.py | 75 +++++++------ .../blenderbim/bim/module/model/stair.py | 3 +- .../blenderbim/bim/module/model/window.py | 102 ++++++++---------- src/blenderbim/blenderbim/tool/model.py | 40 ++++++- 4 files changed, 123 insertions(+), 97 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 1a7e0aa370..66c45333cb 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -225,21 +225,13 @@ class BIMStairProperties(PropertyGroup): if not convert_to_project_units: return stair_kwargs - si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - for prop_name in stair_kwargs: - if prop_name in self.non_si_units_props: - continue - prop_value = stair_kwargs[prop_name] - stair_kwargs[prop_name] = prop_value / si_conversion + stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props) return stair_kwargs def set_props_kwargs_from_ifc_data(self, kwargs): - si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props) for prop_name in kwargs: - prop_value = kwargs[prop_name] - if prop_name not in self.non_si_units_props: - prop_value = prop_value * si_conversion - setattr(self, prop_name, prop_value) + setattr(self, prop_name, kwargs[prop_name]) class BIMSverchokProperties(PropertyGroup): @@ -248,15 +240,13 @@ class BIMSverchokProperties(PropertyGroup): def window_type_prop_update(self, context): number_of_panels, panels_data = self.window_types_panels[self.window_type] - - si_coversion = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - panels_data = [[v * si_coversion for v in data] for data in panels_data] self.first_mullion_offset, self.second_mullion_offset = panels_data[0] self.first_transom_offset, self.second_transom_offset = panels_data[1] # default prop values are in mm and converted later class BIMWindowProperties(PropertyGroup): + non_si_units_props = ("is_editing", "window_type", "window_added_previously") window_types = ( ("SINGLE_PANEL", "SINGLE_PANEL", ""), ("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""), @@ -289,50 +279,63 @@ class BIMWindowProperties(PropertyGroup): window_type: bpy.props.EnumProperty( name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update ) - overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9) - overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6) + overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9, subtype="DISTANCE") + overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6, subtype="DISTANCE") # lining properties - lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050) - lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050) - lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050) - lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025) - lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025) - mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050) + lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE") + lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE") + lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050, subtype="DISTANCE") + lining_to_panel_offset_x: bpy.props.FloatProperty( + name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE" + ) + lining_to_panel_offset_y: bpy.props.FloatProperty( + name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE" + ) + mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050, subtype="DISTANCE") first_mullion_offset: bpy.props.FloatProperty( name="First Mullion Offset", description="Distance from the first lining to the first mullion center", default=0.3, + subtype="DISTANCE", ) second_mullion_offset: bpy.props.FloatProperty( name="Second Mullion Offset", description="Distance from the first lining to the second mullion center", default=0.45, + subtype="DISTANCE", ) - transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050) + transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050, subtype="DISTANCE") first_transom_offset: bpy.props.FloatProperty( name="First Transom Offset", description="Distance from the first lining to the first transom center", default=0.3, + subtype="DISTANCE", ) second_transom_offset: bpy.props.FloatProperty( name="Second Transom Offset", description="Distance from the first lining to the second transom center", default=0.6, + subtype="DISTANCE", ) # panel properties - frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3) - frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3) + frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3, subtype="TRANSLATION") + frame_thickness: bpy.props.FloatVectorProperty( + name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION" + ) - def get_general_kwargs(self): - return { + def get_general_kwargs(self, convert_to_project_units=False): + kwargs = { "window_type": self.window_type, "overall_height": self.overall_height, "overall_width": self.overall_width, } + if not convert_to_project_units: + return kwargs + return tool.Model.convert_data_to_project_units(kwargs, ["window_type"]) - def get_lining_kwargs(self): + def get_lining_kwargs(self, convert_to_project_units=False): kwargs = { "lining_depth": self.lining_depth, "lining_thickness": self.lining_thickness, @@ -369,13 +372,23 @@ class BIMWindowProperties(PropertyGroup): if self.window_type in ("TRIPLE_PANEL_HORIZONTAL",): kwargs["second_transom_offset"] = self.second_transom_offset - return kwargs + if not convert_to_project_units: + return kwargs + return tool.Model.convert_data_to_project_units(kwargs) - def get_panel_kwargs(self): - return { + def get_panel_kwargs(self, convert_to_project_units=False): + kwargs = { "frame_depth": self.frame_depth, "frame_thickness": self.frame_thickness, } + if not convert_to_project_units: + return kwargs + return tool.Model.convert_data_to_project_units(kwargs) + + def set_props_kwargs_from_ifc_data(self, kwargs): + kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props) + for prop_name in kwargs: + setattr(self, prop_name, kwargs[prop_name]) class BIMDoorProperties(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index b89f086505..3868743785 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -18,7 +18,7 @@ import bpy from bpy.types import Operator -from bpy.props import FloatProperty, IntProperty, BoolProperty +from bpy.props import FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add import bmesh @@ -28,7 +28,6 @@ import ifcopenshell from ifcopenshell.util.shape_builder import V, ShapeBuilder import blenderbim import blenderbim.tool as tool -from blenderbim.bim.helper import convert_property_group_from_si from mathutils import Vector from pprint import pprint diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index a9bcb7a153..e3aeccfc4e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -27,11 +27,7 @@ import blenderbim.core.root import blenderbim.core.geometry from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS from ifcopenshell.util.shape_builder import V -from bpy.types import Operator -from bpy.props import FloatProperty, IntProperty, BoolProperty from bmesh.types import BMVert -from blenderbim.bim.helper import convert_property_group_from_si -from blenderbim.bim.ifc import IfcStore from mathutils import Vector @@ -105,31 +101,32 @@ def update_window_modifier_representation(context): element = tool.Ifc.get_entity(obj) props = obj.BIMWindowProperties ifc_file = tool.Ifc.get() + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) representation_data = { "partition_type": props.window_type, - "overall_height": props.overall_height, - "overall_width": props.overall_width, + "overall_height": props.overall_height / si_conversion, + "overall_width": props.overall_width / si_conversion, "lining_properties": { - "LiningDepth": props.lining_depth, - "LiningThickness": props.lining_thickness, - "LiningOffset": props.lining_offset, - "LiningToPanelOffsetX": props.lining_to_panel_offset_x, - "LiningToPanelOffsetY": props.lining_to_panel_offset_y, - "MullionThickness": props.mullion_thickness, - "FirstMullionOffset": props.first_mullion_offset, - "SecondMullionOffset": props.second_mullion_offset, - "TransomThickness": props.transom_thickness, - "FirstTransomOffset": props.first_transom_offset, - "SecondTransomOffset": props.second_transom_offset, + "LiningDepth": props.lining_depth / si_conversion, + "LiningThickness": props.lining_thickness / si_conversion, + "LiningOffset": props.lining_offset / si_conversion, + "LiningToPanelOffsetX": props.lining_to_panel_offset_x / si_conversion, + "LiningToPanelOffsetY": props.lining_to_panel_offset_y / si_conversion, + "MullionThickness": props.mullion_thickness / si_conversion, + "FirstMullionOffset": props.first_mullion_offset / si_conversion, + "SecondMullionOffset": props.second_mullion_offset / si_conversion, + "TransomThickness": props.transom_thickness / si_conversion, + "FirstTransomOffset": props.first_transom_offset / si_conversion, + "SecondTransomOffset": props.second_transom_offset / si_conversion, }, "panel_properties": [], } number_of_panels, panels_data = props.window_types_panels[props.window_type] for panel_i in range(number_of_panels): panel_data = { - "FrameDepth": props.frame_depth[panel_i], - "FrameThickness": props.frame_thickness[panel_i], + "FrameDepth": props.frame_depth[panel_i] / si_conversion, + "FrameThickness": props.frame_thickness[panel_i] / si_conversion, } representation_data["panel_properties"].append(panel_data) @@ -175,10 +172,10 @@ def update_window_modifier_representation(context): # occurences attributes occurences = tool.Ifc.get_all_element_occurences(element) for occurence in occurences: - occurence.OverallWidth = props.overall_width - occurence.OverallHeight = props.overall_height + occurence.OverallWidth = props.overall_width / si_conversion + occurence.OverallHeight = props.overall_height / si_conversion - update_simple_openings(element, props.overall_width, props.overall_height) + update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion) def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()): @@ -291,27 +288,26 @@ def create_bm_window( def update_window_modifier_bmesh(context): obj = context.object props = obj.BIMWindowProperties - si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type] accumulated_height = [0] * len(panel_schema[0]) built_panels = [] - overall_width = props.overall_width * si_conversion - lining_depth = props.lining_depth * si_conversion - overall_height = props.overall_height * si_conversion - lining_to_panel_offset_x = props.lining_to_panel_offset_x * si_conversion - lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion - lining_thickness = props.lining_thickness * si_conversion - lining_offset = props.lining_offset * si_conversion + overall_width = props.overall_width + lining_depth = props.lining_depth + overall_height = props.overall_height + lining_to_panel_offset_x = props.lining_to_panel_offset_x + lining_to_panel_offset_y = props.lining_to_panel_offset_y + lining_thickness = props.lining_thickness + lining_offset = props.lining_offset - mullion_thickness = props.mullion_thickness * si_conversion / 2 - first_mullion_offset = props.first_mullion_offset * si_conversion - second_mullion_offset = props.second_mullion_offset * si_conversion - transom_thickness = props.transom_thickness * si_conversion / 2 - first_transom_offset = props.first_transom_offset * si_conversion - second_transom_offset = props.second_transom_offset * si_conversion + mullion_thickness = props.mullion_thickness / 2 + first_mullion_offset = props.first_mullion_offset + second_mullion_offset = props.second_mullion_offset + transom_thickness = props.transom_thickness / 2 + first_transom_offset = props.first_transom_offset + second_transom_offset = props.second_transom_offset - glass_thickness = 0.01 * si_conversion + glass_thickness = 0.01 bm = bmesh.new() panel_schema = list(reversed(panel_schema)) @@ -349,8 +345,8 @@ def update_window_modifier_bmesh(context): accumulated_width += panel_width continue - frame_depth = props.frame_depth[panel_i] * si_conversion - frame_thickness = props.frame_thickness[panel_i] * si_conversion + frame_depth = props.frame_depth[panel_i] + frame_thickness = props.frame_thickness[panel_i] # add window window_lining_size = V( @@ -456,13 +452,9 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "Object has to be IfcWindow/IfcWindowType type to add a window.") return {"CANCELLED"} - # need to make sure all default props will have correct units - if not props.window_added_previously: - convert_property_group_from_si(props, skip_props=("is_editing", "window_type", "window_added_previously")) - - window_data = props.get_general_kwargs() - lining_props = props.get_lining_kwargs() - panel_props = props.get_panel_kwargs() + window_data = props.get_general_kwargs(convert_to_project_units=True) + lining_props = props.get_lining_kwargs(convert_to_project_units=True) + panel_props = props.get_panel_kwargs(convert_to_project_units=True) window_data["lining_properties"] = lining_props window_data["panel_properties"] = panel_props @@ -490,8 +482,7 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data")) props = obj.BIMWindowProperties - for prop_name in data: - setattr(props, prop_name, data[prop_name]) + props.set_props_kwargs_from_ifc_data(data) body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") blenderbim.core.geometry.switch_representation( @@ -518,9 +509,9 @@ class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) props = obj.BIMWindowProperties - window_data = props.get_general_kwargs() - lining_props = props.get_lining_kwargs() - panel_props = props.get_panel_kwargs() + window_data = props.get_general_kwargs(convert_to_project_units=True) + lining_props = props.get_lining_kwargs(convert_to_project_units=True) + panel_props = props.get_panel_kwargs(convert_to_project_units=True) window_data["lining_properties"] = lining_props window_data["panel_properties"] = panel_props @@ -549,14 +540,7 @@ class EnableEditingWindow(bpy.types.Operator, tool.Ifc.Operator): data.update(data.pop("panel_properties")) # required since we could load pset from .ifc and BIMWindowProperties won't be set - for prop_name in data: - setattr(props, prop_name, data[prop_name]) - - # need to make sure all props that weren't used before - # will have correct units - skip_props = ("is_editing", "window_type", "window_added_previously") - skip_props += tuple(data.keys()) - convert_property_group_from_si(props, skip_props=skip_props) + props.set_props_kwargs_from_ifc_data(data) props.is_editing = 1 return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index a8d8b4dc93..7c27eafc74 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -28,6 +28,7 @@ import blenderbim.core.geometry as geometry from mathutils import Matrix, Vector from blenderbim.bim import import_ifc from blenderbim.bim.module.geometry.helper import Helper +import collections class Model(blenderbim.core.tool.Model): @@ -43,6 +44,33 @@ class Model(blenderbim.core.tool.Model): return [v * cls.unit_scale for v in value] return value * cls.unit_scale + @classmethod + def convert_data_to_project_units(cls, data, non_si_props=[]): + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + for prop_name in data: + if prop_name in non_si_props: + continue + prop_value = data[prop_name] + if isinstance(prop_value, collections.abc.Iterable): + data[prop_name] = [v / si_conversion for v in prop_value] + else: + data[prop_name] = prop_value / si_conversion + return data + + @classmethod + def convert_data_to_si_units(cls, data, non_si_props=[]): + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + for prop_name in data: + if prop_name in non_si_props: + continue + prop_value = data[prop_name] + print(prop_name, prop_value) + if isinstance(prop_value, collections.abc.Iterable): + data[prop_name] = [v * si_conversion for v in prop_value] + else: + data[prop_name] = prop_value * si_conversion + return data + @classmethod def export_curve(cls, position, edge_indices, points=None): position_i = position.inverted() @@ -147,11 +175,13 @@ class Model(blenderbim.core.tool.Model): cls.bm.edges.ensure_lookup_table() surface = tool.Ifc.get().createIfcCurveBoundedPlane() - surface.BasisSurface = tool.Ifc.get().createIfcPlane(tool.Ifc.get().createIfcAxis2Placement3D( - tool.Ifc.get().createIfcCartesianPoint([o / cls.unit_scale for o in p1]), - tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]), - tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]), - )) + surface.BasisSurface = tool.Ifc.get().createIfcPlane( + tool.Ifc.get().createIfcAxis2Placement3D( + tool.Ifc.get().createIfcCartesianPoint([o / cls.unit_scale for o in p1]), + tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]), + tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]), + ) + ) if tool.Ifc.get().schema != "IFC2X3": cls.points = cls.export_points(position, indices["points"]) From 8bb4ab91b83ea7349379d59fae12fed3fd3a17f2 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 24 May 2023 18:02:53 +0500 Subject: [PATCH 41/65] Fixed problem with ifccsv set_element_key #3192 Currently "type" attribute in selector returns something like "#71=IfcWallType('20jY5HfLH4LBwMrDEoTdML',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.)" when the code was expecting the class name like "IfcSlab" or "IfcWall". Changed it to "class" atribute that returns just class name. --- src/ifccsv/ifccsv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 8fc4adb5a1..e415f4bad2 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -45,7 +45,7 @@ except: class IfcAttributeSetter: @staticmethod def set_element_key(ifc_file, element, key, value): - if key == "type" and element.is_a() != value: + if key == "class" and element.is_a() != value: return ifcopenshell.util.schema.reassign_class(ifc_file, element, value) if hasattr(element, key): setattr(element, key, value) From 604dfa8cf47afae61d95b4f09fc4f2693504499f Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Wed, 24 May 2023 16:06:33 +0200 Subject: [PATCH 42/65] Fix annoying tooltip when using the eyedropper to copy a type --- src/blenderbim/blenderbim/bim/module/type/operator.py | 4 +--- src/blenderbim/blenderbim/bim/module/type/prop.py | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index a87f25ffb6..b7f825d10f 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -51,8 +51,6 @@ class AssignType(bpy.types.Operator, Operator): ) for obj in related_objects: core.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=type) - oprops = obj.BIMObjectProperties - obj.BIMTypeProperties.relating_type_object = None class UnassignType(bpy.types.Operator): @@ -119,6 +117,7 @@ class EnableEditingType(bpy.types.Operator): def execute(self, context): context.active_object.BIMTypeProperties.is_editing_type = True + context.active_object.BIMTypeProperties.relating_type_object = None return {"FINISHED"} @@ -131,7 +130,6 @@ class DisableEditingType(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object obj.BIMTypeProperties.is_editing_type = False - obj.BIMTypeProperties.relating_type_object = None return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index 5f0afb42aa..7c9775cd79 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -52,10 +52,9 @@ def update_relating_type_class(self, context): def update_relating_type_from_object(self, context): - obj = self.relating_type_object - if obj is None: + if self.relating_type_object is None: return - element = tool.Ifc.get_entity(obj) + element = tool.Ifc.get_entity(self.relating_type_object) if not element: return element_type = ifcopenshell.util.element.get_type(element) @@ -74,7 +73,7 @@ def is_object_class_applicable(self, obj): element_type = ifcopenshell.util.element.get_type(element) if element_type is None: return False - return str(element_type.is_a()) in [r_t_c[0] for r_t_c in TypeData.data["relating_type_classes"]] + return str(element_type.is_a()) in (r_t_c[0] for r_t_c in TypeData.data["relating_type_classes"]) class BIMTypeProperties(PropertyGroup): From 301d4fecf8443e0c793ed7ce4c7d778f11287375 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Wed, 24 May 2023 16:54:57 +0200 Subject: [PATCH 43/65] Remove the cryptic (and slightly annoying) console warning logs when trying to display the type manager when there is no element type in the file --- .../blenderbim/bim/module/model/workspace.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index a658af8316..7366d08e8b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -323,15 +323,15 @@ class BimToolUI: def draw_basic_bim_tool_interface(cls): cls.draw_type_selection_interface() - if cls.props.ifc_class: - box = cls.layout.box() - if AuthoringData.data["type_thumbnail"]: - box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5) - else: - op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH") - op.ifc_class = cls.props.ifc_class - if AuthoringData.data["ifc_classes"]: + if cls.props.ifc_class: + box = cls.layout.box() + if AuthoringData.data["type_thumbnail"]: + box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5) + else: + op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH") + op.ifc_class = cls.props.ifc_class + row = cls.layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_A") From 6c2be8f886028cb09da2441c23b95e6fed0a181c Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 25 May 2023 14:21:39 +0500 Subject: [PATCH 44/65] Procedurally generating list of props for shading styles to cover more props If it will be too much we still have option to exclude some of them --- .../bim/data/assets/shading_styles.json | 742 ++++++++++++++++-- src/blenderbim/blenderbim/bim/handler.py | 1 - .../blenderbim/bim/module/drawing/operator.py | 34 +- .../blenderbim/bim/module/drawing/prop.py | 39 +- 4 files changed, 730 insertions(+), 86 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json b/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json index 01dd9742b3..e89fb84f10 100644 --- a/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json +++ b/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json @@ -1,125 +1,755 @@ { "Technical": { + "render_type": "VIEWPORT", "raster_style": { "bpy.data.worlds[0].color": [ - 1, - 1, - 1 + 1.0, + 1.0, + 1.0 ], + "scene.render.bake_bias": 0.0010000000474974513, + "scene.render.bake_margin": 16, + "scene.render.bake_margin_type": "ADJACENT_FACES", + "scene.render.bake_samples": 256, + "scene.render.bake_type": "NORMALS", + "scene.render.bake_user_scale": 0.0, + "scene.render.border_max_x": 1.0, + "scene.render.border_max_y": 1.0, + "scene.render.border_min_x": 0.0, + "scene.render.border_min_y": 0.0, + "scene.render.dither_intensity": 1.0, "scene.render.engine": "BLENDER_WORKBENCH", "scene.render.film_transparent": false, - "scene.display.shading.show_object_outline": true, - "scene.display.shading.show_cavity": false, - "scene.display.shading.cavity_type": "BOTH", - "scene.display.shading.curvature_ridge_factor": 1, - "scene.display.shading.curvature_valley_factor": 1, + "scene.render.filter_size": 1.5, + "scene.render.fps": 24, + "scene.render.fps_base": 1.0, + "scene.render.frame_map_new": 100, + "scene.render.frame_map_old": 100, + "scene.render.hair_subdiv": 0, + "scene.render.hair_type": "STRAND", + "scene.render.line_thickness": 1.0, + "scene.render.line_thickness_mode": "ABSOLUTE", + "scene.render.metadata_input": "SCENE", + "scene.render.motion_blur_shutter": 0.5, + "scene.render.pixel_aspect_x": 1.0, + "scene.render.pixel_aspect_y": 1.0, + "scene.render.preview_pixel_size": "AUTO", + "scene.render.resolution_percentage": 100, + "scene.render.resolution_x": 1000, + "scene.render.resolution_y": 1000, + "scene.render.sequencer_gl_preview": "SOLID", + "scene.render.simplify_child_particles": 1.0, + "scene.render.simplify_child_particles_render": 1.0, + "scene.render.simplify_gpencil": false, + "scene.render.simplify_gpencil_antialiasing": true, + "scene.render.simplify_gpencil_modifier": true, + "scene.render.simplify_gpencil_onplay": false, + "scene.render.simplify_gpencil_shader_fx": true, + "scene.render.simplify_gpencil_tint": true, + "scene.render.simplify_gpencil_view_fill": true, + "scene.render.simplify_shadows": 1.0, + "scene.render.simplify_shadows_render": 1.0, + "scene.render.simplify_subdivision": 6, + "scene.render.simplify_subdivision_render": 6, + "scene.render.simplify_volumes": 1.0, + "scene.render.stamp_font_size": 12, + "scene.render.stamp_note_text": "", + "scene.render.threads": 20, + "scene.render.threads_mode": "AUTO", + "scene.render.use_bake_clear": true, + "scene.render.use_bake_lores_mesh": false, + "scene.render.use_bake_multires": false, + "scene.render.use_bake_selected_to_active": false, + "scene.render.use_bake_user_scale": false, + "scene.render.use_border": false, + "scene.render.use_compositing": true, + "scene.render.use_crop_to_border": false, + "scene.render.use_file_extension": true, + "scene.render.use_freestyle": false, + "scene.render.use_high_quality_normals": false, + "scene.render.use_lock_interface": false, + "scene.render.use_motion_blur": false, + "scene.render.use_multiview": false, + "scene.render.use_overwrite": true, + "scene.render.use_persistent_data": false, + "scene.render.use_placeholder": false, + "scene.render.use_render_cache": false, + "scene.render.use_sequencer": true, + "scene.render.use_sequencer_override_scene_strip": false, + "scene.render.use_simplify": false, + "scene.render.use_single_layer": false, + "scene.render.use_stamp": false, + "scene.render.use_stamp_camera": true, + "scene.render.use_stamp_date": true, + "scene.render.use_stamp_filename": true, + "scene.render.use_stamp_frame": true, + "scene.render.use_stamp_frame_range": false, + "scene.render.use_stamp_hostname": false, + "scene.render.use_stamp_labels": true, + "scene.render.use_stamp_lens": false, + "scene.render.use_stamp_marker": false, + "scene.render.use_stamp_memory": false, + "scene.render.use_stamp_note": false, + "scene.render.use_stamp_render_time": true, + "scene.render.use_stamp_scene": true, + "scene.render.use_stamp_sequencer_strip": false, + "scene.render.use_stamp_time": true, + "scene.render.views_format": "STEREO_3D", + "scene.view_settings.exposure": 0.0, + "scene.view_settings.gamma": 1.0, + "scene.view_settings.look": "None", + "scene.view_settings.use_curve_mapping": false, "scene.view_settings.view_transform": "Standard", - "scene.display.shading.light": "FLAT", - "scene.display.shading.color_type": "SINGLE", - "scene.display.shading.single_color": [ - 1, - 1, - 1 + "scene.display.shading.aov_name": "", + "scene.display.shading.background_color": [ + 0.0, + 0.0, + 0.0 ], - "scene.display.shading.show_shadows": false, + "scene.display.shading.background_type": "THEME", + "scene.display.shading.cavity_ridge_factor": 1.0, + "scene.display.shading.cavity_type": "BOTH", + "scene.display.shading.cavity_valley_factor": 1.0, + "scene.display.shading.color_type": "SINGLE", + "scene.display.shading.curvature_ridge_factor": 1.0, + "scene.display.shading.curvature_valley_factor": 1.0, + "scene.display.shading.light": "FLAT", + "scene.display.shading.object_outline_color": [ + 0.0, + 0.0, + 0.0 + ], + "scene.display.shading.render_pass": "COMBINED", "scene.display.shading.shadow_intensity": 0.5, + "scene.display.shading.show_backface_culling": false, + "scene.display.shading.show_cavity": false, + "scene.display.shading.show_object_outline": true, + "scene.display.shading.show_shadows": false, + "scene.display.shading.show_specular_highlight": true, + "scene.display.shading.show_xray": false, + "scene.display.shading.show_xray_wireframe": false, + "scene.display.shading.single_color": [ + 1.0, + 1.0, + 1.0 + ], + "scene.display.shading.studio_light": "Default", + "scene.display.shading.studiolight_background_alpha": 0.0, + "scene.display.shading.studiolight_background_blur": 0.0, + "scene.display.shading.studiolight_intensity": 0.0, + "scene.display.shading.studiolight_rotate_z": 0.0, + "scene.display.shading.type": "SOLID", + "scene.display.shading.use_compositor": "DISABLED", + "scene.display.shading.use_dof": false, + "scene.display.shading.use_scene_lights": false, + "scene.display.shading.use_scene_lights_render": false, + "scene.display.shading.use_scene_world": false, + "scene.display.shading.use_scene_world_render": false, + "scene.display.shading.use_studiolight_view_rotation": true, + "scene.display.shading.use_world_space_lighting": false, + "scene.display.shading.wireframe_color_type": "MATERIAL", + "scene.display.shading.xray_alpha": 0.5, + "scene.display.shading.xray_alpha_wireframe": 0.0, "scene.display.light_direction": [ 0.5, 0.5, 0.5 ], - "scene.view_settings.use_curve_mapping": false, - "space.overlay.show_wireframes": true, - "space.overlay.wireframe_threshold": 0, - "space.overlay.show_floor": false, + "scene.display.matcap_ssao_attenuation": 1.0, + "scene.display.matcap_ssao_distance": 0.20000000298023224, + "scene.display.matcap_ssao_samples": 16, + "scene.display.render_aa": "8", + "scene.display.shadow_focus": 0.0, + "scene.display.shadow_shift": 0.10000000149011612, + "scene.display.viewport_aa": "FXAA", + "space.overlay.backwire_opacity": 0.5, + "space.overlay.bone_wire_alpha": 1.0, + "space.overlay.display_handle": "SELECTED", + "space.overlay.fade_inactive_alpha": 0.4000000059604645, + "space.overlay.gpencil_fade_layer": 0.5, + "space.overlay.gpencil_fade_objects": 0.5, + "space.overlay.gpencil_grid_opacity": 0.5, + "space.overlay.gpencil_vertex_paint_opacity": 1.0, + "space.overlay.grid_lines": 16, + "space.overlay.grid_scale": 1.0, + "space.overlay.grid_subdivisions": 10, + "space.overlay.normals_constant_screen_size": 7.0, + "space.overlay.normals_length": 0.10000000149011612, + "space.overlay.sculpt_curves_cage_opacity": 0.5, + "space.overlay.sculpt_mode_face_sets_opacity": 1.0, + "space.overlay.sculpt_mode_mask_opacity": 0.75, + "space.overlay.show_annotation": true, "space.overlay.show_axis_x": false, "space.overlay.show_axis_y": false, "space.overlay.show_axis_z": false, + "space.overlay.show_bones": true, + "space.overlay.show_cursor": true, + "space.overlay.show_curve_normals": false, + "space.overlay.show_edge_bevel_weight": true, + "space.overlay.show_edge_crease": true, + "space.overlay.show_edge_seams": true, + "space.overlay.show_edge_sharp": true, + "space.overlay.show_edges": false, + "space.overlay.show_extra_edge_angle": false, + "space.overlay.show_extra_edge_length": false, + "space.overlay.show_extra_face_angle": false, + "space.overlay.show_extra_face_area": false, + "space.overlay.show_extra_indices": false, + "space.overlay.show_extras": true, + "space.overlay.show_face_center": false, + "space.overlay.show_face_normals": false, + "space.overlay.show_face_orientation": false, + "space.overlay.show_faces": true, + "space.overlay.show_fade_inactive": false, + "space.overlay.show_floor": false, + "space.overlay.show_freestyle_edge_marks": true, + "space.overlay.show_freestyle_face_marks": true, + "space.overlay.show_look_dev": false, + "space.overlay.show_motion_paths": true, "space.overlay.show_object_origins": false, - "space.overlay.show_relationship_lines": false - }, - "render_type": "VIEWPORT" + "space.overlay.show_object_origins_all": false, + "space.overlay.show_occlude_wire": false, + "space.overlay.show_onion_skins": false, + "space.overlay.show_ortho_grid": true, + "space.overlay.show_outline_selected": true, + "space.overlay.show_overlays": true, + "space.overlay.show_paint_wire": false, + "space.overlay.show_relationship_lines": false, + "space.overlay.show_sculpt_curves_cage": true, + "space.overlay.show_sculpt_face_sets": true, + "space.overlay.show_sculpt_mask": true, + "space.overlay.show_split_normals": false, + "space.overlay.show_stats": false, + "space.overlay.show_statvis": false, + "space.overlay.show_text": true, + "space.overlay.show_vertex_normals": false, + "space.overlay.show_viewer_attribute": true, + "space.overlay.show_weight": false, + "space.overlay.show_wireframes": true, + "space.overlay.show_wpaint_contours": false, + "space.overlay.show_xray_bone": false, + "space.overlay.texture_paint_mode_opacity": 1.0, + "space.overlay.use_debug_freeze_view_culling": false, + "space.overlay.use_gpencil_canvas_xray": false, + "space.overlay.use_gpencil_edit_lines": true, + "space.overlay.use_gpencil_fade_gp_objects": false, + "space.overlay.use_gpencil_fade_layers": false, + "space.overlay.use_gpencil_fade_objects": false, + "space.overlay.use_gpencil_grid": false, + "space.overlay.use_gpencil_multiedit_line_only": false, + "space.overlay.use_gpencil_onion_skin": false, + "space.overlay.use_gpencil_show_directions": false, + "space.overlay.use_gpencil_show_material_name": false, + "space.overlay.use_normals_constant_screen_size": false, + "space.overlay.vertex_opacity": 1.0, + "space.overlay.vertex_paint_mode_opacity": 1.0, + "space.overlay.viewer_attribute_opacity": 1.0, + "space.overlay.weight_paint_mode_opacity": 1.0, + "space.overlay.wireframe_opacity": 1.0, + "space.overlay.wireframe_threshold": 0.0, + "space.overlay.xray_alpha_bone": 0.0 + } }, "Shaded": { + "render_type": "VIEWPORT", "raster_style": { "bpy.data.worlds[0].color": [ - 1, - 1, - 1 + 1.0, + 1.0, + 1.0 ], + "scene.render.bake_bias": 0.0010000000474974513, + "scene.render.bake_margin": 16, + "scene.render.bake_margin_type": "ADJACENT_FACES", + "scene.render.bake_samples": 256, + "scene.render.bake_type": "NORMALS", + "scene.render.bake_user_scale": 0.0, + "scene.render.border_max_x": 1.0, + "scene.render.border_max_y": 1.0, + "scene.render.border_min_x": 0.0, + "scene.render.border_min_y": 0.0, + "scene.render.dither_intensity": 1.0, "scene.render.engine": "BLENDER_WORKBENCH", "scene.render.film_transparent": false, - "scene.display.shading.show_object_outline": true, - "scene.display.shading.show_cavity": true, - "scene.display.shading.cavity_type": "BOTH", - "scene.display.shading.curvature_ridge_factor": 1, - "scene.display.shading.curvature_valley_factor": 1, + "scene.render.filter_size": 1.5, + "scene.render.fps": 24, + "scene.render.fps_base": 1.0, + "scene.render.frame_map_new": 100, + "scene.render.frame_map_old": 100, + "scene.render.hair_subdiv": 0, + "scene.render.hair_type": "STRAND", + "scene.render.line_thickness": 1.0, + "scene.render.line_thickness_mode": "ABSOLUTE", + "scene.render.metadata_input": "SCENE", + "scene.render.motion_blur_shutter": 0.5, + "scene.render.pixel_aspect_x": 1.0, + "scene.render.pixel_aspect_y": 1.0, + "scene.render.preview_pixel_size": "AUTO", + "scene.render.resolution_percentage": 100, + "scene.render.resolution_x": 1000, + "scene.render.resolution_y": 1000, + "scene.render.sequencer_gl_preview": "SOLID", + "scene.render.simplify_child_particles": 1.0, + "scene.render.simplify_child_particles_render": 1.0, + "scene.render.simplify_gpencil": false, + "scene.render.simplify_gpencil_antialiasing": true, + "scene.render.simplify_gpencil_modifier": true, + "scene.render.simplify_gpencil_onplay": false, + "scene.render.simplify_gpencil_shader_fx": true, + "scene.render.simplify_gpencil_tint": true, + "scene.render.simplify_gpencil_view_fill": true, + "scene.render.simplify_shadows": 1.0, + "scene.render.simplify_shadows_render": 1.0, + "scene.render.simplify_subdivision": 6, + "scene.render.simplify_subdivision_render": 6, + "scene.render.simplify_volumes": 1.0, + "scene.render.stamp_font_size": 12, + "scene.render.stamp_note_text": "", + "scene.render.threads": 20, + "scene.render.threads_mode": "AUTO", + "scene.render.use_bake_clear": true, + "scene.render.use_bake_lores_mesh": false, + "scene.render.use_bake_multires": false, + "scene.render.use_bake_selected_to_active": false, + "scene.render.use_bake_user_scale": false, + "scene.render.use_border": false, + "scene.render.use_compositing": true, + "scene.render.use_crop_to_border": false, + "scene.render.use_file_extension": true, + "scene.render.use_freestyle": false, + "scene.render.use_high_quality_normals": false, + "scene.render.use_lock_interface": false, + "scene.render.use_motion_blur": false, + "scene.render.use_multiview": false, + "scene.render.use_overwrite": true, + "scene.render.use_persistent_data": false, + "scene.render.use_placeholder": false, + "scene.render.use_render_cache": false, + "scene.render.use_sequencer": true, + "scene.render.use_sequencer_override_scene_strip": false, + "scene.render.use_simplify": false, + "scene.render.use_single_layer": false, + "scene.render.use_stamp": false, + "scene.render.use_stamp_camera": true, + "scene.render.use_stamp_date": true, + "scene.render.use_stamp_filename": true, + "scene.render.use_stamp_frame": true, + "scene.render.use_stamp_frame_range": false, + "scene.render.use_stamp_hostname": false, + "scene.render.use_stamp_labels": true, + "scene.render.use_stamp_lens": false, + "scene.render.use_stamp_marker": false, + "scene.render.use_stamp_memory": false, + "scene.render.use_stamp_note": false, + "scene.render.use_stamp_render_time": true, + "scene.render.use_stamp_scene": true, + "scene.render.use_stamp_sequencer_strip": false, + "scene.render.use_stamp_time": true, + "scene.render.views_format": "STEREO_3D", + "scene.view_settings.exposure": 0.0, + "scene.view_settings.gamma": 1.0, + "scene.view_settings.look": "None", + "scene.view_settings.use_curve_mapping": false, "scene.view_settings.view_transform": "Standard", - "scene.display.shading.light": "STUDIO", - "scene.display.shading.color_type": "MATERIAL", - "scene.display.shading.single_color": [ - 1, - 1, - 1 + "scene.display.shading.aov_name": "", + "scene.display.shading.background_color": [ + 0.0, + 0.0, + 0.0 ], - "scene.display.shading.show_shadows": true, + "scene.display.shading.background_type": "THEME", + "scene.display.shading.cavity_ridge_factor": 1.0, + "scene.display.shading.cavity_type": "BOTH", + "scene.display.shading.cavity_valley_factor": 1.0, + "scene.display.shading.color_type": "MATERIAL", + "scene.display.shading.curvature_ridge_factor": 1.0, + "scene.display.shading.curvature_valley_factor": 1.0, + "scene.display.shading.light": "STUDIO", + "scene.display.shading.object_outline_color": [ + 0.0, + 0.0, + 0.0 + ], + "scene.display.shading.render_pass": "COMBINED", "scene.display.shading.shadow_intensity": 0.5, + "scene.display.shading.show_backface_culling": false, + "scene.display.shading.show_cavity": true, + "scene.display.shading.show_object_outline": true, + "scene.display.shading.show_shadows": true, + "scene.display.shading.show_specular_highlight": true, + "scene.display.shading.show_xray": false, + "scene.display.shading.show_xray_wireframe": false, + "scene.display.shading.single_color": [ + 1.0, + 1.0, + 1.0 + ], + "scene.display.shading.studio_light": "Default", + "scene.display.shading.studiolight_background_alpha": 0.0, + "scene.display.shading.studiolight_background_blur": 0.0, + "scene.display.shading.studiolight_intensity": 0.0, + "scene.display.shading.studiolight_rotate_z": 0.0, + "scene.display.shading.type": "SOLID", + "scene.display.shading.use_compositor": "DISABLED", + "scene.display.shading.use_dof": false, + "scene.display.shading.use_scene_lights": false, + "scene.display.shading.use_scene_lights_render": false, + "scene.display.shading.use_scene_world": false, + "scene.display.shading.use_scene_world_render": false, + "scene.display.shading.use_studiolight_view_rotation": true, + "scene.display.shading.use_world_space_lighting": false, + "scene.display.shading.wireframe_color_type": "MATERIAL", + "scene.display.shading.xray_alpha": 0.5, + "scene.display.shading.xray_alpha_wireframe": 0.0, "scene.display.light_direction": [ 0.5, 0.5, 0.5 ], - "scene.view_settings.use_curve_mapping": false, - "space.overlay.show_wireframes": false, - "space.overlay.wireframe_threshold": 0, - "space.overlay.show_floor": false, + "scene.display.matcap_ssao_attenuation": 1.0, + "scene.display.matcap_ssao_distance": 0.20000000298023224, + "scene.display.matcap_ssao_samples": 16, + "scene.display.render_aa": "8", + "scene.display.shadow_focus": 0.0, + "scene.display.shadow_shift": 0.10000000149011612, + "scene.display.viewport_aa": "FXAA", + "space.overlay.backwire_opacity": 0.5, + "space.overlay.bone_wire_alpha": 1.0, + "space.overlay.display_handle": "SELECTED", + "space.overlay.fade_inactive_alpha": 0.4000000059604645, + "space.overlay.gpencil_fade_layer": 0.5, + "space.overlay.gpencil_fade_objects": 0.5, + "space.overlay.gpencil_grid_opacity": 0.5, + "space.overlay.gpencil_vertex_paint_opacity": 1.0, + "space.overlay.grid_lines": 16, + "space.overlay.grid_scale": 1.0, + "space.overlay.grid_subdivisions": 10, + "space.overlay.normals_constant_screen_size": 7.0, + "space.overlay.normals_length": 0.10000000149011612, + "space.overlay.sculpt_curves_cage_opacity": 0.5, + "space.overlay.sculpt_mode_face_sets_opacity": 1.0, + "space.overlay.sculpt_mode_mask_opacity": 0.75, + "space.overlay.show_annotation": true, "space.overlay.show_axis_x": false, "space.overlay.show_axis_y": false, "space.overlay.show_axis_z": false, + "space.overlay.show_bones": true, + "space.overlay.show_cursor": true, + "space.overlay.show_curve_normals": false, + "space.overlay.show_edge_bevel_weight": true, + "space.overlay.show_edge_crease": true, + "space.overlay.show_edge_seams": true, + "space.overlay.show_edge_sharp": true, + "space.overlay.show_edges": false, + "space.overlay.show_extra_edge_angle": false, + "space.overlay.show_extra_edge_length": false, + "space.overlay.show_extra_face_angle": false, + "space.overlay.show_extra_face_area": false, + "space.overlay.show_extra_indices": false, + "space.overlay.show_extras": true, + "space.overlay.show_face_center": false, + "space.overlay.show_face_normals": false, + "space.overlay.show_face_orientation": false, + "space.overlay.show_faces": true, + "space.overlay.show_fade_inactive": false, + "space.overlay.show_floor": false, + "space.overlay.show_freestyle_edge_marks": true, + "space.overlay.show_freestyle_face_marks": true, + "space.overlay.show_look_dev": false, + "space.overlay.show_motion_paths": true, "space.overlay.show_object_origins": false, - "space.overlay.show_relationship_lines": false - }, - "render_type": "VIEWPORT" + "space.overlay.show_object_origins_all": false, + "space.overlay.show_occlude_wire": false, + "space.overlay.show_onion_skins": false, + "space.overlay.show_ortho_grid": true, + "space.overlay.show_outline_selected": true, + "space.overlay.show_overlays": true, + "space.overlay.show_paint_wire": false, + "space.overlay.show_relationship_lines": false, + "space.overlay.show_sculpt_curves_cage": true, + "space.overlay.show_sculpt_face_sets": true, + "space.overlay.show_sculpt_mask": true, + "space.overlay.show_split_normals": false, + "space.overlay.show_stats": false, + "space.overlay.show_statvis": false, + "space.overlay.show_text": true, + "space.overlay.show_vertex_normals": false, + "space.overlay.show_viewer_attribute": true, + "space.overlay.show_weight": false, + "space.overlay.show_wireframes": false, + "space.overlay.show_wpaint_contours": false, + "space.overlay.show_xray_bone": false, + "space.overlay.texture_paint_mode_opacity": 1.0, + "space.overlay.use_debug_freeze_view_culling": false, + "space.overlay.use_gpencil_canvas_xray": false, + "space.overlay.use_gpencil_edit_lines": true, + "space.overlay.use_gpencil_fade_gp_objects": false, + "space.overlay.use_gpencil_fade_layers": false, + "space.overlay.use_gpencil_fade_objects": false, + "space.overlay.use_gpencil_grid": false, + "space.overlay.use_gpencil_multiedit_line_only": false, + "space.overlay.use_gpencil_onion_skin": false, + "space.overlay.use_gpencil_show_directions": false, + "space.overlay.use_gpencil_show_material_name": false, + "space.overlay.use_normals_constant_screen_size": false, + "space.overlay.vertex_opacity": 1.0, + "space.overlay.vertex_paint_mode_opacity": 1.0, + "space.overlay.viewer_attribute_opacity": 1.0, + "space.overlay.weight_paint_mode_opacity": 1.0, + "space.overlay.wireframe_opacity": 1.0, + "space.overlay.wireframe_threshold": 0.0, + "space.overlay.xray_alpha_bone": 0.0 + } }, "Blender Default": { + "render_type": "VIEWPORT", "raster_style": { "bpy.data.worlds[0].color": [ 0.05087608844041824, 0.05087608844041824, 0.05087608844041824 ], + "scene.render.bake_bias": 0.0010000000474974513, + "scene.render.bake_margin": 16, + "scene.render.bake_margin_type": "ADJACENT_FACES", + "scene.render.bake_samples": 256, + "scene.render.bake_type": "NORMALS", + "scene.render.bake_user_scale": 0.0, + "scene.render.border_max_x": 1.0, + "scene.render.border_max_y": 1.0, + "scene.render.border_min_x": 0.0, + "scene.render.border_min_y": 0.0, + "scene.render.dither_intensity": 1.0, "scene.render.engine": "BLENDER_EEVEE", "scene.render.film_transparent": false, + "scene.render.filter_size": 1.5, + "scene.render.fps": 24, + "scene.render.fps_base": 1.0, + "scene.render.frame_map_new": 100, + "scene.render.frame_map_old": 100, + "scene.render.hair_subdiv": 0, + "scene.render.hair_type": "STRAND", + "scene.render.line_thickness": 1.0, + "scene.render.line_thickness_mode": "ABSOLUTE", + "scene.render.metadata_input": "SCENE", + "scene.render.motion_blur_shutter": 0.5, + "scene.render.pixel_aspect_x": 1.0, + "scene.render.pixel_aspect_y": 1.0, + "scene.render.preview_pixel_size": "AUTO", + "scene.render.resolution_percentage": 100, + "scene.render.resolution_x": 1000, + "scene.render.resolution_y": 1000, + "scene.render.sequencer_gl_preview": "SOLID", + "scene.render.simplify_child_particles": 1.0, + "scene.render.simplify_child_particles_render": 1.0, + "scene.render.simplify_gpencil": false, + "scene.render.simplify_gpencil_antialiasing": true, + "scene.render.simplify_gpencil_modifier": true, + "scene.render.simplify_gpencil_onplay": false, + "scene.render.simplify_gpencil_shader_fx": true, + "scene.render.simplify_gpencil_tint": true, + "scene.render.simplify_gpencil_view_fill": true, + "scene.render.simplify_shadows": 1.0, + "scene.render.simplify_shadows_render": 1.0, + "scene.render.simplify_subdivision": 6, + "scene.render.simplify_subdivision_render": 6, + "scene.render.simplify_volumes": 1.0, + "scene.render.stamp_font_size": 12, + "scene.render.stamp_note_text": "", + "scene.render.threads": 20, + "scene.render.threads_mode": "AUTO", + "scene.render.use_bake_clear": true, + "scene.render.use_bake_lores_mesh": false, + "scene.render.use_bake_multires": false, + "scene.render.use_bake_selected_to_active": false, + "scene.render.use_bake_user_scale": false, + "scene.render.use_border": false, + "scene.render.use_compositing": true, + "scene.render.use_crop_to_border": false, + "scene.render.use_file_extension": true, + "scene.render.use_freestyle": false, + "scene.render.use_high_quality_normals": false, + "scene.render.use_lock_interface": false, + "scene.render.use_motion_blur": false, + "scene.render.use_multiview": false, + "scene.render.use_overwrite": true, + "scene.render.use_persistent_data": false, + "scene.render.use_placeholder": false, + "scene.render.use_render_cache": false, + "scene.render.use_sequencer": true, + "scene.render.use_sequencer_override_scene_strip": false, + "scene.render.use_simplify": false, + "scene.render.use_single_layer": false, + "scene.render.use_stamp": false, + "scene.render.use_stamp_camera": true, + "scene.render.use_stamp_date": true, + "scene.render.use_stamp_filename": true, + "scene.render.use_stamp_frame": true, + "scene.render.use_stamp_frame_range": false, + "scene.render.use_stamp_hostname": false, + "scene.render.use_stamp_labels": true, + "scene.render.use_stamp_lens": false, + "scene.render.use_stamp_marker": false, + "scene.render.use_stamp_memory": false, + "scene.render.use_stamp_note": false, + "scene.render.use_stamp_render_time": true, + "scene.render.use_stamp_scene": true, + "scene.render.use_stamp_sequencer_strip": false, + "scene.render.use_stamp_time": true, + "scene.render.views_format": "STEREO_3D", + "scene.view_settings.exposure": 0.0, + "scene.view_settings.gamma": 1.0, + "scene.view_settings.look": "None", + "scene.view_settings.use_curve_mapping": false, "scene.view_settings.view_transform": "Filmic", - "scene.display.shading.show_object_outline": false, - "scene.display.shading.show_cavity": false, + "scene.display.shading.aov_name": "", + "scene.display.shading.background_color": [ + 0.0, + 0.0, + 0.0 + ], + "scene.display.shading.background_type": "THEME", + "scene.display.shading.cavity_ridge_factor": 1.0, "scene.display.shading.cavity_type": "WORLD", + "scene.display.shading.cavity_valley_factor": 1.0, + "scene.display.shading.color_type": "MATERIAL", "scene.display.shading.curvature_ridge_factor": 0.0, "scene.display.shading.curvature_valley_factor": 0.0, "scene.display.shading.light": "STUDIO", - "scene.display.shading.color_type": "MATERIAL", + "scene.display.shading.object_outline_color": [ + 0.0, + 0.0, + 0.0 + ], + "scene.display.shading.render_pass": "COMBINED", + "scene.display.shading.shadow_intensity": 0.5, + "scene.display.shading.show_backface_culling": false, + "scene.display.shading.show_cavity": false, + "scene.display.shading.show_object_outline": false, + "scene.display.shading.show_shadows": false, + "scene.display.shading.show_specular_highlight": true, + "scene.display.shading.show_xray": false, + "scene.display.shading.show_xray_wireframe": false, "scene.display.shading.single_color": [ 0.800000011920929, 0.800000011920929, 0.800000011920929 ], - "scene.display.shading.show_shadows": false, - "scene.display.shading.shadow_intensity": 0.5, + "scene.display.shading.studio_light": "Default", + "scene.display.shading.studiolight_background_alpha": 0.0, + "scene.display.shading.studiolight_background_blur": 0.0, + "scene.display.shading.studiolight_intensity": 0.0, + "scene.display.shading.studiolight_rotate_z": 0.0, + "scene.display.shading.type": "SOLID", + "scene.display.shading.use_compositor": "DISABLED", + "scene.display.shading.use_dof": false, + "scene.display.shading.use_scene_lights": false, + "scene.display.shading.use_scene_lights_render": false, + "scene.display.shading.use_scene_world": false, + "scene.display.shading.use_scene_world_render": false, + "scene.display.shading.use_studiolight_view_rotation": true, + "scene.display.shading.use_world_space_lighting": false, + "scene.display.shading.wireframe_color_type": "MATERIAL", + "scene.display.shading.xray_alpha": 0.5, + "scene.display.shading.xray_alpha_wireframe": 0.0, "scene.display.light_direction": [ 0.5773502588272095, 0.5773502588272095, 0.5773502588272095 ], - "scene.view_settings.use_curve_mapping": false, - "space.overlay.show_wireframes": false, - "space.overlay.wireframe_threshold": 1.0, - "space.overlay.show_floor": true, + "scene.display.matcap_ssao_attenuation": 1.0, + "scene.display.matcap_ssao_distance": 0.20000000298023224, + "scene.display.matcap_ssao_samples": 16, + "scene.display.render_aa": "8", + "scene.display.shadow_focus": 0.0, + "scene.display.shadow_shift": 0.10000000149011612, + "scene.display.viewport_aa": "FXAA", + "space.overlay.backwire_opacity": 0.5, + "space.overlay.bone_wire_alpha": 1.0, + "space.overlay.display_handle": "SELECTED", + "space.overlay.fade_inactive_alpha": 0.4000000059604645, + "space.overlay.gpencil_fade_layer": 0.5, + "space.overlay.gpencil_fade_objects": 0.5, + "space.overlay.gpencil_grid_opacity": 0.5, + "space.overlay.gpencil_vertex_paint_opacity": 1.0, + "space.overlay.grid_lines": 16, + "space.overlay.grid_scale": 1.0, + "space.overlay.grid_subdivisions": 10, + "space.overlay.normals_constant_screen_size": 7.0, + "space.overlay.normals_length": 0.10000000149011612, + "space.overlay.sculpt_curves_cage_opacity": 0.5, + "space.overlay.sculpt_mode_face_sets_opacity": 1.0, + "space.overlay.sculpt_mode_mask_opacity": 0.75, + "space.overlay.show_annotation": true, "space.overlay.show_axis_x": true, "space.overlay.show_axis_y": true, "space.overlay.show_axis_z": false, + "space.overlay.show_bones": true, + "space.overlay.show_cursor": true, + "space.overlay.show_curve_normals": false, + "space.overlay.show_edge_bevel_weight": true, + "space.overlay.show_edge_crease": true, + "space.overlay.show_edge_seams": true, + "space.overlay.show_edge_sharp": true, + "space.overlay.show_edges": false, + "space.overlay.show_extra_edge_angle": false, + "space.overlay.show_extra_edge_length": false, + "space.overlay.show_extra_face_angle": false, + "space.overlay.show_extra_face_area": false, + "space.overlay.show_extra_indices": false, + "space.overlay.show_extras": true, + "space.overlay.show_face_center": false, + "space.overlay.show_face_normals": false, + "space.overlay.show_face_orientation": false, + "space.overlay.show_faces": true, + "space.overlay.show_fade_inactive": false, + "space.overlay.show_floor": true, + "space.overlay.show_freestyle_edge_marks": true, + "space.overlay.show_freestyle_face_marks": true, + "space.overlay.show_look_dev": false, + "space.overlay.show_motion_paths": true, "space.overlay.show_object_origins": true, - "space.overlay.show_relationship_lines": true - }, - "render_type": "VIEWPORT" + "space.overlay.show_object_origins_all": false, + "space.overlay.show_occlude_wire": false, + "space.overlay.show_onion_skins": false, + "space.overlay.show_ortho_grid": true, + "space.overlay.show_outline_selected": true, + "space.overlay.show_overlays": true, + "space.overlay.show_paint_wire": false, + "space.overlay.show_relationship_lines": true, + "space.overlay.show_sculpt_curves_cage": true, + "space.overlay.show_sculpt_face_sets": true, + "space.overlay.show_sculpt_mask": true, + "space.overlay.show_split_normals": false, + "space.overlay.show_stats": false, + "space.overlay.show_statvis": false, + "space.overlay.show_text": true, + "space.overlay.show_vertex_normals": false, + "space.overlay.show_viewer_attribute": true, + "space.overlay.show_weight": false, + "space.overlay.show_wireframes": false, + "space.overlay.show_wpaint_contours": false, + "space.overlay.show_xray_bone": false, + "space.overlay.texture_paint_mode_opacity": 1.0, + "space.overlay.use_debug_freeze_view_culling": false, + "space.overlay.use_gpencil_canvas_xray": false, + "space.overlay.use_gpencil_edit_lines": true, + "space.overlay.use_gpencil_fade_gp_objects": false, + "space.overlay.use_gpencil_fade_layers": false, + "space.overlay.use_gpencil_fade_objects": false, + "space.overlay.use_gpencil_grid": false, + "space.overlay.use_gpencil_multiedit_line_only": false, + "space.overlay.use_gpencil_onion_skin": false, + "space.overlay.use_gpencil_show_directions": false, + "space.overlay.use_gpencil_show_material_name": false, + "space.overlay.use_normals_constant_screen_size": false, + "space.overlay.vertex_opacity": 1.0, + "space.overlay.vertex_paint_mode_opacity": 1.0, + "space.overlay.viewer_attribute_opacity": 1.0, + "space.overlay.weight_paint_mode_opacity": 1.0, + "space.overlay.wireframe_opacity": 1.0, + "space.overlay.wireframe_threshold": 1.0, + "space.overlay.xray_alpha_bone": 0.0 + } } } \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 4456fc34fa..e16e10ab88 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -22,7 +22,6 @@ import addon_utils import ifcopenshell.api.owner.settings import blenderbim.tool as tool import blenderbim.core.owner as core_owner -from blenderbim.bim.module.drawing.prop import RasterStyleProperty from bpy.app.handlers import persistent from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index bc4c90af90..51d633da00 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -44,9 +44,9 @@ from blenderbim.bim.module.drawing.decoration import CutDecorator from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData import blenderbim.bim.export_ifc from lxml import etree -from mathutils import Vector +from mathutils import Vector, Color from timeit import default_timer as timer -from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal +from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal, RASTER_STYLE_PROPERTIES_EXCLUDE from blenderbim.bim.ifc import IfcStore from pathlib import Path @@ -1522,14 +1522,38 @@ class SaveDrawingStyle(bpy.types.Operator, Operator): space = self.get_view_3d(context) # Do not remove. It is used later in eval scene = context.scene style = {} - for prop in RasterStyleProperty: - value = eval(prop.value) + eval_namespace = { + 'context': context, + 'scene': scene, + 'space': space + } + + def add_prop_to_style(prop_path, context, scene, space): + value = eval(prop_path) if not isinstance(value, str): try: value = tuple(value) except TypeError: pass - style[prop.value] = value + style[prop_path] = value + + for prop in RasterStyleProperty: + if prop.name.startswith("EVAL_PROP"): + prop_path = prop.value + add_prop_to_style(prop_path, **eval_namespace) + else: + props_source_path = prop.value + props_source = eval(props_source_path) + for prop_name in dir(props_source): + if prop_name.startswith("__"): + continue + + prop_path = f"{props_source_path}.{prop_name}" + prop_value = eval(prop_path) + if not isinstance(prop_value, (int, float, bool, str, Color, Vector)) or props_source.is_property_readonly(prop_name) or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE: + continue + + add_prop_to_style(prop_path, **eval_namespace) if self.index: index = int(self.index) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 11d90762f3..97cbcf2d4f 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -274,31 +274,22 @@ class DrawingStyle(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=StrProperty) + class RasterStyleProperty(enum.Enum): - WORLD_COLOR = "bpy.data.worlds[0].color" - RENDER_ENGINE = "scene.render.engine" - RENDER_TRANSPARENT = "scene.render.film_transparent" - VIEW_TRANSFORM = "scene.view_settings.view_transform" - SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline" - SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity" - SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type" - SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor" - SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor" - SHADING_LIGHT = "scene.display.shading.light" - SHADING_COLOR_TYPE = "scene.display.shading.color_type" - SHADING_SINGLE_COLOR = "scene.display.shading.single_color" - SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows" - SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity" - DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction" - VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping" - OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes" - OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold" - OVERLAY_SHOW_FLOOR = "space.overlay.show_floor" - OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x" - OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y" - OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z" - OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins" - OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines" + # EVAL_PROP_ props will be evaluated explicitly + EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color" + + # those props attributes used as a source for shading style properties + RENDER = "scene.render" + VIEW_SETTINGS = "scene.view_settings" + SHADING = "scene.display.shading" + DISPLAY = "scene.display" + OVERLAY = "space.overlay" + + +RASTER_STYLE_PROPERTIES_EXCLUDE = ( + "scene.render.filepath", +) class DocProperties(PropertyGroup): From 812eb5eaef5ccbe0dfb5c764c048225d551da6e0 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 25 May 2023 16:09:43 +0500 Subject: [PATCH 45/65] Small documentation note --- src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py index 37b269d433..46fee7b392 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py @@ -64,7 +64,7 @@ class Usecase: become IfcLabel, float values will become IfcReal, booleans will become IfcBoolean, and integers will become IfcInteger. If more control is desired, you may explicitly specify IFC data objects - directly. + directly. Note that provided `properties` might be mutated in the process. :type properties: dict :param pset_template: If a property set template is provided, this will be used to determine data types. If no user-defined template is From 4c793be5f76cff0c631af8202862a6da629561a8 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Fri, 26 May 2023 08:28:20 +0100 Subject: [PATCH 46/65] BBIM Ensure LF end-of-line in Git repo (#3096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git auto text detection seems to fail with IFC files, possibly due to long lines. So we now force detection as text which ensures conversion from CRLF to LF when adding to the repository. We also convert the local IFC file to LF otherwise Git thinks that the local file is out-of-sync with the repo version ¯\_(ツ)_/¯ --- src/blenderbim/blenderbim/tool/ifcgit.py | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index 641d0ad295..80520c6845 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -16,22 +16,13 @@ class IfcGit: @classmethod def init_repo(cls, path_dir): IfcGitRepo.repo = git.Repo.init(path_dir) - autocrlf = "input" - if os.name == "nt": - autocrlf = "true" - IfcGitRepo.repo.config_writer().set_value("core", "autocrlf", autocrlf) cls.config_info_attributes(IfcGitRepo.repo) @classmethod def clone_repo(cls, remote_url, local_folder): - autocrlf = "input" - if os.name == "nt": - autocrlf = "true" IfcGitRepo.repo = git.Repo.clone_from( url=remote_url, to_path=local_folder, - allow_unsafe_options=True, - multi_options=["--config core.autocrlf=" + autocrlf], ) cls.config_info_attributes(IfcGitRepo.repo) return IfcGitRepo.repo @@ -78,6 +69,8 @@ class IfcGit: @classmethod def add_file_to_repo(cls, repo, path_file): + if os.name == "nt": + cls.dos2unix(path_file) repo.index.add(path_file) repo.index.commit(message="Added " + os.path.relpath(path_file, repo.working_dir)) bpy.ops.ifcgit.refresh() @@ -90,6 +83,8 @@ class IfcGit: def git_commit(cls, path_file): props = bpy.context.scene.IfcGitProperties repo = IfcGitRepo.repo + if os.name == "nt": + cls.dos2unix(path_file) repo.index.add(path_file) repo.index.commit(message=props.commit_message) props.commit_message = "" @@ -375,8 +370,16 @@ class IfcGit: path_attributes = os.path.join(repo.git_dir, "info", "attributes") if not os.path.exists(path_attributes): with open(path_attributes, "w") as f: - f.write("*.ifc text\n") - f.write("*.IFC text") + # attributes patterns are case-insensitive + f.write("*.ifc text") + + @classmethod + def dos2unix(cls, path_file): + with open(path_file, "rb") as infile: + content = infile.read() + with open(path_file, "wb") as output: + for line in content.splitlines(): + output.write(line + b"\n") @classmethod def execute_merge(cls, path_ifc, operator): From 68f8d8798af8f677969ce37357448acf9e82e094 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 26 May 2023 19:24:14 +1000 Subject: [PATCH 47/65] Fix #3181. --- .../blenderbim/bim/module/drawing/operator.py | 10 +++++----- src/blenderbim/blenderbim/core/drawing.py | 3 +++ src/blenderbim/blenderbim/core/tool.py | 6 ++++-- src/blenderbim/blenderbim/tool/drawing.py | 8 ++++++++ src/blenderbim/test/bim/feature/drawing.feature | 15 +++++++++++++++ src/blenderbim/test/core/test_drawing.py | 2 ++ 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 51d633da00..b7562030ff 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -1299,9 +1299,13 @@ class ActivateModel(bpy.types.Operator): bl_description = "Activates the model view" def execute(self, context): + dprops = bpy.context.scene.DocProperties + dprops.active_drawing_id = 0 + CutDecorator.uninstall() - bpy.ops.object.hide_view_clear() + if not bpy.app.background: + bpy.ops.object.hide_view_clear() subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") @@ -1411,15 +1415,11 @@ class RemoveDrawing(bpy.types.Operator, Operator): else: drawings = [tool.Ifc.get().by_id(self.drawing)] - print("Removing drawings: {}".format([d for d in drawings])) removed_drawings = [drawing.id() for drawing in drawings] for drawing in drawings: core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing) - active_drawing_id = context.scene.DocProperties.active_drawing_id - if active_drawing_id in removed_drawings: - context.scene.DocProperties.active_drawing_id = 0 class ReloadDrawingStyles(bpy.types.Operator): bl_idname = "bim.reload_drawing_styles" diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index 873c554ba0..0fd08697ec 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -270,6 +270,9 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati def remove_drawing(ifc, drawing_tool, drawing=None): + if drawing_tool.is_active_drawing(drawing): + drawing_tool.run_drawing_activate_model() + collection = drawing_tool.get_drawing_collection(drawing) if collection: drawing_tool.delete_collection(collection) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 29eb554456..1a5dcefd5d 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -273,9 +273,8 @@ class Drawing: def get_body_context(cls): pass def get_default_drawing_path(cls, name): pass def get_default_drawing_resource_path(cls, resource): pass - def get_default_shading_style(cls): pass - def setup_shading_styles_path(cls, resource_path): pass def get_default_layout_path(cls, identification, name): pass + def get_default_shading_style(cls): pass def get_default_sheet_path(cls, identification, name): pass def get_default_sheet_path(cls, identification, name): pass def get_default_titleblock_path(cls, name): pass @@ -301,17 +300,20 @@ class Drawing: def import_schedules(cls): pass def import_sheets(cls): pass def import_text_attributes(cls, obj): pass + def is_active_drawing(cls, drawing): pass def is_camera_orthographic(cls): pass def is_drawing_active(cls): pass def move_file(cls, src, dest): pass def open_spreadsheet(cls, uri): pass def open_svg(cls, filepath): pass def remove_literal_from_annotation(cls, obj, literal): pass + def run_drawing_activate_model(cls): pass def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass def select_assigned_product(cls, drawing): pass def set_drawing_collection_name(cls, group, collection): pass def set_name(cls, element, name): pass def setup_annotation_object(cls, obj, object_type): pass + def setup_shading_styles_path(cls, resource_path): pass def show_decorations(cls): pass def sync_object_placement(cls, obj): pass def synchronise_ifc_and_text_attributes(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index 8abe940481..bd3a5b78b1 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -1442,6 +1442,14 @@ class Drawing(blenderbim.core.tool.Drawing): camera = bpy.context.scene.camera return True if (camera and camera.data.type == "ORTHO") else False + @classmethod + def is_active_drawing(cls, drawing): + return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id + + @classmethod + def run_drawing_activate_model(cls): + bpy.ops.bim.activate_model() + @classmethod def activate_drawing(cls, camera): area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") diff --git a/src/blenderbim/test/bim/feature/drawing.feature b/src/blenderbim/test/bim/feature/drawing.feature index 012aafdc80..e2ca0a2b74 100644 --- a/src/blenderbim/test/bim/feature/drawing.feature +++ b/src/blenderbim/test/bim/feature/drawing.feature @@ -74,6 +74,21 @@ Scenario: Remove drawing - via object deletion When I press "bim.override_object_delete" Then the collection "IfcGroup/PLAN_VIEW" does not exist +Scenario: Remove drawing - deleting active drawing + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()" + And I press "bim.add_drawing" + And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()" + And I set "scene.DocProperties.active_drawing_index" to "0" + And I press "bim.activate_drawing(drawing={drawing})" + And the object "IfcAnnotation/PLAN_VIEW" is selected + When I press "bim.override_object_delete" + Then the collection "IfcGroup/PLAN_VIEW" does not exist + Scenario: Reproducing freeze on generating drawing - Issue 3169 Given an empty IFC project And I add a cube diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index e35be93fe0..4f901d7a62 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -345,6 +345,8 @@ class TestDuplicateDrawing: class TestRemoveDrawing: def test_run(self, ifc, drawing): + drawing.is_active_drawing("drawing").should_be_called().will_return(True) + drawing.run_drawing_activate_model().should_be_called() drawing.get_drawing_collection("drawing").should_be_called().will_return("collection") drawing.get_drawing_group("drawing").should_be_called().will_return("group") drawing.get_group_elements("group").should_be_called().will_return("elements") From d3251c5ca12b678f6820be7713769e0a956aa470 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 26 May 2023 19:24:44 +1000 Subject: [PATCH 48/65] New get_shape_matrix utility. --- .../geometry_processing.rst | 23 ++++++++++++------- .../ifcopenshell/util/shape.py | 16 ++++++++----- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst index 20ee185476..5bd4131902 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst @@ -20,6 +20,7 @@ related information in ``shape.geometry``: import ifcopenshell import ifcopenshell.geom + import ifcopenshell.util.shape ifc_file = ifcopenshell.open('model.ifc') element = ifc_file.by_type('IfcWall')[0] @@ -55,19 +56,25 @@ related information in ``shape.geometry``: # Objects are never scaled, so the scale factor of the matrix is always 1. matrix = shape.transformation.matrix.data - # Indices of vertices per triangle face e.g. [f1v1, f1v2, f1v3, f2v1, f2v2, f2v3, ...] - faces = shape.geometry.faces - - # Indices of vertices per edge e.g. [e1v1, e1v2, e2v1, e2v2, ...] - edges = shape.geometry.edges + # For convenience, you might want the matrix as a nested numpy array, so you can do matrix math. + matrix = ifcopenshell.util.shape.get_shape_matrix(shape) # X Y Z of vertices in flattened list e.g. [v1x, v1y, v1z, v2x, v2y, v2z, ...] verts = shape.geometry.verts + # Indices of vertices per edge e.g. [e1v1, e1v2, e2v1, e2v2, ...] + edges = shape.geometry.edges + + # Indices of vertices per triangle face e.g. [f1v1, f1v2, f1v3, f2v1, f2v2, f2v3, ...] + faces = shape.geometry.faces + # Since the lists are flattened, you may prefer to group them like so depending on your geometry kernel - grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)] - grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)] - grouped_faces = [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)] + # A nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...] + grouped_verts = ifcopenshell.util.shape.get_vertices(shape.geometry) + # A nested numpy array e.g. [[e1v1, e1v2], [e2v1, e2v2], ...] + grouped_edges = ifcopenshell.util.shape.get_edges(shape.geometry) + # A nested numpy array e.g. [[f1v1, f1v2, f1v3], [f2v1, f2v2, f2v3], ...] + grouped_faces = ifcopenshell.util.shape.get_faces(shape.geometry) # A list of styles that are relevant to this shape styles = shape.geometry.materials diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape.py b/src/ifcopenshell-python/ifcopenshell/util/shape.py index 7de09a4f2c..9053838679 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape.py @@ -66,6 +66,11 @@ def get_z(geometry): return max(z_values) - min(z_values) +def get_shape_matrix(shape): + m = shape.transformation.matrix.data + return np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])) + + def get_bbox_centroid(geometry): x_values = [geometry.verts[i] for i in range(0, len(geometry.verts), 3)] y_values = [geometry.verts[i + 1] for i in range(0, len(geometry.verts), 3)] @@ -89,9 +94,7 @@ def get_element_bbox_centroid(element, geometry): def get_shape_bbox_centroid(shape, geometry): centroid = get_bbox_centroid(geometry) - m = shape.transformation.matrix.data - mat = np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])) - return (mat @ np.array([*centroid, 1.0]))[0:3] + return (get_shape_matrix(shape) @ np.array([*centroid, 1.0]))[0:3] def get_vertices(geometry): @@ -110,14 +113,15 @@ def get_faces(geometry): def get_shape_vertices(shape, geometry): - m = shape.transformation.matrix.data - mat = np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])) + verts = get_vertices(geometry) + mat = get_shape_matrix(shape) return np.array([mat @ np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)]) def get_element_vertices(element, geometry): + verts = get_vertices(geometry) if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"): - return get_shape_vertices(geometry) + return verts mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) return np.array([mat @ np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)]) From adec3853e8dea9bd8711f286d72d92a4d39359ff Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 26 May 2023 19:54:26 +1000 Subject: [PATCH 49/65] Fix #3202. --- src/ifctester/ifctester/facet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index 5e47dba907..f2b6396605 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -58,6 +58,8 @@ class Facet: return results def parse(self, xml): + setattr(self, "minOccurs", 1) + setattr(self, "maxOccurs", 1) for name, value in xml.items(): name = name.replace("@", "") if isinstance(value, dict) and "simpleValue" in value.keys(): @@ -893,7 +895,7 @@ class ClassificationResult(Result): return "The entity has no classification" elif self.reason["type"] == "VALUE": return f"The references \"{str(self.reason['actual'])}\" do not match the requirements" - elif self.reason["type"] == "system": + elif self.reason["type"] == "SYSTEM": return f"The systems \"{str(self.reason['actual'])}\" do not match the requirements" elif self.reason["type"] == "PROHIBITED": return f"The classification should not have met the requirement" From 1d2c9780da4563fe202fae9dbd41f0dbe2bf19c6 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Sat, 27 May 2023 15:22:07 +0500 Subject: [PATCH 50/65] Revision Clouds - new annotation type Short demonstration - https://github.com/IfcOpenShell/IfcOpenShell/assets/9417531/90c9b458-0bae-40a2-9eee-d748e57c24a3 Curently it doesn't have any special representation in viewport and it doesn't have any customisable parameters. You might get come across error "Revision cloud edges must form just 1 polygon" - it means your edges either don't form any polygon or form 2 or more, both cases are not supported. --- .../bim/module/drawing/annotation.py | 7 +- .../bim/module/drawing/decoration.py | 18 +++- .../blenderbim/bim/module/drawing/prop.py | 4 +- .../bim/module/drawing/svgwriter.py | 98 ++++++++++++++++++- .../blenderbim/bim/module/drawing/ui.py | 32 +++--- src/blenderbim/blenderbim/tool/drawing.py | 26 ++--- 6 files changed, 144 insertions(+), 41 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index 8eae554b15..ac6130e604 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -20,6 +20,7 @@ import bpy import os import blenderbim.tool as tool from mathutils import Vector +import bmesh class Annotator: @@ -94,7 +95,7 @@ class Annotator: return obj @staticmethod - def add_plane_to_annotation(obj): + def add_plane_to_annotation(obj, remove_face=False): # default order = bot left, top left, bot right, top right # therefore we redefine the order face_verts = [0, 2, 3, 1] @@ -104,7 +105,9 @@ class Annotator: bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True) new_verts = [bm.verts.new(v) for v in verts_local] - bm.faces.new([new_verts[i] for i in face_verts]) + face = bm.faces.new([new_verts[i] for i in face_verts]) + if remove_face: + bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY") tool.Blender.apply_bmesh(obj.data, bm, obj) return obj diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index d4e30af08f..9554e5ac18 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -343,7 +343,7 @@ class BaseDecorator: shader.uniform_float("color", color) batch.draw(shader) - def draw_lines(self, context, obj, vertices, indices, color=None): + def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0): """`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center""" region = context.region if not color: @@ -352,7 +352,7 @@ class BaseDecorator: self.line_shader.bind() # POLYLINE_UNIFORM_COLOR specific uniforms self.line_shader.uniform_float("viewportSize", (region.width, region.height)) - self.line_shader.uniform_float("lineWidth", 1.0) + self.line_shader.uniform_float("lineWidth", line_width) gpu.state.blend_set("ALPHA") self.draw_batch("LINES", vertices, color, indices) @@ -1010,6 +1010,19 @@ class MiscDecorator(BaseDecorator): self.draw_lines(context, obj, winspace_verts, idxs) +class RevisionCloudDecorator(BaseDecorator): + objecttype = "REVISION_CLOUD" + + def decorate(self, context, obj): + if obj.data.is_editmode: + verts, idxs = self.get_editmesh_geom(obj) + else: + verts, idxs = self.get_mesh_geom(obj) + winspace_verts = worldspace_to_winspace(verts, context) + # TODO: draw revision clouds inside viewport? + self.draw_lines(context, obj, winspace_verts, idxs, color=(1, 0, 0, 1), line_width=2) + + # TODO: custom frag shader to support dashed lines? class HiddenDecorator(BaseDecorator): objecttype = "HIDDEN_LINE" @@ -1883,6 +1896,7 @@ class DecorationsHandler: PlanLevelDecorator, SectionLevelDecorator, StairDecorator, + RevisionCloudDecorator, BreakDecorator, SectionDecorator, ElevationDecorator, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 97cbcf2d4f..5358bab396 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -274,11 +274,10 @@ class DrawingStyle(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=StrProperty) - class RasterStyleProperty(enum.Enum): # EVAL_PROP_ props will be evaluated explicitly EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color" - + # those props attributes used as a source for shading style properties RENDER = "scene.render" VIEW_SETTINGS = "scene.view_settings" @@ -487,6 +486,7 @@ ANNOTATION_TYPES_DATA = { "BREAKLINE": ("Breakline", "", "FCURVE", "mesh"), "LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"), "BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"), + "REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"), "FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"), "FALL": ("Fall", "", "SORT_ASC", "curve"), } diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index bcbf5dc8f7..0ab437359c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -403,8 +403,7 @@ class SvgWriter: self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes)) ) - # BATTING ANNOTATIONS - if predefined_type == "BATTING": + def draw_batting_annotation(): v0_global = matrix_world @ obj.data.vertices[0].co.xyz v1_global = matrix_world @ obj.data.vertices[1].co.xyz v0 = self.project_point_onto_camera(v0_global) @@ -470,7 +469,94 @@ class SvgWriter: ) self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style)) - elif predefined_type == "SECTION": + def draw_revision_cloud_annotation(): + segment_width = 15.0 + base_height = 1 + width = 5 + + def get_svg_half_circle(height, width): + cp0 = f"0,-{height}" + cp1 = f"{width},-{height}" + end_point = f"{width},0" + circle = f"c{cp0} {cp1} {end_point}" + return circle + + def get_revision_pattern(base_offset): + pattern = f"m{base_offset.x},{base_offset.y}" + pattern += " " + get_svg_half_circle(2 * base_height, width) + pattern += " " + get_svg_half_circle(2.5 * base_height, width) + pattern += " " + get_svg_half_circle(1.5 * base_height, width) + return pattern + + def get_scale(size, direction): + vector = direction * size + shrinked_vector = size // segment_width * segment_width * direction + scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)] + return "scale(%f, %f)" % (scale[0], scale[1]) + + def poly_to_edges(poly): + edges = [] + n_verts = len(poly) + lats_index = n_verts - 1 + for i in range(len(poly)): + edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]] + edges.append(edge) + return edges + + element = tool.Ifc.get_entity(obj) + safe_offset_x = 2.0 + marker_width = segment_width + safe_offset_x * 2 + market_height = 15.0 + ref_y = 5.0 + revision_pattern = get_revision_pattern(Vector([safe_offset_x, ref_y])) + + bm = tool.Blender.get_bmesh_for_mesh(obj.data).copy() + bmesh.ops.contextual_create(bm, geom=bm.edges[:]) + faces = bm.faces[:] + assert len(faces) == 1, "Revision cloud edges must form just 1 polygon" + + # ensure clockwise order of polygon verts + # given default blender counter-clockwise order + polygon = faces[0] + if polygon.normal.z > 0: + polygon.normal_flip() + + marker_id = f"revision-cloud-{element.GlobalId}" + svg_path = self.svg.path(style="fill: none; stroke:red; stroke-width:0.20", d=revision_pattern) + marker = self.svg.marker( + markerUnits="userSpaceOnUse", + insert=(safe_offset_x, ref_y), + size=(marker_width, market_height), + orient="auto", + id=marker_id, + ) + marker.add(svg_path) + self.svg.add(marker) + + for v0, v1 in poly_to_edges(polygon.verts): + v0_global = matrix_world @ v0.co.xyz + v1_global = matrix_world @ v1.co.xyz + v0 = self.project_point_onto_camera(v0_global) + v1 = self.project_point_onto_camera(v1_global) + start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale + end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale + + pattern_edge = end_svg - start_svg + pattern_dir = pattern_edge.normalized() + pattern_length = pattern_edge.length + + segments = int(pattern_length // segment_width) + pattern_dir_step = pattern_dir * segment_width + points = [pattern_dir_step * i for i in range(segments)] + + polyline_style = f"marker: url(#{marker_id}); stroke: none;" + polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}" + polyline = self.svg.polyline( + points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform + ) + self.svg.add(polyline) + + def draw_section_annotation(): display_data = DecoratorData.get_section_markers_display_data(obj) connect_markers = display_data["connect_markers"] @@ -511,6 +597,12 @@ class SvgWriter: ) ) + if predefined_type == "BATTING": + draw_batting_annotation() + elif predefined_type == "REVISIONCLOUD": + draw_revision_cloud_annotation() + elif predefined_type == "SECTION": + draw_section_annotation() else: for edge in obj.data.edges: draw_simple_edge_annotation(*edge.vertices[:]) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index b4a9ba8723..24d10c8add 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -27,6 +27,7 @@ from blenderbim.bim.module.drawing.data import ( DrawingsData, DecoratorData, ) +from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA class BIM_PT_camera(Panel): @@ -471,55 +472,55 @@ class BIM_PT_annotation_utilities(Panel): row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE") op.object_type = "DIMENSION" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE") op.object_type = "ANGLE" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD") op.object_type = "RADIUS" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT") op.object_type = "DIAMETER" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS") op.object_type = "TEXT" - op.data_type = "empty" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS") op.object_type = "TEXT_LEADER" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK") op.object_type = "STAIR_ARROW" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO") op.object_type = "HIDDEN_LINE" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT") op.object_type = "PLAN_LEVEL" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN") op.object_type = "SECTION_LEVEL" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE") op.object_type = "BREAKLINE" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY") op.object_type = "LINEWORK" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE") op.object_type = "BATTING" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set" op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE") op.object_type = "FILL_AREA" @@ -527,7 +528,10 @@ class BIM_PT_annotation_utilities(Panel): row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC") op.object_type = "FALL" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) + op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA") + op.object_type = "REVISION_CLOUD" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) row.prop(self.props, "should_draw_decorations", text="Viewport Annotations") diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index bd3a5b78b1..c10abf326f 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -37,7 +37,7 @@ import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.helper as helper from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData -from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS +from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA from mathutils import Vector import collections @@ -55,28 +55,18 @@ class Drawing(blenderbim.core.tool.Drawing): tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"] ) + @classmethod + def get_annotation_data_type(cls, object_type): + return ANNOTATION_TYPES_DATA[object_type][3] + @classmethod def create_annotation_object(cls, drawing, object_type): - data_type = { - "ANGLE": "curve", - "BATTING": "mesh", - "BREAKLINE": "mesh", - "DIAMETER": "curve", - "DIMENSION": "curve", - "FALL": "curve", - "FILL_AREA": "mesh", - "HIDDEN_LINE": "mesh", - "LINEWORK": "mesh", - "PLAN_LEVEL": "curve", - "RADIUS": "curve", - "SECTION_LEVEL": "curve", - "STAIR_ARROW": "curve", - "TEXT": "empty", - "TEXT_LEADER": "curve", - }[object_type] + data_type = cls.get_annotation_data_type(object_type) obj = annotation.Annotator.get_annotation_obj(drawing, object_type, data_type) if object_type == "FILL_AREA": obj = annotation.Annotator.add_plane_to_annotation(obj) + elif object_type == "REVISION_CLOUD": + obj = annotation.Annotator.add_plane_to_annotation(obj, remove_face=True) elif object_type == "TEXT_LEADER": co1, _, co2, _ = annotation.Annotator.get_placeholder_coords() obj = annotation.Annotator.add_line_to_annotation(obj, co2, co1) From 00c010e04d1474c1307a7ad52b1f5a0c5a322ca1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 27 May 2023 21:31:58 +1000 Subject: [PATCH 51/65] See #3169. Fix bug where non walls and slabs didn't get semantic classes in drawings. --- src/blenderbim/blenderbim/bim/module/drawing/operator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index b7562030ff..d2e806340d 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -836,15 +836,15 @@ class CreateDrawing(bpy.types.Operator): ifc = tool.Ifc.get() for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"): element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid")) - # Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge. - if not element.is_a("IfcWall") and not element.is_a("IfcSlab"): - continue classes = self.get_svg_classes(element) classes.append("cut") - el.set("class", " ".join(classes)) + # Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge. + if not element.is_a("IfcWall") and not element.is_a("IfcSlab"): + continue + keys = [] for query in join_criteria: key = ifcopenshell.util.selector.get_element_value(element, query) From c8c0c9a6219dfc7a596f13156f2f8b20e2f47836 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 27 May 2023 22:50:59 +1000 Subject: [PATCH 52/65] Fix #3169. Don't allow the user to create invalid material sets without items. --- .../blenderbim/bim/module/material/data.py | 15 +++++ .../bim/module/material/operator.py | 55 ++++++++++--------- .../blenderbim/bim/module/material/prop.py | 24 ++------ .../blenderbim/bim/module/material/ui.py | 8 +-- 4 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/data.py b/src/blenderbim/blenderbim/bim/module/material/data.py index 81be795f0d..9e1d4154b9 100644 --- a/src/blenderbim/blenderbim/bim/module/material/data.py +++ b/src/blenderbim/blenderbim/bim/module/material/data.py @@ -96,6 +96,7 @@ class ObjectMaterialData: cls.data["total_thickness"] = cls.total_thickness() cls.data["materials"] = cls.materials() cls.data["type_material"] = cls.type_material() + cls.data["material_type"] = cls.material_type() cls.is_loaded = True @classmethod @@ -244,3 +245,17 @@ class ObjectMaterialData: else: name_attr = "Name" return getattr(material, name_attr, "Unnamed") or "Unnamed" + + @classmethod + def material_type(cls): + material_types = [ + "IfcMaterial", + "IfcMaterialConstituentSet", + "IfcMaterialLayerSet", + "IfcMaterialProfileSet", + "IfcMaterialList", + ] + version = tool.Ifc.get_schema() + if version == "IFC2X3": + material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"] + return [(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types] diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 2390537890..756b2d8e9b 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -189,23 +189,25 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator): material=material, ) assigned_material = ifcopenshell.util.element.get_material(element) - if assigned_material.is_a() in ("IfcMaterialLayerSet", "IfcMaterialLayerSetUsage"): - if assigned_material.is_a("IfcMaterialLayerSet"): - layer_set = assigned_material - else: - layer_set = assigned_material.ForLayerSet - - if not layer_set.MaterialLayers: + if assigned_material.is_a("IfcMaterialConstituentSet"): + if not assigned_material.MaterialConstituents: + ifcopenshell.api.run( + "material.add_constituent", + tool.Ifc.get(), + constituent_set=assigned_material, + material=material, + ) + elif assigned_material.is_a() == "IfcMaterialLayerSet": + if not assigned_material.MaterialLayers: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) layer = ifcopenshell.api.run( "material.add_layer", tool.Ifc.get(), - layer_set=layer_set, + layer_set=assigned_material, material=material, ) thickness = 0.1 # Arbitrary metric thickness for now layer.LayerThickness = thickness / unit_scale - elif assigned_material.is_a("IfcMaterialProfileSet"): if not assigned_material.MaterialProfiles: named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName] @@ -279,12 +281,12 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator): constituent: bpy.props.IntProperty() def _execute(self, context): - for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.constituent)): - if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1: - return - ifcopenshell.api.run( - "material.remove_constituent", tool.Ifc.get(), constituent=tool.Ifc.get().by_id(self.constituent) - ) + constituent = tool.Ifc.get().by_id(self.constituent) + for material_set in layer.ToMaterialConstituentSet: + if len(material_set.MaterialConstituents) == 1: + self.report({"ERROR"}, "At least one constituent must exist") + return {"ERROR"} + ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent) class AddProfile(bpy.types.Operator, tool.Ifc.Operator): @@ -314,10 +316,12 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator): profile: bpy.props.IntProperty() def _execute(self, context): - for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.profile)): - if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1: - return - ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile)) + profile = tool.Ifc.get().by_id(self.profile) + for material_set in profile.ToMaterialProfileSet: + if len(material_set.MaterialProfiles) == 1: + self.report({"ERROR"}, "At least one profile must exist") + return {"ERROR"} + ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile) class AddLayer(bpy.types.Operator, tool.Ifc.Operator): @@ -340,7 +344,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator): ) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - thickness = 0.1 # Arbitrary metric thickness for now + thickness = 0.1 # Arbitrary metric thickness for now layer.LayerThickness = thickness / unit_scale @@ -376,13 +380,12 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator): layer: bpy.props.IntProperty() def _execute(self, context): - for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)): - if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1: - self.report( - {"ERROR"}, "Cannot remove material layer - IfcMaterialLayerSet should alawys have atleast 1 layer" - ) + layer = tool.Ifc.get().by_id(self.layer) + for material_set in layer.ToMaterialLayerSet: + if len(material_set.MaterialLayers) == 1: + self.report({"ERROR"}, "At least one layer must exist") return {"ERROR"} - ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer)) + ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer) class AddListItem(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 16c00f6978..f09075cdf0 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -85,24 +85,10 @@ def get_materials(self, context): return ObjectMaterialData.data["materials"] -def get_object_material_types(self, context): - global materialtypes_enum - if len(materialtypes_enum) == 0 and IfcStore.get_file(): - material_types = [ - "IfcMaterial", - "IfcMaterialConstituentSet", - "IfcMaterialLayerSet", - "IfcMaterialLayerSetUsage", - "IfcMaterialProfileSet", - "IfcMaterialProfileSetUsage", - "IfcMaterialList", - ] - version = tool.Ifc.get_schema() - if version == "IFC2X3": - material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"] - materialtypes_enum.clear() - materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types] - return materialtypes_enum +def get_object_material_type(self, context): + if not ObjectMaterialData.is_loaded: + ObjectMaterialData.load() + return ObjectMaterialData.data["material_type"] def get_material_types(self, context): @@ -137,7 +123,7 @@ class BIMMaterialProperties(PropertyGroup): class BIMObjectMaterialProperties(PropertyGroup): - material_type: EnumProperty(items=get_object_material_types, name="Material Type") + material_type: EnumProperty(items=get_object_material_type, name="Material Type") material: EnumProperty(items=get_materials, name="Material") is_editing: BoolProperty(name="Is Editing", default=False) material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute) diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index be85ca2e06..da1490aae1 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -155,13 +155,7 @@ class BIM_PT_object_material(Panel): row = self.layout.row(align=True) prop_with_search(row, self.props, "material_type", text="") - if self.props.material_type in ( - "IfcMaterial", - "IfcMaterialList", - "IfcMaterialLayerSet", - "IfcMaterialLayerSetUsage", - ): - prop_with_search(row, self.props, "material", text="") + prop_with_search(row, self.props, "material", text="") row.operator("bim.assign_material", icon="ADD", text="") def draw_material_ui(self): From a0edd66b9207bdf821245623ef890a19bce13c49 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 28 May 2023 13:52:23 +1000 Subject: [PATCH 53/65] Fix #3050. Unlinking now gives you an option to delete as well. --- .../blenderbim/bim/module/root/operator.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 89ff3c7fca..ad960d650c 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -156,6 +156,7 @@ class UnlinkObject(bpy.types.Operator): bl_label = "Unlink Object" bl_options = {"REGISTER", "UNDO"} obj: bpy.props.StringProperty() + should_delete: bpy.props.BoolProperty(name="Should Delete", default=True) def execute(self, context): return IfcStore.execute_ifc_operator(self, context) @@ -167,7 +168,14 @@ class UnlinkObject(bpy.types.Operator): else: objects = context.selected_objects for obj in objects: - if obj.BIMObjectProperties.ifc_definition_id: + element = tool.Ifc.get_entity(obj) + if element: + if self.should_delete: + obj_copy = obj.copy() + for collection in obj.users_collection: + collection.objects.link(obj_copy) + tool.Geometry.delete_ifc_object(obj) + obj = obj_copy if obj in IfcStore.edited_objs: IfcStore.edited_objs.remove(obj) IfcStore.unlink_element(obj=obj) @@ -182,6 +190,13 @@ class UnlinkObject(bpy.types.Operator): obj.name = "/".join(obj.name.split("/")[1:]) return {"FINISHED"} + def draw(self, context): + row = self.layout.row() + row.prop(self, "should_delete") + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + class CopyClass(bpy.types.Operator, Operator): bl_idname = "bim.copy_class" From d3d2c3608bf77deb2b9d4ef76e04253323b5cc8c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 28 May 2023 15:02:55 +1000 Subject: [PATCH 54/65] Fix #2106. You can now assign an opening to another element. --- .../blenderbim/bim/module/void/operator.py | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 3559dea9c9..7cd620d754 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -34,25 +34,34 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator): props = context.scene.BIMModelProperties if len(context.selected_objects) != 2: return {"FINISHED"} + + # The convention is that element1 is the element and element2 is the opening. obj1, obj2 = context.selected_objects element1 = tool.Ifc.get_entity(obj1) element2 = tool.Ifc.get_entity(obj2) - if type(element1) == type(element2): - if ( - element1 - and element2 - and not element1.is_a("IfcOpeningElement") - and not element2.is_a("IfcOpeningElement") - ): - if element1.is_a("IfcWindow") or element1.is_a("IfcDoor"): + + if not element1 and not element2: + return {"FINISHED"} # Both are not IFC objects. + + if element1 and element2: + if element1.is_a("IfcOpeningElement") and element2.is_a("IfcOpeningElement"): + return {"FINISHED"} # You can't add an opening to another opening. + elif not element1.is_a("IfcOpeningElement") and not element2.is_a("IfcOpeningElement"): + if element1.is_a("IfcWindow") or element1.is_a("IfcDoor"): # Add a fill to an element. obj1, obj2 = obj2, obj1 FilledOpeningGenerator().generate(obj2, obj1, target=obj2.matrix_world.translation) - return {"FINISHED"} + return {"FINISHED"} + elif element1.is_a("IfcOpeningElement") or element2.is_a("IfcOpeningElement"): + if element1.is_a("IfcOpeningElement"): # Reassign an opening to another element. + obj1, obj2 = obj2, obj1 + element1, element2 = element2, element1 + if element2 and not element1: obj1, obj2 = obj2, obj1 element1, element2 = element2, element1 + if element1.is_a("IfcOpeningElement"): - return {"FINISHED"} + return {"FINISHED"} # You can't add an opening to another opening. if tool.Ifc.is_moved(obj1): blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1) @@ -64,17 +73,21 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator): break body_context = ifcopenshell.util.representation.get_context(IfcStore.get_file(), "Model", "Body") - element2 = blenderbim.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj2, - ifc_class="IfcOpeningElement", - should_add_representation=True, - context=body_context, - ) + if not element2: + element2 = blenderbim.core.root.assign_class( + tool.Ifc, + tool.Collector, + tool.Root, + obj=obj2, + ifc_class="IfcOpeningElement", + should_add_representation=True, + context=body_context, + ) ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=element2, element=element1) + if tool.Ifc.is_moved(obj2): + blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj2) + voided_objs = [obj1] for subelement in ifcopenshell.util.element.get_decomposition(element1): subobj = tool.Ifc.get_object(subelement) From 5d583741b9b8d9df44b4c85e5b0646145d744cfb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 28 May 2023 17:29:00 +1000 Subject: [PATCH 55/65] See #3184. Preserve parametric layerset axis direction hints when changing types. --- src/blenderbim/blenderbim/bim/module/model/slab.py | 10 +++++++++- src/blenderbim/blenderbim/bim/module/model/wall.py | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 8b9efc0ef7..7c9bbceebc 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -252,9 +252,17 @@ class DumbSlabPlaner: new_material = ifcopenshell.util.element.get_material(settings["relating_type"]) if not new_material or not new_material.is_a("IfcMaterialLayerSet"): return + parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric") + layer_set_direction = None + if parametric: + layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers]) material = ifcopenshell.util.element.get_material(settings["related_object"]) - if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3": + if not material or not material.is_a("IfcMaterialLayerSetUsage"): + return + if layer_set_direction: + material.LayerSetDirection = layer_set_direction + if material.LayerSetDirection == "AXIS3": self.change_thickness(settings["related_object"], new_thickness) def change_thickness(self, element, thickness): diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 174fd97e2d..0a43c1d021 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -699,8 +699,16 @@ class DumbWallPlaner: new_material = ifcopenshell.util.element.get_material(settings["relating_type"]) if not new_material or not new_material.is_a("IfcMaterialLayerSet"): return + parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric") + layer_set_direction = None + if parametric: + layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) material = ifcopenshell.util.element.get_material(settings["related_object"]) - if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2": + if not material or not material.is_a("IfcMaterialLayerSetUsage"): + return + if layer_set_direction: + material.LayerSetDirection = layer_set_direction + if material.LayerSetDirection == "AXIS2": DumbWallRecalculator().recalculate([obj]) From 83e97b7205df33d74816460f4c8cbb195ee8b208 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Sun, 28 May 2023 11:02:46 +0100 Subject: [PATCH 56/65] Fix checkout version equivalent to a remote HEAD --- src/blenderbim/blenderbim/tool/ifcgit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index 80520c6845..aa9cede3c8 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -336,9 +336,9 @@ class IfcGit: for branch in lookup[item.hexsha]: if branch.name == props.display_branch: branch.checkout() - else: - # NOTE this is calling the git binary in a subprocess - repo.git.checkout(item.hexsha) + return + # NOTE this is calling the git binary in a subprocess + repo.git.checkout(item.hexsha) @classmethod def delete_collection(cls, blender_collection): From 929aa3a49b60f2aa79314de4b10bad7f00fbe4f5 Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Sun, 28 May 2023 11:22:01 +0100 Subject: [PATCH 57/65] Select new/changed objects in visual diff (#3096) When visualising differences between current/selected revision, new/changed objects are selected as well as colourised --- src/blenderbim/blenderbim/tool/ifcgit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index aa9cede3c8..e72d58a5b7 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -192,6 +192,7 @@ class IfcGit: bpy.data.orphans_purge(do_recursive=True) bpy.ops.bim.load_project(filepath=path_ifc) + bpy.ops.object.select_all(action='DESELECT') @classmethod def branches_by_hexsha(cls, repo): @@ -311,6 +312,7 @@ class IfcGit: def colourise(cls, step_ids): area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area.spaces[0].shading.color_type = "OBJECT" + bpy.ops.object.select_all(action='DESELECT') for obj in bpy.context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: @@ -318,10 +320,13 @@ class IfcGit: step_id = obj.BIMObjectProperties.ifc_definition_id if step_id in step_ids["modified"]: obj.color = (0.3, 0.3, 1.0, 1) + obj.select_set(True) elif step_id in step_ids["added"]: obj.color = (0.2, 0.8, 0.2, 1) + obj.select_set(True) elif step_id in step_ids["removed"]: obj.color = (1.0, 0.2, 0.2, 1) + obj.select_set(True) else: obj.color = (1.0, 1.0, 1.0, 0.5) From 90c19f8454b2b96c8fb22e3b1c87122adcd9a27a Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 28 May 2023 14:45:57 +0100 Subject: [PATCH 58/65] Ctrl+P and Alt+P BIMTool Hotekys to assign/unassign aggregation --- .../bim/module/aggregate/operator.py | 11 +++++++++-- .../blenderbim/bim/module/drawing/prop.py | 2 ++ .../blenderbim/bim/module/model/__init__.py | 1 - .../blenderbim/bim/module/model/pie.py | 19 ++----------------- .../blenderbim/bim/module/model/workspace.py | 12 ++++++++++++ src/blenderbim/blenderbim/core/aggregate.py | 15 ++++++++++----- src/blenderbim/blenderbim/tool/aggregate.py | 6 ++++++ .../api/aggregate/assign_object.py | 2 +- 8 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index f17727ac59..9ab00d205c 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -47,7 +47,14 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator): element = tool.Ifc.get_entity(obj) if not element: continue - relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)) + if self.relating_object: + relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)) + else: + if obj == context.active_object: + continue + relating_obj = context.active_object + if not relating_obj: + continue result = core.assign_object( tool.Ifc, tool.Aggregate, @@ -56,7 +63,7 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator): related_obj=obj, ) if not result: - self.report({"ERROR"}, f"Objects {relating_obj} and {obj} cannot be aggregated") + self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 5358bab396..a89e83ffff 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -509,6 +509,8 @@ def update_annotation_object_type(self, context): # changing enum doesn't trigger refresh by itself AnnotationData.is_loaded = False +def update_sheet_data(self, context): + SheetsData.is_loaded = False class BIMAnnotationProperties(PropertyGroup): object_type: bpy.props.EnumProperty( diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index 509df1ecde..5a38a99e01 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -126,7 +126,6 @@ classes = ( pie.OpenPieClass, pie.PieUpdateContainer, pie.PieAddOpening, - pie.PieAssignObjectAggregation, pie.VIEW3D_MT_PIE_bim, pie.VIEW3D_MT_PIE_bim_class, sverchok_modifier.CreateNewSverchokGraph, diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py index 15e8c6bd10..5c15b2c3d9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/pie.py +++ b/src/blenderbim/blenderbim/bim/module/model/pie.py @@ -76,22 +76,6 @@ class PieUpdateContainer(bpy.types.Operator): break return {"FINISHED"} -class PieAssignObjectAggregation(bpy.types.Operator,tool.Ifc.Operator): - bl_idname = "bim.pie_assign_object_aggregation" - bl_label = "Assign Parts to Active Object" - bl_options = {"REGISTER", "UNDO"} - - def _execute(self, context): - for obj in context.selected_objects: - if obj == context.active_object: - continue - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=context.active_object, - related_obj=obj, - ) class VIEW3D_MT_PIE_bim(bpy.types.Menu): bl_label = "Geometry" @@ -103,7 +87,8 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu): pie.operator("bim.pie_add_opening") pie.operator("bim.pie_update_container") pie.operator("bim.open_pie_class", text="Assign IFC Class") - pie.operator("bim.pie_assign_object_aggregation", text ="Assign Aggregation") + pie.operator("bim.assign_object", text ="Assign Aggregation") + pie.operator("bim.unassign_object", text ="Unassign Aggregation") class VIEW3D_MT_PIE_bim_class(bpy.types.Menu): diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 7366d08e8b..ddb1368eaf 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -62,6 +62,8 @@ class BimTool(WorkSpaceTool): ("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}), ("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}), ("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}), + ("bim.hotkey", {"type": "P", "value": "PRESS", "ctrl": True}, {"properties": [("hotkey", "C_P")]}), + ("bim.hotkey", {"type": "P", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_P")]}), ) def draw_settings(context, layout, ws_tool): @@ -404,6 +406,16 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.bim.calculate_all_quantities() + def hotkey_C_P(self): + if not bpy.context.selected_objects: + return + bpy.ops.bim.assign_object() + + def hotkey_A_P(self): + if not bpy.context.selected_objects: + return + bpy.ops.bim.unassign_object() + def hotkey_S_C(self): if not bpy.context.selected_objects: return diff --git a/src/blenderbim/blenderbim/core/aggregate.py b/src/blenderbim/blenderbim/core/aggregate.py index b9e6f8ff7e..5445a5647c 100644 --- a/src/blenderbim/blenderbim/core/aggregate.py +++ b/src/blenderbim/blenderbim/core/aggregate.py @@ -40,11 +40,16 @@ def assign_object(ifc, aggregator, collector, relating_obj=None, related_obj=Non def unassign_object(ifc, aggregate, collector, relating_obj=None, related_obj=None): related_element = ifc.get_entity(related_obj) container = aggregate.get_container(related_element) - ifc.run("aggregate.unassign_object", product=related_element) - if container: - ifc.run("spatial.assign_container", product=related_element, relating_structure=container) - collector.assign(relating_obj) - collector.assign(related_obj) + if not relating_obj: + relating_element = aggregate.get_relating_object(related_element) + if related_element: + relating_obj = ifc.get_object(relating_element) + if relating_obj: + ifc.run("aggregate.unassign_object", product=related_element) + if container: + ifc.run("spatial.assign_container", product=related_element, relating_structure=container) + collector.assign(relating_obj) + collector.assign(related_obj) def add_part_to_object(ifc, aggregator, collector, blender, obj, part_class, part_name=None): diff --git a/src/blenderbim/blenderbim/tool/aggregate.py b/src/blenderbim/blenderbim/tool/aggregate.py index 2da8d0b484..e6827f691c 100644 --- a/src/blenderbim/blenderbim/tool/aggregate.py +++ b/src/blenderbim/blenderbim/tool/aggregate.py @@ -55,3 +55,9 @@ class Aggregate(blenderbim.core.tool.Aggregate): @classmethod def get_container(cls, element): return ifcopenshell.util.element.get_container(element) + + @classmethod + def get_relating_object(cls, related_element): + for rel in related_element.Decomposes: + if rel.is_a("IfcRelAggregates"): + return rel.RelatingObject \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py index 10ec5076b3..f8a1c3c261 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py +++ b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py @@ -96,7 +96,7 @@ class Usecase: break if decomposes and decomposes == is_decomposed_by: - return + return decomposes container = ifcopenshell.util.element.get_container(self.settings["product"], should_get_direct=True) if container: From 6f39648077090a365f8e0060b1763b69edf2f477 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Sun, 28 May 2023 14:47:47 +0100 Subject: [PATCH 59/65] BBIM Drawings: Exclude ifc objects hidden from render view --- src/blenderbim/blenderbim/tool/drawing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index c10abf326f..963b9066a2 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -1528,7 +1528,7 @@ class Drawing(blenderbim.core.tool.Drawing): tool.Ifc.get_entity(o) for o in objs if cls.is_in_camera_view(o, camera_inverse_matrix, x, y, camera.data.clip_start, camera.data.clip_end) - and tool.Ifc.get_entity(o) + and tool.Ifc.get_entity(o) and not o.hide_render ] ) From 07638dac27c0b5d80cfd0b355b01e2e38f70065b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 29 May 2023 15:27:37 +1000 Subject: [PATCH 60/65] Fix #3200. Bug where include / exclude filters could break the drawing. --- .../blenderbim/bim/module/drawing/operator.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index d2e806340d..52b3a9631e 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -481,6 +481,14 @@ class CreateDrawing(bpy.types.Operator): tree.add_element(elem) elements -= processed + if self.camera_element not in drawing_elements: + with profile("Camera element"): + # The camera must always be included, regardless of any include/exclude filters. + geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True) + it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element]) + for elem in self.yield_from_iterator(it): + self.serialiser.write(elem) + with profile("Finalizing"): self.serialiser.finalize() results = self.svg_buffer.get_value() @@ -1522,11 +1530,7 @@ class SaveDrawingStyle(bpy.types.Operator, Operator): space = self.get_view_3d(context) # Do not remove. It is used later in eval scene = context.scene style = {} - eval_namespace = { - 'context': context, - 'scene': scene, - 'space': space - } + eval_namespace = {"context": context, "scene": scene, "space": space} def add_prop_to_style(prop_path, context, scene, space): value = eval(prop_path) @@ -1545,12 +1549,16 @@ class SaveDrawingStyle(bpy.types.Operator, Operator): props_source_path = prop.value props_source = eval(props_source_path) for prop_name in dir(props_source): - if prop_name.startswith("__"): - continue + if prop_name.startswith("__"): + continue prop_path = f"{props_source_path}.{prop_name}" prop_value = eval(prop_path) - if not isinstance(prop_value, (int, float, bool, str, Color, Vector)) or props_source.is_property_readonly(prop_name) or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE: + if ( + not isinstance(prop_value, (int, float, bool, str, Color, Vector)) + or props_source.is_property_readonly(prop_name) + or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE + ): continue add_prop_to_style(prop_path, **eval_namespace) From 605097de762370140850e85e8e71dc3689173a65 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 29 May 2023 12:03:02 +0500 Subject: [PATCH 61/65] Same distance function for viewport and svg #1321 #3198 --- .../bim/module/drawing/decoration.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index 9554e5ac18..e8c01afe27 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -32,9 +32,10 @@ from bpy.types import SpaceView3D from mathutils import Vector, Matrix from bpy_extras.view3d_utils import location_3d_to_region_2d from gpu_extras.batch import batch_for_shader -from blenderbim.bim.module.drawing.data import DecoratorData -from blenderbim.bim.module.drawing.shaders import BASE_LIB_GLSL, BASE_DEF_GLSL, add_verts_sequence, add_offsets - +from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData +from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offsets +from blenderbim.bim.module.drawing.helper import format_distance +from functools import lru_cache UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY # TODO: move back to 0.2 @@ -486,14 +487,15 @@ class BaseDecorator: blf.draw(font_id, text) blf.disable(font_id, blf.ROTATION) + @lru_cache(maxsize=None) def format_value(self, context, value): - return bpy.utils.units.to_string( - context.scene.unit_settings.system, - "LENGTH", - value, - precision=4, - split_unit=context.scene.unit_settings.system == "IMPERIAL", - ) + drawing_pset_data = DrawingsData.data["active_drawing_pset_data"] + precision = drawing_pset_data.get("MetricPrecision", None) + if not precision: + precision = drawing_pset_data.get("ImperialPrecision", None) + + decimal_places = drawing_pset_data.get("DecimalPlaces", None) + return format_distance(value, precision=precision, decimal_places=decimal_places) def draw_asterisk(self, context, obj): # gather geometry data and convert to winspace @@ -1932,6 +1934,9 @@ class DecorationsHandler: if collection is None: return + if not DrawingsData.is_loaded: + DrawingsData.load() + for decorator in self.decorators: for obj in decorator.get_objects(collection): decorator.decorate(context, obj) From 7d852f9e51db9513405c3793c16b8b3c539de849 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 29 May 2023 17:20:55 +1000 Subject: [PATCH 62/65] Fix #3138. Remove manual copy class and be more intelligent about what classe can be reassigned. --- src/blenderbim/blenderbim/bim/module/root/data.py | 11 +++++++++++ src/blenderbim/blenderbim/bim/module/root/ui.py | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py index 123440327c..4d4d9c4a8e 100644 --- a/src/blenderbim/blenderbim/bim/module/root/data.py +++ b/src/blenderbim/blenderbim/bim/module/root/data.py @@ -44,6 +44,7 @@ class IfcClassData: cls.data["name"] = cls.name() cls.data["ifc_class"] = cls.ifc_class() cls.data["ifc_predefined_types"] = cls.ifc_predefined_types() + cls.data["can_reassign_class"] = cls.can_reassign_class() @classmethod def ifc_products(cls): @@ -172,3 +173,13 @@ class IfcClassData: element = tool.Ifc.get_entity(bpy.context.active_object) if element: return element.is_a() + + @classmethod + def can_reassign_class(cls): + element = tool.Ifc.get_entity(bpy.context.active_object) + if element: + if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"): + return False + for product in cls.ifc_products(): + if element.is_a(product[0]): + return True diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index df06f4bae0..2004f7ed08 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -62,9 +62,8 @@ class BIM_PT_class(Panel): row.label(text=IfcClassData.data["name"]) op = row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF") op.ifc_class = IfcClassData.data["ifc_class"] - row.operator("bim.copy_class", icon="DUPLICATE", text="") row.operator("bim.unlink_object", icon="UNLINKED", text="") - if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"): + if IfcClassData.data["can_reassign_class"]: row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="") else: ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context) From 7d7122ee4c85c01c6dbcfe7c98b19af47a257419 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 29 May 2023 20:31:23 +1000 Subject: [PATCH 63/65] Fix #3210. Revert "BBIM Drawings: Exclude ifc objects hidden from render view" This reverts commit 6f39648077090a365f8e0060b1763b69edf2f477. --- src/blenderbim/blenderbim/tool/drawing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index 963b9066a2..c10abf326f 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -1528,7 +1528,7 @@ class Drawing(blenderbim.core.tool.Drawing): tool.Ifc.get_entity(o) for o in objs if cls.is_in_camera_view(o, camera_inverse_matrix, x, y, camera.data.clip_start, camera.data.clip_end) - and tool.Ifc.get_entity(o) and not o.hide_render + and tool.Ifc.get_entity(o) ] ) From 6336db00690d6ea27b4b9a83631164a491435586 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 29 May 2023 12:00:32 +0500 Subject: [PATCH 64/65] Optimized iteration over objects for viewport annotations #3198 This commit should increase viewport annotations performance significantly on large projects. Described in #3198 --- .../bim/module/drawing/decoration.py | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index e8c01afe27..a814b084d0 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -35,11 +35,35 @@ from gpu_extras.batch import batch_for_shader from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offsets from blenderbim.bim.module.drawing.helper import format_distance +from timeit import default_timer as timer from functools import lru_cache UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY # TODO: move back to 0.2 +class profile_consequential: + start_time = None + lines = [] + + @classmethod + def __init__(cls, test_name): + cls.log() + cls.start_time = timer() + cls.test_name = test_name + + @classmethod + def log(cls): + if cls.start_time is not None: + cls.lines.append(f"{cls.test_name}\t{timer() - cls.start_time:.10f}") + + @classmethod + def stop(cls): + cls.log() + cls.start_time = None + print('\n'.join(cls.lines)) + cls.lines = [] + + def ccw(A, B, C): """whether a-b-c located in counter-clockwise order in 2d space""" return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x) @@ -168,56 +192,6 @@ class BaseDecorator: def camera_zoom_to_factor(self, zoom): return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2) - def get_objects(self, collection): - """find relevant objects - using class.objecttype - - returns: iterable of blender objects - """ - results = [] - # NOTE: if the ObjectType is not on the list - # it will be also drawn with MiscDecorator - decoration_presets = ( - "DIMENSION", - "TEXT_LEADER", - "STAIR_ARROW", - "HIDDEN_LINE", - "PLAN_LEVEL", - "SECTION_LEVEL", - "BREAKLINE", - "GRID", - "ELEVATION", - "SECTION", - "TEXT", - "BATTING", - ) - for obj in collection.all_objects: - if obj.hide_get(): - continue - - element = tool.Ifc.get_entity(obj) - if not element: - continue - - if element.is_a("IfcAnnotation"): - if element.ObjectType == self.objecttype: - results.append(obj) - - elif ( - self.objecttype == "MISC" - and element.ObjectType not in decoration_presets - and isinstance(obj.data, bpy.types.Mesh) - ): - results.append(obj) - - elif self.objecttype == "FALL" and element.ObjectType in ( - "SLOPE_ANGLE", - "SLOPE_FRACTION", - "SLOPE_PERCENT", - ): - results.append(obj) - return results - def get_splines(self, obj): """Iterates through splines Args: @@ -522,6 +496,7 @@ class BaseDecorator: def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False): """if `text_world_position` is not provided, the object's location will be used""" + if not text_world_position: text_world_position = obj.location @@ -572,6 +547,7 @@ class BaseDecorator: line_i += 1 if not reverse_lines_order else -1 + class DimensionDecorator(BaseDecorator): """Decorator for dimension objects - each edge of a segment with arrow @@ -659,6 +635,7 @@ class DimensionDecorator(BaseDecorator): text_offset_value = viewportDrawingScale * 3 for i0, i1 in indices: + v0 = Vector(vertices[i0]) v1 = Vector(vertices[i1]) p0 = location_3d_to_region_2d(region, region3d, v0) @@ -671,8 +648,6 @@ class DimensionDecorator(BaseDecorator): if not show_description_only: length = (v1 - v0).length - # TODO: same distance format function as in svg? - # requires storing drawing precision and decimal_places from pset to data.py text = self.format_value(context, length) text = text_prefix + text + text_suffix @@ -1914,7 +1889,7 @@ class DecorationsHandler: if cls.installed: cls.uninstall() handler = cls() - # NOTE: that we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR + # NOTE: we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR # and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL") @@ -1927,7 +1902,35 @@ class DecorationsHandler: cls.installed = None def __init__(self): - self.decorators = [cls() for cls in self.decorators_classes] + self.decorators = {cls.objecttype: cls() for cls in self.decorators_classes} + for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"): + self.decorators[object_type] = self.decorators["FALL"] + + def get_objects_and_decorators(self, collection): + results = [] + + for obj in collection.all_objects: + if obj.hide_get(): + continue + + element = tool.Ifc.get_entity(obj) + if not element: + continue + + if not element.is_a("IfcAnnotation"): + continue + + object_type = element.ObjectType + if object_type == "DRAWING": + continue + + if (dec := self.decorators.get(object_type, None)): + results.append((obj, dec)) + + elif isinstance(obj.data, bpy.types.Mesh): + results.append((obj, self.decorators["MISC"])) + + return results def __call__(self, context): collection, _ = helper.get_active_drawing(context.scene) @@ -1937,6 +1940,6 @@ class DecorationsHandler: if not DrawingsData.is_loaded: DrawingsData.load() - for decorator in self.decorators: - for obj in decorator.get_objects(collection): - decorator.decorate(context, obj) + object_decorators = self.get_objects_and_decorators(collection) + for obj, decorator in object_decorators: + decorator.decorate(context, obj) From 51ad31f5a326bc03caa3e605f655bda3124ed5b4 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 29 May 2023 17:13:51 +0500 Subject: [PATCH 65/65] Fixed Stair annotation bug introduced in 762299124 in 762299124 I've changed number of segments and verts but never adjusted StairDecorator - so it wasn't drawing parts of the annotation --- src/blenderbim/blenderbim/bim/module/drawing/decoration.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index a814b084d0..3a18f10166 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -959,15 +959,13 @@ class StairDecorator(BaseDecorator): # circle head on first vert if edge[0] == 0: circle_head = get_circle_head(circle_size) - add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True) - start_i += 12 + start_i = add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True) # arrow head on last vert if edge[1] == last_vert: edge_dir = (v1 - v0).normalized() arrow_head = get_arrow_head(edge_dir, arrow_size, rot_matrix_cw, rot_matrix_ccw) - add_verts_sequence([v1 - arrow_head[1], v1, v1 - arrow_head[2]], start_i, **out_kwargs) - start_i += 3 + start_i = add_verts_sequence([v1 - arrow_head[1], v1, v1 - arrow_head[2]], start_i, **out_kwargs) # stem with gaps for arrows add_verts_sequence([v0, v1], start_i, **out_kwargs)