See #5972. Boolean operands are now loaded in item editing mode and are editable.

This commit is contained in:
Dion Moult
2025-01-16 21:28:05 +11:00
parent 21624d6065
commit 2fe4680ca1
4 changed files with 72 additions and 27 deletions
@@ -36,15 +36,18 @@ class ItemDecorator:
if cls.is_installed:
cls.uninstall()
obj_is_selected = {}
obj_is_boolean = {}
objs = {}
for item_obj in context.scene.BIMGeometryProperties.item_objs:
if obj := item_obj.obj:
objs[obj.name] = cls.get_obj_data(obj)
obj_is_selected[obj.name] = obj.select_get()
obj_is_boolean[obj.name] = item_obj.is_boolean
handler = cls()
handler.objs = objs
handler.obj_is_selected = obj_is_selected
handler.obj_is_boolean = obj_is_boolean
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw, (context,), "WINDOW", "POST_VIEW"))
cls.is_installed = True
@@ -138,7 +141,7 @@ class ItemDecorator:
self.line_shader.bind() # required to be able to change uniforms of the shader
# POLYLINE_UNIFORM_COLOR specific uniforms
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
self.line_shader.uniform_float("lineWidth", 1.0)
self.line_shader.uniform_float("lineWidth", 2.0)
# general shader
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
@@ -154,6 +157,9 @@ class ItemDecorator:
continue
self.draw_batch("LINES", data["verts"], selected_elements_color, data["edges"])
self.draw_batch("TRIS", data["verts"], transparent_color(selected_elements_color), data["tris"])
else:
self.draw_batch("LINES", data["verts"], decorator_color_background, data["edges"])
elif self.obj_is_boolean[obj_name]:
self.draw_batch("LINES", data["verts"], special_elements_color, data["edges"])
self.draw_batch("TRIS", data["verts"], transparent_color(special_elements_color), data["tris"])
else:
self.draw_batch("LINES", data["verts"], transparent_color(unselected_elements_color, alpha=0.2), data["edges"])
self.draw_batch("TRIS", data["verts"], transparent_color(special_elements_color), data["tris"])
@@ -2694,8 +2694,19 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
data = obj.data
assert data
item_ids = data["ios_item_ids"] if "ios_item_ids" in data else data["ios_edges_item_ids"]
for item_id in set(item_ids):
queue = list(set(item_ids))
processed_ids = set()
boolean_ids = set()
while queue:
item_id = queue.pop()
if item_id in processed_ids:
continue # In theory, an item can be used multiple times (e.g. in a boolean stack).
item = tool.Ifc.get().by_id(item_id)
if item.is_a("IfcBooleanResult"):
queue.append(item.FirstOperand.id())
queue.append(item.SecondOperand.id())
boolean_ids.add(item.SecondOperand.id())
continue
item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}")
item_mesh.BIMMeshProperties.ifc_definition_id = item_id
@@ -2705,6 +2716,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
new = props.item_objs.add()
new.obj = item_obj
new.is_boolean = item_id in boolean_ids
tool.Geometry.import_item(item_obj)
tool.Geometry.import_item_attributes(item_obj)
@@ -2712,11 +2724,9 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
if not tool.Geometry.is_movable(item):
tool.Geometry.lock_object(item_obj)
if "IfcOpeningElement" in obj.name:
item_obj.display_type = "WIRE"
item_obj.select_set(True) # so you can quickly hit tab again, for edit mode
context.view_layer.objects.active = item_obj
processed_ids.add(item_id)
tool.Root.reload_item_decorator()
@@ -131,10 +131,7 @@ class RepresentationItemObject(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
obj: PointerProperty(type=bpy.types.Object)
verts: StringProperty(name="Verts")
edges: StringProperty(name="Edges")
special_verts: StringProperty(name="Special Verts")
special_edges: StringProperty(name="Special Edges")
is_boolean: BoolProperty(name="Is Boolean", default=False)
class ShapeAspect(PropertyGroup):
+48 -16
View File
@@ -952,7 +952,7 @@ class Geometry(bonsai.core.tool.Geometry):
@classmethod
def is_movable(cls, item: ifcopenshell.entity_instance) -> bool:
return item.is_a("IfcSweptAreaSolid")
return item.is_a("IfcSweptAreaSolid") or item.is_a("IfcHalfSpaceSolid")
@classmethod
def is_profile_based(cls, data: bpy.types.Mesh) -> bool:
@@ -1571,12 +1571,10 @@ class Geometry(bonsai.core.tool.Geometry):
has_changed = False
for item_obj in props.item_objs:
if not (obj := item_obj.obj):
if not (obj := item_obj.obj) or not tool.Ifc.is_moved(obj):
continue
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if is_swept_area := item.is_a("IfcSweptAreaSolid"):
if not tool.Ifc.is_moved(obj):
continue
has_changed = True
old_position = item.Position
@@ -1591,6 +1589,25 @@ class Geometry(bonsai.core.tool.Geometry):
item.Position = builder.create_axis2_placement_3d_from_matrix(position)
if old_position:
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position)
if item.is_a("IfcHalfSpaceSolid"):
has_changed = True
surface = item.BaseSurface
if surface.is_a("IfcPlane"):
position = surface.Position
m = Matrix(ifcopenshell.util.placement.get_axis2placement(position).tolist())
m.translation *= unit_scale
new_m = rep_obj.matrix_world.inverted() @ obj.matrix_world
new_m.normalize()
new_m.translation /= unit_scale
new_m = np.array(new_m)
surface.Position = builder.create_axis2_placement_3d_from_matrix(new_m)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), position)
else:
self.report(
{"INFO"},
f"Editing boolean using non-IfcPlane IfcSurface ({surface.is_a()}) is not supported.",
)
if has_changed:
cls.reload_representation(rep_obj)
@@ -1631,21 +1648,36 @@ class Geometry(bonsai.core.tool.Geometry):
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
obj.data.clear_geometry()
geometry = tool.Loader.create_generic_shape(item)
if (cartesian_point_offset := cls.get_cartesian_point_offset(rep_obj)) is not None:
verts_array = np.array(geometry.verts)
offset = np.array([-cartesian_point_offset[0], -cartesian_point_offset[1], -cartesian_point_offset[2]])
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
verts = offset_verts.tolist()
if item.is_a("IfcHalfSpaceSolid"):
bm = bmesh.new()
bmesh.ops.create_grid(bm, size=0.5)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
bm.to_mesh(obj.data)
bm.free()
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
position = item.BaseSurface.Position
position = Matrix(ifcopenshell.util.placement.get_axis2placement(position).tolist())
position.translation *= unit_scale
obj.matrix_world = rep_obj.matrix_world @ position
else:
verts = geometry.verts
tool.Loader.convert_geometry_to_mesh(geometry, obj.data, verts=verts)
geometry = tool.Loader.create_generic_shape(item)
if (cartesian_point_offset := cls.get_cartesian_point_offset(rep_obj)) is not None:
verts_array = np.array(geometry.verts)
offset = np.array([-cartesian_point_offset[0], -cartesian_point_offset[1], -cartesian_point_offset[2]])
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
verts = offset_verts.tolist()
else:
verts = geometry.verts
tool.Loader.convert_geometry_to_mesh(geometry, obj.data, verts=verts)
if ios_materials := list(obj.data["ios_materials"]):
material = tool.Ifc.get_object(tool.Ifc.get().by_id(ios_materials[0]))
obj.data.materials.append(material)
if ios_materials := list(obj.data["ios_materials"]):
material = tool.Ifc.get_object(tool.Ifc.get().by_id(ios_materials[0]))
obj.data.materials.append(material)
obj.matrix_world = rep_obj.matrix_world.copy()
obj.matrix_world = rep_obj.matrix_world.copy()
if is_swept_area := item.is_a("IfcSweptAreaSolid"):
position = item.Position