mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 18:21:59 +00:00
See #5972. You can now add booleans in item editing mode, with support for all three boolean operators.
This commit is contained in:
@@ -532,77 +532,59 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
ifc_file = tool.Ifc.get()
|
first_obj = tool.Blender.get_active_object()
|
||||||
ifc_objects: list[bpy.types.Object] = []
|
if not tool.Geometry.is_representation_item(first_obj):
|
||||||
non_ifc_objects: list[bpy.types.Object] = []
|
self.report({"INFO"}, "At least two representation items must be selected to add a boolean.")
|
||||||
for obj in context.selected_objects:
|
return {"CANCELLED"}
|
||||||
if tool.Ifc.get_entity(obj):
|
second_objs = [
|
||||||
ifc_objects.append(obj)
|
o for o in tool.Blender.get_selected_objects() if o != first_obj and tool.Geometry.is_representation_item(o)
|
||||||
else:
|
]
|
||||||
non_ifc_objects.append(obj)
|
if not second_objs:
|
||||||
|
self.report({"INFO"}, "At least two representation items must be selected to add a boolean.")
|
||||||
if not non_ifc_objects:
|
|
||||||
self.report({"INFO"}, "At least 1 non-ifc object should be selected to be added as a boolean.")
|
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
if len(ifc_objects) != 1:
|
props = context.scene.BIMBooleanProperties
|
||||||
self.report(
|
|
||||||
{"INFO"},
|
|
||||||
f"Only 1 IFC object need to be selected to add booleans to, currently selected {len(ifc_objects)} IFC objects.",
|
|
||||||
)
|
|
||||||
return {"CANCELLED"}
|
|
||||||
|
|
||||||
ifc_obj = ifc_objects[0]
|
first_item = tool.Ifc.get().by_id(first_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||||
|
|
||||||
representation = tool.Geometry.get_active_representation(ifc_obj)
|
while True:
|
||||||
if not representation:
|
is_part_of_boolean = False
|
||||||
self.report({"INFO"}, "No representation found for the selected IFC object.")
|
for inverse in tool.Ifc.get().get_inverse(first_item):
|
||||||
return {"FINISHED"}
|
if inverse.is_a("IfcBooleanResult"):
|
||||||
|
is_part_of_boolean = True
|
||||||
|
first_item = inverse
|
||||||
|
if not is_part_of_boolean:
|
||||||
|
break
|
||||||
|
|
||||||
# Apply objects as booleans.
|
# Don't replace style or aspect relationships.
|
||||||
booleans = []
|
to_replace = set(
|
||||||
for boolean_obj in non_ifc_objects:
|
[
|
||||||
if (
|
i
|
||||||
not boolean_obj.data
|
for i in tool.Ifc.get().get_inverse(first_item)
|
||||||
or not isinstance(boolean_obj.data, bpy.types.Mesh)
|
if i.is_a("IfcShapeRepresentation") or i.is_a("IfcBooleanResult")
|
||||||
or len(boolean_obj.data.polygons) <= 4
|
]
|
||||||
): # It takes 4 faces to create a closed solid
|
|
||||||
mesh_data = {
|
|
||||||
"type": "IfcHalfSpaceSolid",
|
|
||||||
"matrix": ifc_obj.matrix_world.inverted() @ boolean_obj.matrix_world,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
mesh_data = {
|
|
||||||
"type": "Mesh",
|
|
||||||
"blender_obj": ifc_obj,
|
|
||||||
"blender_void": boolean_obj,
|
|
||||||
}
|
|
||||||
|
|
||||||
booleans_ = ifcopenshell.api.geometry.add_boolean(
|
|
||||||
ifc_file,
|
|
||||||
representation=representation,
|
|
||||||
operator="DIFFERENCE",
|
|
||||||
**mesh_data,
|
|
||||||
)
|
|
||||||
booleans.extend(booleans_)
|
|
||||||
|
|
||||||
element = tool.Ifc.get_entity(ifc_obj)
|
|
||||||
assert element
|
|
||||||
tool.Model.mark_manual_booleans(element, booleans)
|
|
||||||
|
|
||||||
bonsai.core.geometry.switch_representation(
|
|
||||||
tool.Ifc,
|
|
||||||
tool.Geometry,
|
|
||||||
obj=ifc_obj,
|
|
||||||
representation=representation,
|
|
||||||
should_reload=True,
|
|
||||||
is_global=True,
|
|
||||||
should_sync_changes_first=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tool.Blender.remove_data_blocks(non_ifc_objects, remove_unused_data=True)
|
first = first_item
|
||||||
tool.Model.purge_scene_openings()
|
|
||||||
return {"FINISHED"}
|
booleans = set()
|
||||||
|
for second_obj in second_objs:
|
||||||
|
second = tool.Ifc.get().by_id(second_obj.data.BIMMeshProperties.ifc_definition_id)
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(second):
|
||||||
|
if inverse.is_a("IfcShapeRepresentation"):
|
||||||
|
inverse.Items = list(set(inverse.Items) - {second})
|
||||||
|
first = tool.Ifc.get().create_entity("IfcBooleanResult", props.operator, first, second)
|
||||||
|
booleans.add(first)
|
||||||
|
|
||||||
|
for inverse in to_replace:
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, first_item, first)
|
||||||
|
|
||||||
|
rep_obj = bpy.context.scene.BIMGeometryProperties.representation_obj
|
||||||
|
rep_element = tool.Ifc.get_entity(rep_obj)
|
||||||
|
tool.Model.mark_manual_booleans(rep_element, booleans)
|
||||||
|
tool.Geometry.reload_representation(rep_obj)
|
||||||
|
if props.is_editing:
|
||||||
|
bpy.ops.bim.enable_editing_booleans()
|
||||||
|
|
||||||
|
|
||||||
class ShowBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
class ShowBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
from bpy.props import PointerProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty
|
from bpy.props import PointerProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty, EnumProperty
|
||||||
|
|
||||||
|
|
||||||
class Boolean(PropertyGroup):
|
class Boolean(PropertyGroup):
|
||||||
@@ -36,3 +36,12 @@ class BIMBooleanProperties(PropertyGroup):
|
|||||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||||
booleans: CollectionProperty(name="Booleans", type=Boolean)
|
booleans: CollectionProperty(name="Booleans", type=Boolean)
|
||||||
active_boolean_index: IntProperty(name="Active Boolean Index")
|
active_boolean_index: IntProperty(name="Active Boolean Index")
|
||||||
|
operator: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("DIFFERENCE", "DIFFERENCE", ""),
|
||||||
|
("INTERSECTION", "INTERSECTION", ""),
|
||||||
|
("UNION", "UNION", ""),
|
||||||
|
],
|
||||||
|
name="Operator",
|
||||||
|
default="DIFFERENCE",
|
||||||
|
)
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ class BIM_PT_booleans(Panel):
|
|||||||
row.operator("bim.disable_editing_booleans", icon="CANCEL", text="")
|
row.operator("bim.disable_editing_booleans", icon="CANCEL", text="")
|
||||||
else:
|
else:
|
||||||
row.operator("bim.enable_editing_booleans", icon="IMPORT", text="")
|
row.operator("bim.enable_editing_booleans", icon="IMPORT", text="")
|
||||||
row.operator("bim.add_boolean", text="", icon="ADD")
|
|
||||||
booleans_are_manual = len(manual_booleans) == len(total_booleans)
|
booleans_are_manual = len(manual_booleans) == len(total_booleans)
|
||||||
op = row.operator(
|
op = row.operator(
|
||||||
"bim.booleans_mark_as_manual", text="", icon="PINNED" if booleans_are_manual else "UNPINNED"
|
"bim.booleans_mark_as_manual", text="", icon="PINNED" if booleans_are_manual else "UNPINNED"
|
||||||
@@ -173,6 +172,10 @@ class BIM_PT_booleans(Panel):
|
|||||||
if not props.is_editing:
|
if not props.is_editing:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(props, "operator", text="")
|
||||||
|
row.operator("bim.add_boolean", text="", icon="ADD")
|
||||||
|
|
||||||
self.layout.template_list("BIM_UL_booleans", "", props, "booleans", props, "active_boolean_index")
|
self.layout.template_list("BIM_UL_booleans", "", props, "booleans", props, "active_boolean_index")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user