mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 22:43:41 +00:00
Fix #5482. Reimplement handling of arc detection to allow consecutive arcs.
This commit is contained in:
@@ -120,174 +120,6 @@ class Helper:
|
|||||||
|
|
||||||
return {"profile": profile, "extrusion": extrusion}
|
return {"profile": profile, "extrusion": extrusion}
|
||||||
|
|
||||||
|
|
||||||
def auto_detect_arbitrary_profile_with_voids(
|
|
||||||
self, obj: bpy.types.Object, mesh: bpy.types.Mesh
|
|
||||||
) -> Union[tuple, dict]:
|
|
||||||
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
|
|
||||||
for i, group in enumerate(obj.vertex_groups):
|
|
||||||
if "IFCARCINDEX" in group.name:
|
|
||||||
groups["IFCARCINDEX"].append(i)
|
|
||||||
elif "IFCCIRCLE" in group.name:
|
|
||||||
groups["IFCCIRCLE"].append(i)
|
|
||||||
|
|
||||||
bm = bmesh.new()
|
|
||||||
bm.from_mesh(mesh)
|
|
||||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
|
||||||
bmesh.ops.delete(bm, geom=bm.faces, context="FACES_ONLY")
|
|
||||||
|
|
||||||
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
|
||||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
|
||||||
deform_layer = bm.verts.layers.deform.active
|
|
||||||
|
|
||||||
# Sanity check
|
|
||||||
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
|
|
||||||
for vert in bm.verts:
|
|
||||||
total_groups = 0
|
|
||||||
is_circle = False
|
|
||||||
for group_type, group_indices in groups.items():
|
|
||||||
if not group_indices:
|
|
||||||
continue
|
|
||||||
is_special, group_index = tool.Blender.bmesh_check_vertex_in_groups(vert, deform_layer, group_indices)
|
|
||||||
if not is_special:
|
|
||||||
continue
|
|
||||||
if group_type == "IFCCIRCLE":
|
|
||||||
is_circle = True
|
|
||||||
group_verts[group_type].setdefault(group_index, 0)
|
|
||||||
group_verts[group_type][group_index] += 1
|
|
||||||
total_groups += 0
|
|
||||||
if total_groups > 1: # A vert can only belong to one group
|
|
||||||
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
|
|
||||||
elif is_circle:
|
|
||||||
pass # Circles are allowed to be unclosed
|
|
||||||
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
|
|
||||||
return (False, "UNCLOSED_LOOP")
|
|
||||||
|
|
||||||
for group_type, group_counts in group_verts.items():
|
|
||||||
if group_type == "IFCARCINDEX":
|
|
||||||
for group_count in group_counts.values():
|
|
||||||
if group_count != 3: # Each arc needs 3 verts
|
|
||||||
return (False, "3POINT_ARC")
|
|
||||||
elif group_type == "IFCCIRCLE":
|
|
||||||
for group_count in group_counts.values():
|
|
||||||
if group_count != 2: # Each circle needs 2 verts
|
|
||||||
return (False, "CIRCLE")
|
|
||||||
|
|
||||||
loop_edges = set(bm.edges)
|
|
||||||
|
|
||||||
# Create loops from edges
|
|
||||||
loops = []
|
|
||||||
while loop_edges:
|
|
||||||
edge = loop_edges.pop()
|
|
||||||
loop = [edge]
|
|
||||||
has_found_connected_edge = True
|
|
||||||
while has_found_connected_edge:
|
|
||||||
has_found_connected_edge = False
|
|
||||||
for edge in loop_edges.copy():
|
|
||||||
edge_verts = set(edge.verts)
|
|
||||||
if edge_verts & set(loop[0].verts):
|
|
||||||
loop.insert(0, edge)
|
|
||||||
loop_edges.remove(edge)
|
|
||||||
has_found_connected_edge = True
|
|
||||||
elif edge_verts & set(loop[-1].verts):
|
|
||||||
loop.append(edge)
|
|
||||||
loop_edges.remove(edge)
|
|
||||||
has_found_connected_edge = True
|
|
||||||
loops.append(loop)
|
|
||||||
|
|
||||||
# Determine outer loop
|
|
||||||
max_area = 0
|
|
||||||
outer_loop = None
|
|
||||||
inner_loops = []
|
|
||||||
|
|
||||||
for loop in loops:
|
|
||||||
loop_vertices = []
|
|
||||||
total_edges = len(loop)
|
|
||||||
|
|
||||||
if total_edges == 1:
|
|
||||||
loop_vertices = [loop[0].verts[0], loop[0].verts[1]]
|
|
||||||
else:
|
|
||||||
for i, edge in enumerate(loop):
|
|
||||||
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
|
|
||||||
loop_vertices.append(edge.verts[0])
|
|
||||||
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
|
|
||||||
loop_vertices.append(edge.verts[1])
|
|
||||||
elif edge.verts[0] in loop[i + 1].verts:
|
|
||||||
loop_vertices.append(edge.verts[1])
|
|
||||||
elif edge.verts[1] in loop[i + 1].verts:
|
|
||||||
loop_vertices.append(edge.verts[0])
|
|
||||||
|
|
||||||
if len(loop_vertices) == 2:
|
|
||||||
# Unofficial convention right now that a loop of 2 verts always is a circle
|
|
||||||
radius = (loop_vertices[0].co - loop_vertices[1].co).length / 2
|
|
||||||
face_area = pi * pow(radius, 2)
|
|
||||||
else:
|
|
||||||
loop_bm = bmesh.new()
|
|
||||||
for vert in loop_vertices:
|
|
||||||
loop_bm.verts.new(vert.co)
|
|
||||||
face = loop_bm.faces.new(loop_bm.verts)
|
|
||||||
face_area = face.calc_area()
|
|
||||||
loop_bm.free()
|
|
||||||
|
|
||||||
loop_vertex_indices = []
|
|
||||||
active_arc_id = None
|
|
||||||
arc_stack = []
|
|
||||||
last_index = len(loop_vertices) - 1
|
|
||||||
|
|
||||||
# It is possible to loop through loop_vertices halfway through an arc.
|
|
||||||
# If that is the case, we store it in incomplete_arc
|
|
||||||
incomplete_arc = []
|
|
||||||
for i, v in enumerate(loop_vertices):
|
|
||||||
if len(arc_stack) == 3:
|
|
||||||
loop_vertex_indices.append(arc_stack)
|
|
||||||
loop_vertex_indices.append((arc_stack[-1], v.index))
|
|
||||||
arc_stack = []
|
|
||||||
active_arc_id = None
|
|
||||||
|
|
||||||
is_arc = False
|
|
||||||
for group_index in groups["IFCARCINDEX"]:
|
|
||||||
if group_index in v[deform_layer]:
|
|
||||||
if active_arc_id is not None and active_arc_id != group_index:
|
|
||||||
incomplete_arc = arc_stack
|
|
||||||
arc_stack = []
|
|
||||||
active_arc_id = group_index
|
|
||||||
is_arc = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if i == last_index and not is_arc:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if is_arc:
|
|
||||||
arc_stack.append(v.index)
|
|
||||||
else:
|
|
||||||
if active_arc_id is not None:
|
|
||||||
incomplete_arc = arc_stack
|
|
||||||
active_arc_id = None
|
|
||||||
arc_stack = []
|
|
||||||
loop_vertex_indices.append((v.index, loop_vertices[i + 1].index))
|
|
||||||
|
|
||||||
if active_arc_id is not None:
|
|
||||||
arc_stack.extend(incomplete_arc)
|
|
||||||
loop_vertex_indices.append(arc_stack)
|
|
||||||
|
|
||||||
loop_vertex_indices.append((loop_vertex_indices[-1][-1], loop_vertex_indices[0][0]))
|
|
||||||
|
|
||||||
if face_area > max_area:
|
|
||||||
max_area = face_area
|
|
||||||
outer_loop = loop_vertex_indices
|
|
||||||
inner_loops.append(loop_vertex_indices)
|
|
||||||
|
|
||||||
inner_loops.remove(outer_loop)
|
|
||||||
|
|
||||||
# Copy vectors to prevent random data mangling after bmesh is freed.
|
|
||||||
points = [v.co.copy() for v in bm.verts]
|
|
||||||
|
|
||||||
bm.to_mesh(mesh)
|
|
||||||
mesh.update()
|
|
||||||
bm.free()
|
|
||||||
|
|
||||||
return {"points": points, "profile": outer_loop, "inner_curves": inner_loops}
|
|
||||||
|
|
||||||
# An arbitrary closed profile with voids is similar to one without voids.
|
# An arbitrary closed profile with voids is similar to one without voids.
|
||||||
# We start the same way with any ngon (no tri), but instead of being the entire
|
# We start the same way with any ngon (no tri), but instead of being the entire
|
||||||
# profile, it is only one of the possible faces that make up the end of our
|
# profile, it is only one of the possible faces that make up the end of our
|
||||||
|
|||||||
@@ -144,15 +144,13 @@ class ProfileDecorator:
|
|||||||
# deform_layer is None if there are no verts assigned to vertex groups
|
# deform_layer is None if there are no verts assigned to vertex groups
|
||||||
# even if there are vertex groups in the obj.vertex_groups
|
# even if there are vertex groups in the obj.vertex_groups
|
||||||
if deform_layer:
|
if deform_layer:
|
||||||
is_arc, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, arc_groups)
|
for group_index in tool.Blender.bmesh_get_vertex_groups(vertex, deform_layer):
|
||||||
if is_arc:
|
if is_arc := group_index in arc_groups:
|
||||||
arcs.setdefault(group_index, []).append(vertex)
|
arcs.setdefault(group_index, []).append(vertex)
|
||||||
special_vertex_indices[vertex.index] = group_index
|
special_vertex_indices[vertex.index] = group_index
|
||||||
|
if is_circle := group_index in circle_groups:
|
||||||
is_circle, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, circle_groups)
|
circles.setdefault(group_index, []).append(vertex)
|
||||||
if is_circle:
|
special_vertex_indices[vertex.index] = group_index
|
||||||
circles.setdefault(group_index, []).append(vertex)
|
|
||||||
special_vertex_indices[vertex.index] = group_index
|
|
||||||
|
|
||||||
if vertex.select:
|
if vertex.select:
|
||||||
selected_vertices.append(co)
|
selected_vertices.append(co)
|
||||||
|
|||||||
@@ -696,9 +696,6 @@ class SetArcIndex(bpy.types.Operator):
|
|||||||
return self.cancel_message("Select 3 vertices.")
|
return self.cancel_message("Select 3 vertices.")
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode="OBJECT")
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
in_group = any([bool(obj.data.vertices[v].groups) for v in selected_vertices])
|
|
||||||
for group in obj.vertex_groups:
|
|
||||||
group.remove(selected_vertices)
|
|
||||||
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
||||||
group.add(selected_vertices, 1, "REPLACE")
|
group.add(selected_vertices, 1, "REPLACE")
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
|
|||||||
@@ -712,6 +712,16 @@ class Blender(bonsai.core.tool.Blender):
|
|||||||
return True, group_index
|
return True, group_index
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def bmesh_get_vertex_groups(cls, vertex: bmesh.types.BMVert, deform_layer: bmesh.types.BMLayerItem) -> list[int]:
|
||||||
|
results = []
|
||||||
|
for group_index in vertex[deform_layer].keys():
|
||||||
|
# Ignore vertex groups assignments produced by edge subdivision near arcs
|
||||||
|
# They usually have weight = 0.5
|
||||||
|
if vertex[deform_layer][group_index] == 1.0:
|
||||||
|
results.append(group_index)
|
||||||
|
return results
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def toggle_edit_mode(cls, context: bpy.types.Context) -> set[str]:
|
def toggle_edit_mode(cls, context: bpy.types.Context) -> set[str]:
|
||||||
ao = context.active_object
|
ao = context.active_object
|
||||||
|
|||||||
+96
-110
@@ -128,7 +128,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def export_profile(
|
def export_profile(
|
||||||
cls, obj: bpy.types.Object, position: Optional[Matrix] = None
|
cls, obj: bpy.types.Object, position: Optional[Matrix] = None
|
||||||
) -> Union[ifcopenshell.entity_instance, None]:
|
) -> ifcopenshell.entity_instance | None:
|
||||||
"""Returns `None` in case if profile was invalid."""
|
"""Returns `None` in case if profile was invalid."""
|
||||||
if position is None:
|
if position is None:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
@@ -140,7 +140,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def export_curves(
|
def export_curves(
|
||||||
cls, obj: bpy.types.Object, position: Optional[Matrix] = None
|
cls, obj: bpy.types.Object, position: Optional[Matrix] = None
|
||||||
) -> Union[list[ifcopenshell.entity_instance], None]:
|
) -> list[ifcopenshell.entity_instance] | None:
|
||||||
if position is None:
|
if position is None:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
@@ -171,12 +171,15 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
|
||||||
helper = Helper(tool.Ifc.get())
|
result = cls.auto_detect_profiles(obj, obj.data, position)
|
||||||
indices = helper.auto_detect_arbitrary_profile_with_voids(obj, obj.data)
|
|
||||||
|
|
||||||
if isinstance(indices, tuple) and indices[0] is False: # Ugly
|
if not isinstance(result, dict): # Ugly
|
||||||
return
|
return
|
||||||
|
|
||||||
|
profile_def = result["profile_def"]
|
||||||
|
if profile_def.is_a("IfcCompositeProfileDef"):
|
||||||
|
profile_def = profile_def.Profiles[0]
|
||||||
|
|
||||||
cls.bm = bmesh.new()
|
cls.bm = bmesh.new()
|
||||||
cls.bm.from_mesh(obj.data)
|
cls.bm.from_mesh(obj.data)
|
||||||
cls.bm.verts.ensure_lookup_table()
|
cls.bm.verts.ensure_lookup_table()
|
||||||
@@ -191,14 +194,10 @@ class Model(bonsai.core.tool.Model):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if tool.Ifc.get().schema != "IFC2X3":
|
surface.OuterBoundary = tool.Ifc.get().add(profile_def.OuterCurve)
|
||||||
cls.points = cls.export_points(position, indices["points"])
|
|
||||||
|
|
||||||
surface.OuterBoundary = cls.convert_mesh_to_curve(position, indices["profile"])
|
|
||||||
results = []
|
|
||||||
for inner_curve in indices["inner_curves"]:
|
|
||||||
results.append(cls.convert_mesh_to_curve(position, inner_curve))
|
|
||||||
surface.InnerBoundaries = results
|
surface.InnerBoundaries = results
|
||||||
|
if profile_def.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||||
|
surface.InnerBoundaries = [tool.Ifc.get().add(c) for c in profile_def.InnerCurves]
|
||||||
|
|
||||||
cls.bm.free()
|
cls.bm.free()
|
||||||
return surface
|
return surface
|
||||||
@@ -418,42 +417,26 @@ class Model(bonsai.core.tool.Model):
|
|||||||
for segment in curve.Segments:
|
for segment in curve.Segments:
|
||||||
cls.convert_curve_to_mesh(obj, position, segment.ParentCurve)
|
cls.convert_curve_to_mesh(obj, position, segment.ParentCurve)
|
||||||
elif curve.is_a("IfcIndexedPolyCurve"):
|
elif curve.is_a("IfcIndexedPolyCurve"):
|
||||||
is_arc = False
|
for local_point in curve.Points.CoordList:
|
||||||
is_closed = False
|
global_point = position @ Vector(cls.convert_unit_to_si(local_point)).to_3d()
|
||||||
|
cls.vertices.append(global_point)
|
||||||
if curve.Segments:
|
if curve.Segments:
|
||||||
for segment in curve.Segments:
|
for segment in curve.Segments:
|
||||||
if segment.is_a("IfcArcIndex"):
|
if segment.is_a("IfcArcIndex"):
|
||||||
is_arc = True
|
cls.arcs.append([i - 1 + offset for i in segment[0]])
|
||||||
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment[0][0] - 1])
|
cls.edges.append([i - 1 + offset for i in segment[0][:2]])
|
||||||
global_point = position @ Vector(local_point).to_3d()
|
cls.edges.append([i - 1 + offset for i in segment[0][1:]])
|
||||||
cls.vertices.append(global_point)
|
|
||||||
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment[0][1] - 1])
|
|
||||||
global_point = position @ Vector(local_point).to_3d()
|
|
||||||
cls.vertices.append(global_point)
|
|
||||||
cls.arcs.append([len(cls.vertices) - 2, len(cls.vertices) - 1])
|
|
||||||
else:
|
else:
|
||||||
for segment_index in segment[0][0:-1]:
|
segment = [i - 1 + offset for i in segment[0]]
|
||||||
local_point = cls.convert_unit_to_si(curve.Points.CoordList[segment_index - 1])
|
cls.edges.extend(zip(segment, segment[1:]))
|
||||||
global_point = position @ Vector(local_point).to_3d()
|
|
||||||
cls.vertices.append(global_point)
|
|
||||||
if is_arc:
|
|
||||||
cls.arcs[-1].append(len(cls.vertices) - 1)
|
|
||||||
is_arc = False
|
|
||||||
|
|
||||||
if curve.Segments[0][0][0] == curve.Segments[-1][0][-1]:
|
|
||||||
is_closed = True
|
|
||||||
else:
|
else:
|
||||||
for local_point in curve.Points.CoordList:
|
is_closed = False
|
||||||
global_point = position @ Vector(cls.convert_unit_to_si(local_point)).to_3d()
|
|
||||||
cls.vertices.append(global_point)
|
|
||||||
|
|
||||||
if cls.vertices[offset] == cls.vertices[-1]:
|
if cls.vertices[offset] == cls.vertices[-1]:
|
||||||
is_closed = True
|
is_closed = True
|
||||||
del cls.vertices[-1]
|
del cls.vertices[-1]
|
||||||
|
cls.edges.extend([(i, i + 1) for i in range(offset, len(cls.vertices) - 1)])
|
||||||
cls.edges.extend([(i, i + 1) for i in range(offset, len(cls.vertices) - 1)])
|
if is_closed:
|
||||||
if is_closed:
|
cls.edges.append([len(cls.vertices) - 1, offset]) # Close the loop
|
||||||
cls.edges.append([len(cls.vertices) - 1, offset]) # Close the loop
|
|
||||||
elif curve.is_a("IfcCircle"):
|
elif curve.is_a("IfcCircle"):
|
||||||
circle_position = Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist())
|
circle_position = Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist())
|
||||||
circle_position.translation *= cls.unit_scale
|
circle_position.translation *= cls.unit_scale
|
||||||
@@ -1538,26 +1521,20 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
# Sanity check
|
# Sanity check
|
||||||
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
|
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
|
||||||
for vert in bm.verts:
|
if deform_layer:
|
||||||
total_groups = 0
|
for vert in bm.verts:
|
||||||
is_circle = False
|
vert_group_indices = tool.Blender.bmesh_get_vertex_groups(vert, deform_layer)
|
||||||
for group_type, group_indices in groups.items():
|
is_circle = False
|
||||||
if not group_indices:
|
for group_index in vert_group_indices:
|
||||||
continue
|
group_type = "IFCARCINDEX" if group_index in groups["IFCARCINDEX"] else "IFCCIRCLE"
|
||||||
is_special, group_index = tool.Blender.bmesh_check_vertex_in_groups(vert, deform_layer, group_indices)
|
group_verts[group_type].setdefault(group_index, 0)
|
||||||
if not is_special:
|
group_verts[group_type][group_index] += 1
|
||||||
continue
|
if group_type == "IFCCIRCLE":
|
||||||
if group_type == "IFCCIRCLE":
|
is_circle = True
|
||||||
is_circle = True
|
if is_circle:
|
||||||
group_verts[group_type].setdefault(group_index, 0)
|
pass # Circles are allowed to be unclosed
|
||||||
group_verts[group_type][group_index] += 1
|
elif len(vert.link_edges) != 2: # Unclosed loop or forked loop
|
||||||
total_groups += 0
|
return (False, "UNCLOSED_LOOP")
|
||||||
if total_groups > 1: # A vert can only belong to one group
|
|
||||||
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
|
|
||||||
elif is_circle:
|
|
||||||
pass # Circles are allowed to be unclosed
|
|
||||||
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
|
|
||||||
return (False, "UNCLOSED_LOOP")
|
|
||||||
|
|
||||||
for group_type, group_counts in group_verts.items():
|
for group_type, group_counts in group_verts.items():
|
||||||
if group_type == "IFCARCINDEX":
|
if group_type == "IFCARCINDEX":
|
||||||
@@ -1617,10 +1594,13 @@ class Model(bonsai.core.tool.Model):
|
|||||||
curves.append(
|
curves.append(
|
||||||
tmp.createIfcCircle(tmp.createIfcAxis2Placement2D(tmp.createIfcCartesianPoint(list(mid))), radius)
|
tmp.createIfcCircle(tmp.createIfcAxis2Placement2D(tmp.createIfcCartesianPoint(list(mid))), radius)
|
||||||
)
|
)
|
||||||
else: # For now, assume closed loop
|
else:
|
||||||
loop_verts = []
|
loop_verts = []
|
||||||
for i, edge in enumerate(loop):
|
for i, edge in enumerate(loop):
|
||||||
if i == 0:
|
if i == 0 and len(loop) == 1:
|
||||||
|
loop_verts.append(edge.verts[0])
|
||||||
|
loop_verts.append(edge.verts[1])
|
||||||
|
elif i == 0:
|
||||||
if edge.verts[0] in loop[i + 1].verts:
|
if edge.verts[0] in loop[i + 1].verts:
|
||||||
loop_verts.append(edge.verts[1])
|
loop_verts.append(edge.verts[1])
|
||||||
loop_verts.append(edge.verts[0])
|
loop_verts.append(edge.verts[0])
|
||||||
@@ -1629,15 +1609,23 @@ class Model(bonsai.core.tool.Model):
|
|||||||
loop_verts.append(edge.verts[1])
|
loop_verts.append(edge.verts[1])
|
||||||
else:
|
else:
|
||||||
loop_verts.append(edge.other_vert(loop_verts[-1]))
|
loop_verts.append(edge.other_vert(loop_verts[-1]))
|
||||||
loop_verts.pop()
|
|
||||||
|
|
||||||
# Handle loop_verts possibly starting halfway through an arc
|
if is_closed := loop_verts[0] == loop_verts[-1]:
|
||||||
if (group_index := get_group_index(loop_verts[0], "IFCARCINDEX")) is not None:
|
loop_verts.pop()
|
||||||
if get_group_index(loop_verts[1], "IFCARCINDEX") != group_index:
|
|
||||||
loop_verts.insert(0, loop_verts.pop())
|
# Handle loop_verts possibly starting halfway through an arc
|
||||||
loop_verts.insert(0, loop_verts.pop())
|
if deform_layer:
|
||||||
elif get_group_index(loop_verts[2], "IFCARCINDEX") != group_index:
|
if gi := tool.Blender.bmesh_get_vertex_groups(loop_verts[0], deform_layer):
|
||||||
loop_verts.insert(0, loop_verts.pop())
|
if not (gi2 := tool.Blender.bmesh_get_vertex_groups(loop_verts[1], deform_layer)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
elif not (set(gi) & set(gi2)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
elif not (gi2 := tool.Blender.bmesh_get_vertex_groups(loop_verts[2], deform_layer)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
elif not (set(gi) & set(gi2)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
|
||||||
if tmp.schema != "IFC2X3" and any([is_in_group(v, "IFCARCINDEX") for v in loop_verts]):
|
if tmp.schema != "IFC2X3" and any([is_in_group(v, "IFCARCINDEX") for v in loop_verts]):
|
||||||
# We need to specify segments
|
# We need to specify segments
|
||||||
@@ -1649,30 +1637,34 @@ class Model(bonsai.core.tool.Model):
|
|||||||
while i < total_verts:
|
while i < total_verts:
|
||||||
v = loop_verts[i]
|
v = loop_verts[i]
|
||||||
if (
|
if (
|
||||||
i + 1 != total_verts
|
(i + 1 != total_verts)
|
||||||
and is_in_group(v, "IFCARCINDEX")
|
and (gi := tool.Blender.bmesh_get_vertex_groups(v, deform_layer))
|
||||||
and is_in_group(loop_verts[i + 1], "IFCARCINDEX")
|
and (gi2 := tool.Blender.bmesh_get_vertex_groups(loop_verts[i + 1], deform_layer))
|
||||||
|
and (set(gi) & set(gi2))
|
||||||
):
|
):
|
||||||
segments.append(tmp.createIfcArcIndex([i + 1, i + 2, i + 3]))
|
segments.append(tmp.createIfcArcIndex([i + 1, i + 2, i + 3]))
|
||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
segments.append(tmp.createIfcLineIndex([i + 1, i + 2]))
|
segments.append(tmp.createIfcLineIndex([i + 1, i + 2]))
|
||||||
i += 1
|
i += 1
|
||||||
# Close the loop
|
if is_closed:
|
||||||
last_segment_indices = list(segments[-1][0])
|
# Close the loop
|
||||||
last_segment_indices[-1] = 1
|
last_segment_indices = list(segments[-1][0])
|
||||||
segments[-1][0] = last_segment_indices
|
last_segment_indices[-1] = 1
|
||||||
|
segments[-1][0] = last_segment_indices
|
||||||
curves.append(tmp.createIfcIndexedPolyCurve(points, segments))
|
curves.append(tmp.createIfcIndexedPolyCurve(points, segments))
|
||||||
elif tmp.schema == "IFC2X3":
|
elif tmp.schema == "IFC2X3":
|
||||||
points = [
|
points = [
|
||||||
tmp.createIfcCartesianPoint(list((position_i @ (v.co / unit_scale)).to_2d()))
|
tmp.createIfcCartesianPoint(list((position_i @ (v.co / unit_scale)).to_2d()))
|
||||||
for v in loop_verts
|
for v in loop_verts
|
||||||
]
|
]
|
||||||
points.append(points[0])
|
if is_closed:
|
||||||
|
points.append(points[0])
|
||||||
curves.append(tmp.createIfcPolyline(points))
|
curves.append(tmp.createIfcPolyline(points))
|
||||||
else: # Pure straight polyline, no segments required
|
else: # Pure straight polyline, no segments required
|
||||||
coord_list = [list((position_i @ (v.co / unit_scale)).to_2d()) for v in loop_verts]
|
coord_list = [list((position_i @ (v.co / unit_scale)).to_2d()) for v in loop_verts]
|
||||||
coord_list.append(coord_list[0])
|
if is_closed:
|
||||||
|
coord_list.append(coord_list[0])
|
||||||
points = tmp.createIfcCartesianPointList2D(coord_list)
|
points = tmp.createIfcCartesianPointList2D(coord_list)
|
||||||
curves.append(tmp.createIfcIndexedPolyCurve(points))
|
curves.append(tmp.createIfcIndexedPolyCurve(points))
|
||||||
|
|
||||||
@@ -1723,7 +1715,6 @@ class Model(bonsai.core.tool.Model):
|
|||||||
profile_def = tmp.createIfcCompositeProfileDef("AREA", None, profile_defs)
|
profile_def = tmp.createIfcCompositeProfileDef("AREA", None, profile_defs)
|
||||||
return {"ifc_file": tmp, "profile_def": profile_def}
|
return {"ifc_file": tmp, "profile_def": profile_def}
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def auto_detect_curves(
|
def auto_detect_curves(
|
||||||
cls, obj: bpy.types.Object, mesh: bpy.types.Mesh, position: Matrix | None = None
|
cls, obj: bpy.types.Object, mesh: bpy.types.Mesh, position: Matrix | None = None
|
||||||
@@ -1752,26 +1743,15 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
# Sanity check
|
# Sanity check
|
||||||
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
|
group_verts = {"IFCARCINDEX": {}, "IFCCIRCLE": {}}
|
||||||
for vert in bm.verts:
|
if deform_layer:
|
||||||
total_groups = 0
|
for vert in bm.verts:
|
||||||
is_circle = False
|
vert_group_indices = tool.Blender.bmesh_get_vertex_groups(vert, deform_layer)
|
||||||
for group_type, group_indices in groups.items():
|
for group_index in vert_group_indices:
|
||||||
if not group_indices:
|
group_type = "IFCARCINDEX" if group_index in groups["IFCARCINDEX"] else "IFCCIRCLE"
|
||||||
continue
|
group_verts[group_type].setdefault(group_index, 0)
|
||||||
is_special, group_index = tool.Blender.bmesh_check_vertex_in_groups(vert, deform_layer, group_indices)
|
group_verts[group_type][group_index] += 1
|
||||||
if not is_special:
|
if len(vert.link_edges) > 2: # Forked loop
|
||||||
continue
|
return (False, "FORKED_LOOP")
|
||||||
if group_type == "IFCCIRCLE":
|
|
||||||
is_circle = True
|
|
||||||
group_verts[group_type].setdefault(group_index, 0)
|
|
||||||
group_verts[group_type][group_index] += 1
|
|
||||||
total_groups += 0
|
|
||||||
if total_groups > 1: # A vert can only belong to one group
|
|
||||||
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
|
|
||||||
elif is_circle:
|
|
||||||
pass # Circles are allowed to be unclosed
|
|
||||||
elif total_groups == 0 and len(vert.link_edges) > 2: # Forked loop
|
|
||||||
return (False, "FORKED_LOOP")
|
|
||||||
|
|
||||||
for group_type, group_counts in group_verts.items():
|
for group_type, group_counts in group_verts.items():
|
||||||
if group_type == "IFCARCINDEX":
|
if group_type == "IFCARCINDEX":
|
||||||
@@ -1804,7 +1784,6 @@ class Model(bonsai.core.tool.Model):
|
|||||||
loop_edges.remove(edge)
|
loop_edges.remove(edge)
|
||||||
has_found_connected_edge = True
|
has_found_connected_edge = True
|
||||||
loops.append(loop)
|
loops.append(loop)
|
||||||
print('autodetected loops', loops)
|
|
||||||
|
|
||||||
tmp = ifcopenshell.file(schema=tool.Ifc.get().schema)
|
tmp = ifcopenshell.file(schema=tool.Ifc.get().schema)
|
||||||
|
|
||||||
@@ -1852,12 +1831,18 @@ class Model(bonsai.core.tool.Model):
|
|||||||
loop_verts.pop()
|
loop_verts.pop()
|
||||||
|
|
||||||
# Handle loop_verts possibly starting halfway through an arc
|
# Handle loop_verts possibly starting halfway through an arc
|
||||||
if (group_index := get_group_index(loop_verts[0], "IFCARCINDEX")) is not None:
|
if deform_layer:
|
||||||
if get_group_index(loop_verts[1], "IFCARCINDEX") != group_index:
|
if gi := tool.Blender.bmesh_get_vertex_groups(loop_verts[0], deform_layer):
|
||||||
loop_verts.insert(0, loop_verts.pop())
|
if not (gi2 := tool.Blender.bmesh_get_vertex_groups(loop_verts[1], deform_layer)):
|
||||||
loop_verts.insert(0, loop_verts.pop())
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
elif get_group_index(loop_verts[2], "IFCARCINDEX") != group_index:
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
loop_verts.insert(0, loop_verts.pop())
|
elif not (set(gi) & set(gi2)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
elif not (gi2 := tool.Blender.bmesh_get_vertex_groups(loop_verts[2], deform_layer)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
elif not (set(gi) & set(gi2)):
|
||||||
|
loop_verts.insert(0, loop_verts.pop())
|
||||||
|
|
||||||
if tmp.schema != "IFC2X3" and any([is_in_group(v, "IFCARCINDEX") for v in loop_verts]):
|
if tmp.schema != "IFC2X3" and any([is_in_group(v, "IFCARCINDEX") for v in loop_verts]):
|
||||||
# We need to specify segments
|
# We need to specify segments
|
||||||
@@ -1869,9 +1854,10 @@ class Model(bonsai.core.tool.Model):
|
|||||||
while i < total_verts:
|
while i < total_verts:
|
||||||
v = loop_verts[i]
|
v = loop_verts[i]
|
||||||
if (
|
if (
|
||||||
i + 1 != total_verts
|
(i + 1 != total_verts)
|
||||||
and is_in_group(v, "IFCARCINDEX")
|
and (gi := tool.Blender.bmesh_get_vertex_groups(v, deform_layer))
|
||||||
and is_in_group(loop_verts[i + 1], "IFCARCINDEX")
|
and (gi2 := tool.Blender.bmesh_get_vertex_groups(loop_verts[i + 1], deform_layer))
|
||||||
|
and (set(gi) & set(gi2))
|
||||||
):
|
):
|
||||||
segments.append(tmp.createIfcArcIndex([i + 1, i + 2, i + 3]))
|
segments.append(tmp.createIfcArcIndex([i + 1, i + 2, i + 3]))
|
||||||
i += 2
|
i += 2
|
||||||
|
|||||||
Reference in New Issue
Block a user