mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Fix validate_type corruption; remove debug prints
When validate_type selected a preferred_item from remaining_items (e.g. the sole IfcBooleanResult in a representation), it left that item in the list. The subsequent Items filter removed every item, leaving Items=[] and causing guess_type to return "MappedRepresentation" — silently corrupting the representation. Also removes temporary debug print statements added during investigation of the wall-to-slab extension workflow. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -158,6 +158,9 @@ def regenerate_wall_to_underside(
|
||||
continue
|
||||
if ifc.is_moved(obj):
|
||||
geometry.run_edit_object_placement(obj=obj)
|
||||
# Sync each slab's Blender mesh to its current IFC representation before
|
||||
# reading face geometry, so a changed profile is picked up correctly.
|
||||
model.reload_body_representation(slab_objs)
|
||||
model.remove_wall_to_underside_booleans(wall)
|
||||
for slab_obj in slab_objs:
|
||||
clip = model.get_slab_clipping_bmesh(slab_obj)
|
||||
@@ -175,6 +178,13 @@ def extend_wall_to_slab(
|
||||
slab_objs: list[bpy.types.Object],
|
||||
wall_objs: list[bpy.types.Object],
|
||||
) -> None:
|
||||
# If any wall is currently in item mode, exit it before modifying the
|
||||
# representation. Leaving stale item objects around causes delete_ifc_item
|
||||
# to later remove the extrusion (or other pre-boolean items) from inside
|
||||
# the boolean chain, corrupting the IFC model.
|
||||
geom_props = geometry.get_geometry_props()
|
||||
if geom_props.representation_obj in wall_objs:
|
||||
geometry.disable_item_mode()
|
||||
clipped_walls = []
|
||||
for obj in wall_objs:
|
||||
if ifc.is_moved(obj):
|
||||
|
||||
@@ -225,7 +225,13 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
break
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, bpy.types.Mesh)
|
||||
item = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id)
|
||||
item_id = tool.Geometry.get_mesh_props(mesh).ifc_definition_id
|
||||
try:
|
||||
item = tool.Ifc.get().by_id(item_id)
|
||||
except RuntimeError:
|
||||
# Entity already deleted (e.g. removed as part of a sibling boolean collapse).
|
||||
bpy.data.objects.remove(obj)
|
||||
return
|
||||
rep_obj = props.representation_obj
|
||||
assert (rep_obj := props.representation_obj) and (rep_element := tool.Ifc.get_entity(rep_obj))
|
||||
cls.remove_representation_item(item, rep_element)
|
||||
@@ -1093,11 +1099,16 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
@classmethod
|
||||
def get_representation_item(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
data = obj.data
|
||||
if (
|
||||
isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
|
||||
and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
|
||||
and ((item := tool.Ifc.get().by_id(ifc_id)).is_a("IfcRepresentationItem"))
|
||||
):
|
||||
if not isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES):
|
||||
return None
|
||||
ifc_id = tool.Geometry.get_mesh_props(data).ifc_definition_id
|
||||
if not ifc_id:
|
||||
return None
|
||||
try:
|
||||
item = tool.Ifc.get().by_id(ifc_id)
|
||||
except RuntimeError:
|
||||
return None
|
||||
if item.is_a("IfcRepresentationItem"):
|
||||
return item
|
||||
return None
|
||||
|
||||
@@ -1210,6 +1221,8 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
cls, representation: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
if not representation.Items:
|
||||
return representation
|
||||
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
|
||||
@@ -319,6 +319,8 @@ class Model(bonsai.core.tool.Model):
|
||||
@classmethod
|
||||
def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Return first found IfcExtrudedAreaSolid"""
|
||||
if not representation.Items:
|
||||
return None
|
||||
item = representation.Items[0]
|
||||
while True:
|
||||
if item.is_a("IfcExtrudedAreaSolid"):
|
||||
@@ -839,11 +841,23 @@ class Model(bonsai.core.tool.Model):
|
||||
manual_booleans = cls.get_manual_booleans(wall)
|
||||
if not manual_booleans:
|
||||
return
|
||||
mesh_operands = [
|
||||
b.SecondOperand for b in manual_booleans if b.SecondOperand.is_a("IfcTessellatedFaceSet")
|
||||
]
|
||||
for mesh in mesh_operands:
|
||||
tool.Geometry.remove_representation_item(mesh, wall)
|
||||
ifc_file = tool.Ifc.get()
|
||||
for b in manual_booleans:
|
||||
sec = b.SecondOperand
|
||||
if sec is None:
|
||||
# The IfcPolygonalFaceSet was already deleted externally. Splice the
|
||||
# orphaned IfcBooleanResult out of the chain so the representation stays valid.
|
||||
parents = list(ifc_file.get_inverse(b))
|
||||
for parent in parents:
|
||||
if parent.is_a("IfcBooleanResult") and parent.FirstOperand == b:
|
||||
parent.FirstOperand = b.FirstOperand
|
||||
elif parent.is_a("IfcShapeRepresentation"):
|
||||
new_items = tuple((set(parent.Items) - {b}) | {b.FirstOperand})
|
||||
parent.Items = new_items
|
||||
cls.unmark_manual_booleans(wall, [b.id()])
|
||||
ifc_file.remove(b)
|
||||
elif sec.is_a("IfcTessellatedFaceSet"):
|
||||
tool.Geometry.remove_representation_item(sec, wall)
|
||||
|
||||
@classmethod
|
||||
def get_manual_booleans(
|
||||
@@ -857,7 +871,8 @@ class Model(bonsai.core.tool.Model):
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return []
|
||||
booleans = [b for b in cls.get_booleans(element, representation) if b.id() in boolean_ids]
|
||||
all_chain_booleans = cls.get_booleans(element, representation)
|
||||
booleans = [b for b in all_chain_booleans if b.id() in boolean_ids]
|
||||
return booleans
|
||||
|
||||
@classmethod
|
||||
@@ -2420,6 +2435,7 @@ class Model(bonsai.core.tool.Model):
|
||||
clipping_bm = bmesh.new()
|
||||
vertex_map = {}
|
||||
|
||||
kept = 0
|
||||
for face in bm.faces:
|
||||
face.normal_update()
|
||||
normal = face.normal.to_4d()
|
||||
@@ -2427,6 +2443,7 @@ class Model(bonsai.core.tool.Model):
|
||||
world_normal_z = (obj.matrix_world @ normal).z
|
||||
if world_normal_z >= -0.5:
|
||||
continue
|
||||
kept += 1
|
||||
new_verts = []
|
||||
for vert in face.verts:
|
||||
if not (new_vert := vertex_map.get(vert.index, None)):
|
||||
@@ -2517,6 +2534,7 @@ class Model(bonsai.core.tool.Model):
|
||||
extrusion.Depth = max_z / direction[2]
|
||||
|
||||
if operands:
|
||||
body_repr = ifcopenshell.util.representation.get_representation(wall, "Model", "Body", "MODEL_VIEW")
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(
|
||||
ifc_file, first_item=extrusion, second_items=operands
|
||||
)
|
||||
|
||||
@@ -81,6 +81,13 @@ def validate_type(
|
||||
if not preferred_item and remaining_items:
|
||||
preferred_item = remaining_items[0]
|
||||
|
||||
# preferred_item must not appear in remaining_items — if it was selected from
|
||||
# that list, leaving it in causes add_boolean to union it with itself, and the
|
||||
# subsequent Items filter then removes ALL items (including preferred_item),
|
||||
# leaving Items=[] which guess_type maps to "MappedRepresentation".
|
||||
if preferred_item in remaining_items:
|
||||
remaining_items = [i for i in remaining_items if i != preferred_item]
|
||||
|
||||
if remaining_items:
|
||||
ifcopenshell.api.geometry.add_boolean(file, preferred_item, remaining_items, "UNION")
|
||||
representation.Items = [i for i in representation.Items if i not in remaining_items]
|
||||
|
||||
Reference in New Issue
Block a user