mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
This commit is contained in:
@@ -2409,6 +2409,14 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
item = tool.Geometry.get_active_representation(obj)
|
||||
assert item
|
||||
|
||||
# Fix vertex order for annotation items
|
||||
rep_obj = props.representation_obj
|
||||
if rep_obj:
|
||||
element = tool.Ifc.get_entity(rep_obj)
|
||||
if element and self._is_annotation_object(element):
|
||||
tool.Geometry.ensure_annotation_vertex_order(obj)
|
||||
|
||||
if tool.Geometry.is_meshlike_item(item):
|
||||
if tool.Geometry.is_geometric_data(obj.data) and (
|
||||
item.is_a("IfcVertex") or item.is_a("IfcEdge") or obj.data.polygons
|
||||
@@ -2548,6 +2556,31 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.mode = "EDIT"
|
||||
props.is_changing_mode = False
|
||||
|
||||
def _is_annotation_object(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""Check if element is an annotation object that needs vertex order preservation."""
|
||||
if not element.is_a("IfcAnnotation"):
|
||||
return False
|
||||
|
||||
# Object types that have semantic vertex order (arrow direction, text position)
|
||||
annotation_types_with_order = {
|
||||
"TEXT_LEADER",
|
||||
"DIMENSION",
|
||||
"RADIUS",
|
||||
"DIAMETER",
|
||||
"ANGLE",
|
||||
"FALL",
|
||||
"SLOPE_ANGLE",
|
||||
"SLOPE_FRACTION",
|
||||
"SLOPE_PERCENT",
|
||||
"STAIR_ARROW",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"SECTION",
|
||||
"ELEVATION",
|
||||
}
|
||||
|
||||
return element.ObjectType in annotation_types_with_order
|
||||
|
||||
|
||||
class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.direct_profile_edit"
|
||||
@@ -3374,6 +3407,17 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
tool.Root.reload_item_decorator()
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType in {
|
||||
"TEXT_LEADER", "DIMENSION", "RADIUS", "DIAMETER", "ANGLE",
|
||||
"FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT",
|
||||
"STAIR_ARROW", "PLAN_LEVEL", "SECTION_LEVEL", "SECTION", "ELEVATION"
|
||||
}:
|
||||
for item_obj_data in props.item_objs:
|
||||
item_obj = item_obj_data.obj
|
||||
if item_obj and isinstance(item_obj.data, bpy.types.Mesh) and item_obj.data.vertices:
|
||||
item_obj.data["bonsai_first_vert_co"] = item_obj.data.vertices[0].co[:]
|
||||
|
||||
|
||||
class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.update_item_attributes"
|
||||
|
||||
@@ -1904,6 +1904,17 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
obj.data.transform(transformation_i)
|
||||
cls.record_object_position(obj)
|
||||
|
||||
# ADD THIS AT THE END - Store initial vertex order for annotations
|
||||
if rep_obj and (element := tool.Ifc.get_entity(rep_obj)):
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType in {
|
||||
"TEXT_LEADER", "DIMENSION", "RADIUS", "DIAMETER", "ANGLE",
|
||||
"FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT",
|
||||
"STAIR_ARROW", "PLAN_LEVEL", "SECTION_LEVEL", "SECTION", "ELEVATION"
|
||||
}:
|
||||
# Store the initial first vertex position
|
||||
if isinstance(obj.data, bpy.types.Mesh) and obj.data.vertices:
|
||||
obj.data["bonsai_first_vert_co"] = obj.data.vertices[0].co[:]
|
||||
|
||||
@classmethod
|
||||
def disable_item_mode(cls) -> None:
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
@@ -2340,3 +2351,94 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
continue
|
||||
objects.add(obj)
|
||||
return objects
|
||||
|
||||
@classmethod
|
||||
def ensure_annotation_vertex_order(cls, obj: bpy.types.Object) -> None:
|
||||
"""
|
||||
Ensure vertices form a continuous path from start to end.
|
||||
Uses the original first vertex position as a reference point.
|
||||
"""
|
||||
mesh = obj.data
|
||||
if not isinstance(mesh, bpy.types.Mesh):
|
||||
return
|
||||
|
||||
# Get the original first vertex position from custom properties
|
||||
if "bonsai_first_vert_co" in mesh:
|
||||
original_first_co = Vector(mesh["bonsai_first_vert_co"])
|
||||
else:
|
||||
# Store it for next time
|
||||
if mesh.vertices:
|
||||
original_first_co = Vector(mesh.vertices[0].co)
|
||||
mesh["bonsai_first_vert_co"] = original_first_co[:]
|
||||
else:
|
||||
return
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
if len(bm.verts) == 0:
|
||||
bm.free()
|
||||
return
|
||||
|
||||
# Find endpoints (vertices with only one connected edge)
|
||||
endpoints = [v for v in bm.verts if len(v.link_edges) == 1]
|
||||
|
||||
# Choose the endpoint closest to the original first vertex position
|
||||
if len(endpoints) == 0:
|
||||
# Closed loop - pick any vertex as start
|
||||
start_vert = bm.verts[0]
|
||||
elif len(endpoints) == 1:
|
||||
# Single endpoint
|
||||
start_vert = endpoints[0]
|
||||
else:
|
||||
# Choose endpoint closest to where the original first vertex was
|
||||
start_vert = min(endpoints, key=lambda v: (v.co - original_first_co).length)
|
||||
|
||||
# Build ordered vertex list by following edges
|
||||
ordered_verts = [start_vert]
|
||||
current_vert = start_vert
|
||||
visited_edges = set()
|
||||
|
||||
while True:
|
||||
# Find next unvisited edge
|
||||
next_edge = None
|
||||
for edge in current_vert.link_edges:
|
||||
if edge not in visited_edges:
|
||||
next_edge = edge
|
||||
break
|
||||
|
||||
if not next_edge:
|
||||
break
|
||||
|
||||
visited_edges.add(next_edge)
|
||||
next_vert = next_edge.other_vert(current_vert)
|
||||
|
||||
# Avoid going back on ourselves
|
||||
if next_vert not in ordered_verts:
|
||||
ordered_verts.append(next_vert)
|
||||
|
||||
current_vert = next_vert
|
||||
|
||||
# Store vertex coordinates in the correct order
|
||||
new_verts_co = [v.co.copy() for v in ordered_verts]
|
||||
|
||||
# Update the stored first vertex position to the new first vertex
|
||||
mesh["bonsai_first_vert_co"] = new_verts_co[0][:]
|
||||
|
||||
# Clear and rebuild mesh with correct vertex order
|
||||
bm.clear()
|
||||
|
||||
# Create new vertices in order
|
||||
new_verts = [bm.verts.new(co) for co in new_verts_co]
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
# Create edges connecting consecutive vertices
|
||||
for i in range(len(new_verts) - 1):
|
||||
bm.edges.new([new_verts[i], new_verts[i + 1]])
|
||||
|
||||
# Write back to mesh
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
mesh.update()
|
||||
Reference in New Issue
Block a user