mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 06:58:56 +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()
|
props = tool.Geometry.get_geometry_props()
|
||||||
item = tool.Geometry.get_active_representation(obj)
|
item = tool.Geometry.get_active_representation(obj)
|
||||||
assert item
|
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_meshlike_item(item):
|
||||||
if tool.Geometry.is_geometric_data(obj.data) and (
|
if tool.Geometry.is_geometric_data(obj.data) and (
|
||||||
item.is_a("IfcVertex") or item.is_a("IfcEdge") or obj.data.polygons
|
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.mode = "EDIT"
|
||||||
props.is_changing_mode = False
|
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):
|
class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.direct_profile_edit"
|
bl_idname = "bim.direct_profile_edit"
|
||||||
@@ -3374,6 +3407,17 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
tool.Root.reload_item_decorator()
|
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):
|
class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.update_item_attributes"
|
bl_idname = "bim.update_item_attributes"
|
||||||
|
|||||||
@@ -1904,6 +1904,17 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
obj.data.transform(transformation_i)
|
obj.data.transform(transformation_i)
|
||||||
cls.record_object_position(obj)
|
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
|
@classmethod
|
||||||
def disable_item_mode(cls) -> None:
|
def disable_item_mode(cls) -> None:
|
||||||
props = tool.Geometry.get_geometry_props()
|
props = tool.Geometry.get_geometry_props()
|
||||||
@@ -2340,3 +2351,94 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
continue
|
continue
|
||||||
objects.add(obj)
|
objects.add(obj)
|
||||||
return objects
|
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