mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-29 11:46:33 +00:00
You can now automagically create arbitrary polygonal profiles with voids from meshes
This commit is contained in:
@@ -25,6 +25,7 @@ class Usecase:
|
|||||||
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
|
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
|
||||||
# IfcExtrudedAreaSolid/IfcCircleProfileDef
|
# IfcExtrudedAreaSolid/IfcCircleProfileDef
|
||||||
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
|
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
|
||||||
|
# IfcExtrudedAreaSolid/IfcArbitraryProfileDef
|
||||||
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
|
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
|
||||||
}
|
}
|
||||||
self.ifc_vertices = []
|
self.ifc_vertices = []
|
||||||
@@ -139,6 +140,8 @@ class Usecase:
|
|||||||
return self.create_circle_extrusion_representation()
|
return self.create_circle_extrusion_representation()
|
||||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef":
|
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef":
|
||||||
return self.create_arbitrary_extrusion_representation()
|
return self.create_arbitrary_extrusion_representation()
|
||||||
|
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids":
|
||||||
|
return self.create_arbitrary_void_extrusion_representation()
|
||||||
return self.create_mesh_representation()
|
return self.create_mesh_representation()
|
||||||
|
|
||||||
def create_camera_block_representation(self):
|
def create_camera_block_representation(self):
|
||||||
@@ -302,6 +305,20 @@ class Usecase:
|
|||||||
[item],
|
[item],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def create_arbitrary_void_extrusion_representation(self):
|
||||||
|
helper = Helper(self.file)
|
||||||
|
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self.settings["geometry"])
|
||||||
|
profile_def = helper.create_arbitrary_profile_def_with_voids(
|
||||||
|
self.settings["geometry"], indices["profile"], indices["inner_curves"]
|
||||||
|
)
|
||||||
|
item = helper.create_extruded_area_solid(self.settings["geometry"], indices["extrusion"], profile_def)
|
||||||
|
return self.file.createIfcShapeRepresentation(
|
||||||
|
self.settings["context"],
|
||||||
|
self.settings["context"].ContextIdentifier,
|
||||||
|
"SweptSolid",
|
||||||
|
[item],
|
||||||
|
)
|
||||||
|
|
||||||
def create_mesh_representation(self):
|
def create_mesh_representation(self):
|
||||||
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
|
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
|
||||||
return self.create_faceted_brep()
|
return self.create_faceted_brep()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class Helper:
|
|||||||
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
|
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
bm.from_mesh(mesh)
|
bm.from_mesh(mesh)
|
||||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||||
|
|
||||||
bm.faces.ensure_lookup_table()
|
bm.faces.ensure_lookup_table()
|
||||||
face = None
|
face = None
|
||||||
@@ -41,7 +41,7 @@ class Helper:
|
|||||||
def auto_detect_circle_profile_extruded_area_solid(self, mesh):
|
def auto_detect_circle_profile_extruded_area_solid(self, mesh):
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
bm.from_mesh(mesh)
|
bm.from_mesh(mesh)
|
||||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||||
|
|
||||||
bm.faces.ensure_lookup_table()
|
bm.faces.ensure_lookup_table()
|
||||||
potential_faces = []
|
potential_faces = []
|
||||||
@@ -68,7 +68,7 @@ class Helper:
|
|||||||
def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh):
|
def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh):
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
bm.from_mesh(mesh)
|
bm.from_mesh(mesh)
|
||||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||||
|
|
||||||
bm.faces.ensure_lookup_table()
|
bm.faces.ensure_lookup_table()
|
||||||
potential_faces = []
|
potential_faces = []
|
||||||
@@ -93,13 +93,119 @@ class Helper:
|
|||||||
|
|
||||||
return {"profile": profile, "extrusion": extrusion}
|
return {"profile": profile, "extrusion": extrusion}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# profile, it is only one of the possible faces that make up the end of our
|
||||||
|
# extrusion. Then we find all faces with the same normal. This creates a set
|
||||||
|
# of faces that define the end of our extrusion. From this set of faces, we
|
||||||
|
# need to then extract the outer loop and inner loops. First, all loops are
|
||||||
|
# detected from the faces. Any edge that is not shared between other faces
|
||||||
|
# in the set must be part of either an inner or outer loop. This gives us a
|
||||||
|
# set of edges. Then, connected edges (i.e. sharing a vertex) are joined to
|
||||||
|
# form distinct loops. Finally, the outer loop is distinguished by being
|
||||||
|
# the loop with the greatest area.
|
||||||
|
def auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self, mesh):
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(mesh)
|
||||||
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||||
|
|
||||||
|
bm.faces.ensure_lookup_table()
|
||||||
|
potential_faces = []
|
||||||
|
for face in bm.faces:
|
||||||
|
total_verts = len(face.verts)
|
||||||
|
if total_verts > 4:
|
||||||
|
potential_faces.append(face)
|
||||||
|
|
||||||
|
for face in potential_faces:
|
||||||
|
if face.normal.z < -0.1:
|
||||||
|
break
|
||||||
|
|
||||||
|
end_faces = []
|
||||||
|
end_face_normal = face.normal
|
||||||
|
for face in bm.faces:
|
||||||
|
if (face.normal - end_face_normal).length < 0.001:
|
||||||
|
end_faces.append(face)
|
||||||
|
|
||||||
|
loop_edges = set()
|
||||||
|
for face in end_faces:
|
||||||
|
potential_edges = set(face.edges)
|
||||||
|
for face2 in end_faces:
|
||||||
|
if face == face2:
|
||||||
|
continue
|
||||||
|
potential_edges -= set(face2.edges)
|
||||||
|
loop_edges |= potential_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)
|
||||||
|
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])
|
||||||
|
|
||||||
|
loop_bm = bmesh.new()
|
||||||
|
for vert in loop_vertices:
|
||||||
|
loop_bm.verts.new(vert.co)
|
||||||
|
face = loop_bm.faces.new(loop_bm.verts)
|
||||||
|
|
||||||
|
loop_vertex_indices = [v.index for v in loop_vertices]
|
||||||
|
face_area = face.calc_area()
|
||||||
|
if face_area > max_area:
|
||||||
|
max_area = face_area
|
||||||
|
outer_loop = loop_vertex_indices
|
||||||
|
inner_loops.append(loop_vertex_indices)
|
||||||
|
loop_bm.free()
|
||||||
|
|
||||||
|
inner_loops.remove(outer_loop)
|
||||||
|
|
||||||
|
extrusion = self.detect_extrusion_edge(bm, end_faces[0])
|
||||||
|
|
||||||
|
bm.to_mesh(mesh)
|
||||||
|
mesh.update()
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
|
||||||
|
|
||||||
|
# An extrusion edge is an edge that shares a single vertex with a profile
|
||||||
|
# face and is not parallel to the face.
|
||||||
def detect_extrusion_edge(self, bm, profile_face):
|
def detect_extrusion_edge(self, bm, profile_face):
|
||||||
bm.edges.ensure_lookup_table()
|
bm.edges.ensure_lookup_table()
|
||||||
extrusion = None
|
extrusion = None
|
||||||
face_verts_set = set(profile_face.verts)
|
face_verts_set = set(profile_face.verts)
|
||||||
for edge in bm.edges:
|
for edge in bm.edges:
|
||||||
|
edge_vector = edge.verts[1].co - edge.verts[0].co
|
||||||
unshared_verts = set(edge.verts) - face_verts_set
|
unshared_verts = set(edge.verts) - face_verts_set
|
||||||
if len(unshared_verts) == 1:
|
if len(unshared_verts) == 1 and not (edge_vector.angle(profile_face.normal) - pi / 2 < 0.001):
|
||||||
if unshared_verts.pop() == edge.verts[1]:
|
if unshared_verts.pop() == edge.verts[1]:
|
||||||
return [edge.verts[0].index, edge.verts[1].index]
|
return [edge.verts[0].index, edge.verts[1].index]
|
||||||
return [edge.verts[1].index, edge.verts[0].index]
|
return [edge.verts[1].index, edge.verts[0].index]
|
||||||
@@ -123,6 +229,15 @@ class Helper:
|
|||||||
curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve)
|
curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve)
|
||||||
return {"curve_ucs": curve_ucs, "curve": curve}
|
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||||
|
|
||||||
|
def create_arbitrary_profile_def_with_voids(self, mesh, profile_indices, inner_curve_indices):
|
||||||
|
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||||
|
outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
|
||||||
|
inner_curves = [
|
||||||
|
self.create_polyline_from_loop(mesh, curve_indices, curve_ucs) for curve_indices in inner_curve_indices
|
||||||
|
]
|
||||||
|
curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves)
|
||||||
|
return {"curve_ucs": curve_ucs, "curve": curve}
|
||||||
|
|
||||||
def create_rectangle_profile_def(self, mesh, profile_indices):
|
def create_rectangle_profile_def(self, mesh, profile_indices):
|
||||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||||
xdim = self.convert_si_to_unit(
|
xdim = self.convert_si_to_unit(
|
||||||
@@ -137,7 +252,13 @@ class Helper:
|
|||||||
def create_circle_profile_def(self, mesh, profile_indices):
|
def create_circle_profile_def(self, mesh, profile_indices):
|
||||||
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
|
||||||
radius = self.convert_si_to_unit(
|
radius = self.convert_si_to_unit(
|
||||||
abs((mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[int(len(profile_indices) / 2)]].co).length) / 2
|
abs(
|
||||||
|
(
|
||||||
|
mesh.vertices[profile_indices[0]].co
|
||||||
|
- mesh.vertices[profile_indices[int(len(profile_indices) / 2)]].co
|
||||||
|
).length
|
||||||
|
)
|
||||||
|
/ 2
|
||||||
)
|
)
|
||||||
center = Vector((0, 0))
|
center = Vector((0, 0))
|
||||||
position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
|
position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ class BIM_PT_mesh(Panel):
|
|||||||
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion")
|
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion")
|
||||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
|
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion With Voids")
|
||||||
|
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.operator("bim.get_representation_ifc_parameters")
|
row.operator("bim.get_representation_ifc_parameters")
|
||||||
for index, ifc_parameter in enumerate(props.ifc_parameters):
|
for index, ifc_parameter in enumerate(props.ifc_parameters):
|
||||||
|
|||||||
Reference in New Issue
Block a user