mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 19:07:57 +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:
@@ -176,6 +176,9 @@ def regenerate_wall_to_underside(
|
|||||||
continue
|
continue
|
||||||
if ifc.is_moved(obj):
|
if ifc.is_moved(obj):
|
||||||
geometry.run_edit_object_placement(obj=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)
|
model.remove_wall_to_underside_booleans(wall)
|
||||||
for slab_obj in slab_objs:
|
for slab_obj in slab_objs:
|
||||||
clip = model.get_slab_clipping_bmesh(slab_obj)
|
clip = model.get_slab_clipping_bmesh(slab_obj)
|
||||||
@@ -193,6 +196,13 @@ def extend_wall_to_slab(
|
|||||||
slab_objs: list[bpy.types.Object],
|
slab_objs: list[bpy.types.Object],
|
||||||
wall_objs: list[bpy.types.Object],
|
wall_objs: list[bpy.types.Object],
|
||||||
) -> None:
|
) -> 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 = []
|
clipped_walls = []
|
||||||
for obj in wall_objs:
|
for obj in wall_objs:
|
||||||
if ifc.is_moved(obj):
|
if ifc.is_moved(obj):
|
||||||
|
|||||||
@@ -257,7 +257,13 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
break
|
break
|
||||||
mesh = obj.data
|
mesh = obj.data
|
||||||
assert isinstance(mesh, bpy.types.Mesh)
|
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
|
rep_obj = props.representation_obj
|
||||||
assert (rep_obj := props.representation_obj) and (rep_element := tool.Ifc.get_entity(rep_obj))
|
assert (rep_obj := props.representation_obj) and (rep_element := tool.Ifc.get_entity(rep_obj))
|
||||||
cls.remove_representation_item(item, rep_element)
|
cls.remove_representation_item(item, rep_element)
|
||||||
@@ -1157,11 +1163,16 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_representation_item(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
def get_representation_item(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||||
data = obj.data
|
data = obj.data
|
||||||
if (
|
if not isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES):
|
||||||
isinstance(data, Geometry.TYPES_WITH_MESH_PROPERTIES)
|
return None
|
||||||
and (ifc_id := tool.Geometry.get_mesh_props(data).ifc_definition_id)
|
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 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 item
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1335,6 +1346,8 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
cls, representation: ifcopenshell.entity_instance
|
cls, representation: ifcopenshell.entity_instance
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
if representation.RepresentationType == "MappedRepresentation":
|
if representation.RepresentationType == "MappedRepresentation":
|
||||||
|
if not representation.Items:
|
||||||
|
return representation
|
||||||
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||||
return representation
|
return representation
|
||||||
|
|
||||||
|
|||||||
@@ -351,6 +351,8 @@ class Model(bonsai.core.tool.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||||
"""Return first found IfcExtrudedAreaSolid"""
|
"""Return first found IfcExtrudedAreaSolid"""
|
||||||
|
if not representation.Items:
|
||||||
|
return None
|
||||||
item = representation.Items[0]
|
item = representation.Items[0]
|
||||||
while True:
|
while True:
|
||||||
if item.is_a("IfcExtrudedAreaSolid"):
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
@@ -871,11 +873,23 @@ class Model(bonsai.core.tool.Model):
|
|||||||
manual_booleans = cls.get_manual_booleans(wall)
|
manual_booleans = cls.get_manual_booleans(wall)
|
||||||
if not manual_booleans:
|
if not manual_booleans:
|
||||||
return
|
return
|
||||||
mesh_operands = [
|
ifc_file = tool.Ifc.get()
|
||||||
b.SecondOperand for b in manual_booleans if b.SecondOperand.is_a("IfcTessellatedFaceSet")
|
for b in manual_booleans:
|
||||||
]
|
sec = b.SecondOperand
|
||||||
for mesh in mesh_operands:
|
if sec is None:
|
||||||
tool.Geometry.remove_representation_item(mesh, wall)
|
# 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
|
@classmethod
|
||||||
def get_manual_booleans(
|
def get_manual_booleans(
|
||||||
@@ -889,7 +903,8 @@ class Model(bonsai.core.tool.Model):
|
|||||||
representation = tool.Geometry.get_body_representation(element)
|
representation = tool.Geometry.get_body_representation(element)
|
||||||
if not representation:
|
if not representation:
|
||||||
return []
|
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
|
return booleans
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -2591,6 +2606,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
clipping_bm = bmesh.new()
|
clipping_bm = bmesh.new()
|
||||||
vertex_map = {}
|
vertex_map = {}
|
||||||
|
|
||||||
|
kept = 0
|
||||||
for face in bm.faces:
|
for face in bm.faces:
|
||||||
face.normal_update()
|
face.normal_update()
|
||||||
normal = face.normal.to_4d()
|
normal = face.normal.to_4d()
|
||||||
@@ -2598,6 +2614,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
world_normal_z = (obj.matrix_world @ normal).z
|
world_normal_z = (obj.matrix_world @ normal).z
|
||||||
if world_normal_z >= -0.5:
|
if world_normal_z >= -0.5:
|
||||||
continue
|
continue
|
||||||
|
kept += 1
|
||||||
new_verts = []
|
new_verts = []
|
||||||
for vert in face.verts:
|
for vert in face.verts:
|
||||||
if not (new_vert := vertex_map.get(vert.index, None)):
|
if not (new_vert := vertex_map.get(vert.index, None)):
|
||||||
@@ -2688,6 +2705,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
extrusion.Depth = max_z / direction[2]
|
extrusion.Depth = max_z / direction[2]
|
||||||
|
|
||||||
if operands:
|
if operands:
|
||||||
|
body_repr = ifcopenshell.util.representation.get_representation(wall, "Model", "Body", "MODEL_VIEW")
|
||||||
booleans = ifcopenshell.api.geometry.add_boolean(
|
booleans = ifcopenshell.api.geometry.add_boolean(
|
||||||
ifc_file, first_item=extrusion, second_items=operands
|
ifc_file, first_item=extrusion, second_items=operands
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -81,6 +81,13 @@ def validate_type(
|
|||||||
if not preferred_item and remaining_items:
|
if not preferred_item and remaining_items:
|
||||||
preferred_item = remaining_items[0]
|
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:
|
if remaining_items:
|
||||||
ifcopenshell.api.geometry.add_boolean(file, preferred_item, remaining_items, "UNION")
|
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]
|
representation.Items = [i for i in representation.Items if i not in remaining_items]
|
||||||
|
|||||||
Reference in New Issue
Block a user