mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 10:06:47 +00:00
You can now split objects using a drawn edge, especially useful when creating slab pour breaks and fixing lazily modeled walls and columns. Thanks Gorgious56!
This commit is contained in:
@@ -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*
|
||||
|
||||
@@ -24,6 +24,7 @@ classes = (
|
||||
operator.SetOverrideColour,
|
||||
operator.SetViewportShadowFromSun,
|
||||
operator.SnapSpacesTogether,
|
||||
operator.SplitAlongEdge,
|
||||
prop.BIMMiscProperties,
|
||||
ui.BIM_PT_misc_utilities,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user