diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile index b6160a02d3..2222fe7aad 100644 --- a/src/blenderbim/Makefile +++ b/src/blenderbim/Makefile @@ -243,11 +243,12 @@ endif # Required by IFC4D mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/b0/bb/9c4dddd3ca173cb56241cfb2eddfae24690dc676d357ac4cab17d0a36d9d/PyP6Xer-1.13.0.tar.gz - cd dist/working && tar -xzvf xerparser* - cp -r dist/working/xerparser-1.13.0/xerparser dist/blenderbim/libs/site/packages/ + cd dist/working && tar -xzvf PyP6Xer* + cp -r dist/working/PyP6Xer-1.13.0/xerparser dist/blenderbim/libs/site/packages/ rm -rf dist/working # Required by xerparser and IFC4D + # TODO: remove this dependency. It's only used to show a progress bar. mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/9f/7b/76c4e5ef1a1b528fcaada4133f972e77d33c252831676cf414119ca6093d/tqdm-4.50.0.tar.gz cd dist/working && tar -xzvf tqdm* diff --git a/src/blenderbim/blenderbim/bim/module/misc/__init__.py b/src/blenderbim/blenderbim/bim/module/misc/__init__.py index 687e45cfee..494005dfe4 100644 --- a/src/blenderbim/blenderbim/bim/module/misc/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/misc/__init__.py @@ -24,6 +24,7 @@ classes = ( operator.SetOverrideColour, operator.SetViewportShadowFromSun, operator.SnapSpacesTogether, + operator.SplitAlongEdge, prop.BIMMiscProperties, ui.BIM_PT_misc_utilities, ) diff --git a/src/blenderbim/blenderbim/bim/module/misc/operator.py b/src/blenderbim/blenderbim/bim/module/misc/operator.py index 1f4c39f3e5..e7d77f9262 100644 --- a/src/blenderbim/blenderbim/bim/module/misc/operator.py +++ b/src/blenderbim/blenderbim/bim/module/misc/operator.py @@ -120,8 +120,21 @@ class ResizeToStorey(bpy.types.Operator, Operator): @classmethod def poll(cls, context): - return context.selected_objects + return context.selected_objects and tool.Ifc.get() def _execute(self, context): for obj in context.selected_objects: core.resize_to_storey(tool.Misc, obj=obj) + + +class SplitAlongEdge(bpy.types.Operator, Operator): + bl_idname = "bim.split_along_edge" + bl_label = "Split Along Edge" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return context.selected_objects and tool.Ifc.get() + + def _execute(self, context): + core.split_along_edge(tool.Misc, cutter=context.active_object, objs=context.selected_objects) diff --git a/src/blenderbim/blenderbim/bim/module/misc/ui.py b/src/blenderbim/blenderbim/bim/module/misc/ui.py index 90bd3d3d56..bdac446914 100644 --- a/src/blenderbim/blenderbim/bim/module/misc/ui.py +++ b/src/blenderbim/blenderbim/bim/module/misc/ui.py @@ -40,3 +40,5 @@ class BIM_PT_misc_utilities(bpy.types.Panel): row.operator("bim.snap_spaces_together") row = layout.row() row.operator("bim.resize_to_storey") + row = layout.row() + row.operator("bim.split_along_edge") diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index cc2c038ad0..338b7eb6df 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -42,7 +42,7 @@ class BIM_PT_authoring(Panel): else: col.label(text="No IFC Class", icon="FILE_VOLUME") enabled = False - if type_prop.getAvailableTypes(tprops, context): + if type_prop.get_relating_type(tprops, context): col.prop(tprops, "relating_type", text="", icon="FILE_3D") else: col.label(text="No Relating Type", icon="FILE_3D") diff --git a/src/blenderbim/blenderbim/core/misc.py b/src/blenderbim/blenderbim/core/misc.py index 2ded3a3099..5234194d6c 100644 --- a/src/blenderbim/blenderbim/core/misc.py +++ b/src/blenderbim/blenderbim/core/misc.py @@ -28,3 +28,11 @@ def resize_to_storey(misc, obj=None): misc.move_object_to_elevation(obj, misc.get_storey_elevation_in_si(storey)) misc.scale_object_to_height(obj, height) misc.mark_object_as_edited(obj) + + +def split_along_edge(misc, cutter=None, objs=None): + new_objs = misc.split_objects_with_cutter(objs, cutter) + for obj in new_objs: + misc.run_root_copy_class(obj=obj) + for obj in objs: + misc.mark_object_as_edited(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 3f07ad85d9..1aa4a630e0 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -104,8 +104,10 @@ class Misc: def get_storey_height_in_si(cls, storey): pass def mark_object_as_edited(cls, obj): pass def move_object_to_elevation(cls, obj, elevation): pass + def run_root_copy_class(cls, obj=None): pass def scale_object_to_height(cls, obj, height): pass def set_object_origin_to_bottom(cls, obj): pass + def split_objects_with_cutter(cls, objs, cutter): pass @interface diff --git a/src/blenderbim/blenderbim/tool/misc.py b/src/blenderbim/blenderbim/tool/misc.py index 25f844e080..39af0b96d8 100644 --- a/src/blenderbim/blenderbim/tool/misc.py +++ b/src/blenderbim/blenderbim/tool/misc.py @@ -17,8 +17,10 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import bmesh import ifcopenshell import blenderbim.core.tool +import blenderbim.core.root import blenderbim.tool as tool from mathutils import Vector, Matrix from blenderbim.bim.ifc import IfcStore @@ -76,6 +78,10 @@ class Misc(blenderbim.core.tool.Misc): def move_object_to_elevation(cls, obj, elevation): obj.matrix_world.translation[2] = elevation + @classmethod + def run_root_copy_class(cls, obj=None): + return blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) + @classmethod def scale_object_to_height(cls, obj, height): absolute_bound_box = [obj.matrix_world @ Vector(c) for c in obj.bound_box] @@ -91,3 +97,38 @@ class Misc(blenderbim.core.tool.Misc): @classmethod def mark_object_as_edited(cls, obj): IfcStore.edited_objs.add(obj) + + @classmethod + def split_objects_with_cutter(cls, objs, cutter): + cutter_mesh = cutter.data + + bm = bmesh.new() + bm.from_mesh(cutter_mesh) + bm_flipped = bm.copy() + for f in bm_flipped.faces: + f.normal_flip() + + new_objs = [] + for obj in objs: + if obj.type != "MESH" or obj == cutter: + continue + new_obj = obj.copy() + new_obj.data = obj.data.copy() + + obj.users_collection[0].objects.link(new_obj) + + mod = new_obj.modifiers.new(type="BOOLEAN", name="Boolean") + mod.object = cutter + bpy.ops.object.modifier_apply({"object": new_obj}, modifier="Boolean") + + bm_flipped.to_mesh(cutter_mesh) + + mod = obj.modifiers.new(type="BOOLEAN", name="Boolean") + mod.object = cutter + bpy.ops.object.modifier_apply({"object": obj}, modifier="Boolean") + + new_objs.append(new_obj) + + bm.to_mesh(cutter_mesh) + bm.free() + return new_objs diff --git a/src/blenderbim/test/bim/feature/misc.feature b/src/blenderbim/test/bim/feature/misc.feature index 56f55c84cc..fc59ccaed0 100644 --- a/src/blenderbim/test/bim/feature/misc.feature +++ b/src/blenderbim/test/bim/feature/misc.feature @@ -35,3 +35,16 @@ Scenario: Resize to storey And I press "bim.assign_container(structure={storey})" When I press "bim.resize_to_storey" Then nothing happens + +Scenario: Split along edge + 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 I add a plane of size "4" at "0,0,0" + And the object "IfcWall/Cube" is selected + And additionally the object "Plane" is selected + When I press "bim.split_along_edge" + Then the object "IfcWall/Cube" is an "IfcWall" + And the object "IfcWall/Cube.001" is an "IfcWall" diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 3687f7efd2..64701d779f 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -105,6 +105,12 @@ def i_add_a_cube_of_size_size_at_location(size, location): bpy.ops.mesh.primitive_cube_add(size=float(size), location=[float(co) for co in location.split(",")]) +@given(parsers.parse('I add a plane of size "{size}" at "{location}"')) +@when(parsers.parse('I add a plane of size "{size}" at "{location}"')) +def i_add_a_plane_of_size_size_at_location(size, location): + bpy.ops.mesh.primitive_plane_add(size=float(size), location=[float(co) for co in location.split(",")]) + + @given(parsers.parse('I press "{operator}"')) @when(parsers.parse('I press "{operator}"')) def i_press_operator(operator): diff --git a/src/blenderbim/test/core/test_misc.py b/src/blenderbim/test/core/test_misc.py index 30367401aa..60a1046ed2 100644 --- a/src/blenderbim/test/core/test_misc.py +++ b/src/blenderbim/test/core/test_misc.py @@ -39,3 +39,11 @@ class TestResizeToStorey: misc.get_object_storey("obj").should_be_called().will_return("storey") misc.get_storey_height_in_si("storey").should_be_called().will_return(None) subject.resize_to_storey(misc, obj="obj") + + +class TestSplitAlongEdge: + def test_run(self, misc): + misc.split_objects_with_cutter(["obj"], "cutter").should_be_called().will_return(["new_obj"]) + misc.run_root_copy_class(obj="new_obj").should_be_called() + misc.mark_object_as_edited("obj").should_be_called() + subject.split_along_edge(misc, cutter="cutter", objs=["obj"]) diff --git a/src/blenderbim/test/tool/test_misc.py b/src/blenderbim/test/tool/test_misc.py index 2ff1be9cc9..9f2e47dd84 100644 --- a/src/blenderbim/test/tool/test_misc.py +++ b/src/blenderbim/test/tool/test_misc.py @@ -134,6 +134,11 @@ class TestMoveObjectToElevation(test.bim.bootstrap.NewFile): assert list(obj.matrix_world.translation) == [0.0, 0.0, 3.0] +class TestRunRootCopyClass(test.bim.bootstrap.NewFile): + def test_nothing(self): + pass + + class TestScaleObjectToHeight(test.bim.bootstrap.NewFile): def test_run(self): bpy.ops.mesh.primitive_cube_add() @@ -149,3 +154,16 @@ class TestMarkObjectAsEdited(test.bim.bootstrap.NewFile): obj = bpy.data.objects.new("Cube", None) subject.mark_object_as_edited(obj) assert obj in IfcStore.edited_objs + + +class TestSplitObjectsWithCutter(test.bim.bootstrap.NewFile): + def test_run(self): + bpy.ops.mesh.primitive_cube_add() + bpy.ops.mesh.primitive_plane_add(size=4) + obj = bpy.data.objects.get("Cube") + cutter = bpy.data.objects.get("Plane") + assert obj.dimensions[2] == 2 + new_objs = subject.split_objects_with_cutter([obj], cutter) + assert new_objs[0].name == "Cube.001" + assert obj.dimensions[2] == 1 + assert new_objs[0].dimensions[2] == 1