mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +00:00
See #3884. Rewrite native geometry handling with a much more robust approach fixing a number of invalid coordinates and simplifying things.
This commit is contained in:
@@ -473,13 +473,51 @@ class IfcImporter:
|
|||||||
or getattr(element, "HasOpenings", None)
|
or getattr(element, "HasOpenings", None)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
representations = self.get_transformed_body_representations(element.Representation.Representations)
|
|
||||||
|
representation = None
|
||||||
|
representation_priority = None
|
||||||
|
context = None
|
||||||
|
|
||||||
|
for rep in element.Representation.Representations:
|
||||||
|
if rep.ContextOfItems in self.contexts:
|
||||||
|
rep_priority = self.contexts.index(rep.ContextOfItems)
|
||||||
|
if representation is None or rep_priority < representation_priority:
|
||||||
|
representation = rep
|
||||||
|
representation_priority = rep_priority
|
||||||
|
context = rep.ContextOfItems
|
||||||
|
|
||||||
|
if not representation:
|
||||||
|
return
|
||||||
|
|
||||||
|
matrix = np.eye(4)
|
||||||
|
representation_id = None
|
||||||
|
|
||||||
|
rep = representation
|
||||||
|
while True:
|
||||||
|
if rep.Items and rep.Items[0].is_a("IfcMappedItem"):
|
||||||
|
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(rep.Items[0])
|
||||||
|
if not np.allclose(rep_matrix, np.eye(4)):
|
||||||
|
matrix = rep_matrix @ matrix
|
||||||
|
if representation_id is None:
|
||||||
|
representation_id = rep.id()
|
||||||
|
rep = rep.Items[0].MappingSource.MappedRepresentation
|
||||||
|
else:
|
||||||
|
if representation_id is None:
|
||||||
|
representation_id = rep.id()
|
||||||
|
break
|
||||||
|
resolved_representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||||
|
|
||||||
|
matrix[0][3] *= self.unit_scale
|
||||||
|
matrix[1][3] *= self.unit_scale
|
||||||
|
matrix[2][3] *= self.unit_scale
|
||||||
|
|
||||||
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
|
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
|
||||||
if self.is_native_swept_disk_solid(element, representations):
|
if self.is_native_swept_disk_solid(element, resolved_representation):
|
||||||
self.native_data[element.GlobalId] = {
|
self.native_data[element.GlobalId] = {
|
||||||
"representations": representations,
|
"matrix": matrix,
|
||||||
"representation": self.get_body_representation(element.Representation.Representations),
|
"context": context,
|
||||||
|
"geometry_id": representation_id,
|
||||||
|
"representation": resolved_representation,
|
||||||
"type": "IfcSweptDiskSolid",
|
"type": "IfcSweptDiskSolid",
|
||||||
}
|
}
|
||||||
return True
|
return True
|
||||||
@@ -488,51 +526,52 @@ class IfcImporter:
|
|||||||
return False # Performance improvements only occur on edge cases currently
|
return False # Performance improvements only occur on edge cases currently
|
||||||
|
|
||||||
# FacetedBreps (without voids) are meshes. See #841.
|
# FacetedBreps (without voids) are meshes. See #841.
|
||||||
if self.is_native_faceted_brep(representations):
|
if self.is_native_faceted_brep(resolved_representation):
|
||||||
self.native_data[element.GlobalId] = {
|
self.native_data[element.GlobalId] = {
|
||||||
"representations": representations,
|
"matrix": matrix,
|
||||||
"representation": self.get_body_representation(element.Representation.Representations),
|
"context": context,
|
||||||
|
"geometry_id": representation_id,
|
||||||
|
"representation": resolved_representation,
|
||||||
"type": "IfcFacetedBrep",
|
"type": "IfcFacetedBrep",
|
||||||
}
|
}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.is_native_face_based_surface_model(representations):
|
if self.is_native_face_based_surface_model(resolved_representation):
|
||||||
self.native_data[element.GlobalId] = {
|
self.native_data[element.GlobalId] = {
|
||||||
"representations": representations,
|
"matrix": matrix,
|
||||||
"representation": self.get_body_representation(element.Representation.Representations),
|
"context": context,
|
||||||
|
"geometry_id": representation_id,
|
||||||
|
"representation": resolved_representation,
|
||||||
"type": "IfcFaceBasedSurfaceModel",
|
"type": "IfcFaceBasedSurfaceModel",
|
||||||
}
|
}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_native_swept_disk_solid(self, element, representations):
|
def is_native_swept_disk_solid(self, element, representation):
|
||||||
for representation in representations:
|
items = representation.Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
if tool.Blender.Modifier.is_railing(element):
|
||||||
if tool.Blender.Modifier.is_railing(element):
|
return False
|
||||||
return False
|
return True
|
||||||
return True
|
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
items[0].is_a("IfcSweptDiskSolid")
|
||||||
items[0].is_a("IfcSweptDiskSolid")
|
and len({i.is_a() for i in items}) == 1
|
||||||
and len({i.is_a() for i in items}) == 1
|
and len({i.Radius for i in items}) == 1
|
||||||
and len({i.Radius for i in items}) == 1
|
):
|
||||||
):
|
if tool.Blender.Modifier.is_railing(element):
|
||||||
if tool.Blender.Modifier.is_railing(element):
|
return False
|
||||||
return False
|
return True
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_native_faceted_brep(self, representations):
|
def is_native_faceted_brep(self, representation):
|
||||||
for representation in representations:
|
for i in representation.Items:
|
||||||
for i in representation["raw"].Items:
|
if i.is_a() != "IfcFacetedBrep":
|
||||||
if i.is_a() != "IfcFacetedBrep":
|
return False
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_native_face_based_surface_model(self, representations):
|
def is_native_face_based_surface_model(self, representation):
|
||||||
for representation in representations:
|
for i in representation.Items:
|
||||||
for i in representation["raw"].Items:
|
if i.is_a() != "IfcFaceBasedSurfaceModel":
|
||||||
if i.is_a() != "IfcFaceBasedSurfaceModel":
|
return False
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_products_from_shape_representation(self, element):
|
def get_products_from_shape_representation(self, element):
|
||||||
@@ -781,20 +820,16 @@ class IfcImporter:
|
|||||||
checkpoint = time.time()
|
checkpoint = time.time()
|
||||||
self.incrementally_merge_objects()
|
self.incrementally_merge_objects()
|
||||||
native_data = self.native_data[element.GlobalId]
|
native_data = self.native_data[element.GlobalId]
|
||||||
representation = native_data["representation"]
|
mesh_name = f"{native_data['context'].id()}/{native_data['geometry_id']}"
|
||||||
if not representation:
|
|
||||||
continue
|
|
||||||
context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0
|
|
||||||
mesh_name = f"{context_id}/{representation.id()}"
|
|
||||||
mesh = self.meshes.get(mesh_name)
|
mesh = self.meshes.get(mesh_name)
|
||||||
if mesh is None:
|
if mesh is None:
|
||||||
if native_data["type"] == "IfcSweptDiskSolid":
|
if native_data["type"] == "IfcSweptDiskSolid":
|
||||||
mesh = self.create_native_swept_disk_solid(element, mesh_name)
|
mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data)
|
||||||
elif native_data["type"] == "IfcFacetedBrep":
|
elif native_data["type"] == "IfcFacetedBrep":
|
||||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
|
||||||
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
|
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
|
||||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
|
||||||
tool.Ifc.link(representation, mesh)
|
tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh)
|
||||||
mesh.name = mesh_name
|
mesh.name = mesh_name
|
||||||
self.meshes[mesh_name] = mesh
|
self.meshes[mesh_name] = mesh
|
||||||
self.create_product(element, mesh=mesh)
|
self.create_product(element, mesh=mesh)
|
||||||
@@ -1099,7 +1134,7 @@ class IfcImporter:
|
|||||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||||
styles.extend(style.Styles)
|
styles.extend(style.Styles)
|
||||||
|
|
||||||
def create_native_faceted_brep(self, element, mesh_name):
|
def create_native_faceted_brep(self, element, mesh_name, native_data):
|
||||||
# TODO: georeferencing?
|
# TODO: georeferencing?
|
||||||
# co [x y z x y z x y z ...]
|
# co [x y z x y z x y z ...]
|
||||||
# vertex_index [i i i i i ...]
|
# vertex_index [i i i i i ...]
|
||||||
@@ -1116,10 +1151,11 @@ class IfcImporter:
|
|||||||
"material_ids": [],
|
"material_ids": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
for representation in element.Representation.Representations:
|
for item in native_data["representation"].Items:
|
||||||
if representation.ContextOfItems.id() not in self.body_contexts:
|
if item.is_a() == "IfcFacetedBrep":
|
||||||
continue
|
self.convert_representation_item_faceted_brep(item)
|
||||||
self.convert_representation(representation)
|
elif item.is_a() == "IfcFaceBasedSurfaceModel":
|
||||||
|
self.convert_representation_item_face_based_surface_model(item)
|
||||||
|
|
||||||
mesh = bpy.data.meshes.new("Native")
|
mesh = bpy.data.meshes.new("Native")
|
||||||
|
|
||||||
@@ -1136,19 +1172,33 @@ class IfcImporter:
|
|||||||
)
|
)
|
||||||
verts = [None] * len(self.mesh_data["co"])
|
verts = [None] * len(self.mesh_data["co"])
|
||||||
for i in range(0, len(self.mesh_data["co"]), 3):
|
for i in range(0, len(self.mesh_data["co"]), 3):
|
||||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector(
|
||||||
self.mesh_data["co"][i] * self.unit_scale,
|
(
|
||||||
self.mesh_data["co"][i + 1] * self.unit_scale,
|
*ifcopenshell.util.geolocation.enh2xyz(
|
||||||
self.mesh_data["co"][i + 2] * self.unit_scale,
|
self.mesh_data["co"][i] * self.unit_scale,
|
||||||
offset_point[0] * self.unit_scale,
|
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||||
offset_point[1] * self.unit_scale,
|
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||||
offset_point[2] * self.unit_scale,
|
offset_point[0] * self.unit_scale,
|
||||||
float(props.blender_x_axis_abscissa),
|
offset_point[1] * self.unit_scale,
|
||||||
float(props.blender_x_axis_ordinate),
|
offset_point[2] * self.unit_scale,
|
||||||
|
float(props.blender_x_axis_abscissa),
|
||||||
|
float(props.blender_x_axis_ordinate),
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
mesh["has_cartesian_point_offset"] = True
|
mesh["has_cartesian_point_offset"] = True
|
||||||
else:
|
else:
|
||||||
verts = [c * self.unit_scale for c in self.mesh_data["co"]]
|
verts = [None] * len(self.mesh_data["co"])
|
||||||
|
for i in range(0, len(self.mesh_data["co"]), 3):
|
||||||
|
verts[i], verts[i + 1], verts[i + 2], _ = native_data["matrix"] @ mathutils.Vector(
|
||||||
|
(
|
||||||
|
self.mesh_data["co"][i] * self.unit_scale,
|
||||||
|
self.mesh_data["co"][i + 1] * self.unit_scale,
|
||||||
|
self.mesh_data["co"][i + 2] * self.unit_scale,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
)
|
||||||
mesh["has_cartesian_point_offset"] = False
|
mesh["has_cartesian_point_offset"] = False
|
||||||
|
|
||||||
mesh.vertices.add(self.mesh_data["total_verts"])
|
mesh.vertices.add(self.mesh_data["total_verts"])
|
||||||
@@ -1165,19 +1215,6 @@ class IfcImporter:
|
|||||||
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
|
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
def convert_representation(self, representation):
|
|
||||||
for item in representation.Items:
|
|
||||||
self.convert_representation_item(item)
|
|
||||||
|
|
||||||
def convert_representation_item(self, item):
|
|
||||||
if item.is_a("IfcMappedItem"):
|
|
||||||
# mapping_target = matrix
|
|
||||||
self.convert_representation(item.MappingSource.MappedRepresentation)
|
|
||||||
elif item.is_a() == "IfcFacetedBrep":
|
|
||||||
self.convert_representation_item_faceted_brep(item)
|
|
||||||
elif item.is_a() == "IfcFaceBasedSurfaceModel":
|
|
||||||
self.convert_representation_item_face_based_surface_model(item)
|
|
||||||
|
|
||||||
def convert_representation_item_face_based_surface_model(self, item):
|
def convert_representation_item_face_based_surface_model(self, item):
|
||||||
mesh = item.get_info_2(recursive=True)
|
mesh = item.get_info_2(recursive=True)
|
||||||
for face_set in mesh["FbsmFaces"]:
|
for face_set in mesh["FbsmFaces"]:
|
||||||
@@ -1272,63 +1309,34 @@ class IfcImporter:
|
|||||||
self.mesh_data["loop_start"].extend(loop_start)
|
self.mesh_data["loop_start"].extend(loop_start)
|
||||||
# list(di1.keys())
|
# list(di1.keys())
|
||||||
|
|
||||||
def create_native_swept_disk_solid(self, element, mesh_name):
|
def create_native_swept_disk_solid(self, element, mesh_name, native_data):
|
||||||
# TODO: georeferencing?
|
# TODO: georeferencing?
|
||||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
||||||
curve.dimensions = "3D"
|
curve.dimensions = "3D"
|
||||||
curve.resolution_u = 2
|
curve.resolution_u = 2
|
||||||
polyline = curve.splines.new("POLY")
|
polyline = curve.splines.new("POLY")
|
||||||
|
|
||||||
for representation in self.native_data[element.GlobalId]["representations"]:
|
for item in native_data["representation"].Items:
|
||||||
for item in representation["raw"].Items:
|
# TODO: support inner radius, start param, and end param
|
||||||
# TODO: support inner radius, start param, and end param
|
geometry = self.create_generic_shape(item.Directrix)
|
||||||
geometry = self.create_generic_shape(item.Directrix)
|
e = geometry.edges
|
||||||
e = geometry.edges
|
v = geometry.verts
|
||||||
v = geometry.verts
|
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
||||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
v2 = None
|
||||||
v2 = None
|
for edge in edges:
|
||||||
for edge in edges:
|
v1 = vertices[edge[0]]
|
||||||
v1 = vertices[edge[0]]
|
if v1 != v2:
|
||||||
if v1 != v2:
|
polyline = curve.splines.new("POLY")
|
||||||
polyline = curve.splines.new("POLY")
|
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v1)
|
||||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
|
v2 = vertices[edge[1]]
|
||||||
v2 = vertices[edge[1]]
|
polyline.points.add(1)
|
||||||
polyline.points.add(1)
|
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v2)
|
||||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
|
|
||||||
|
|
||||||
curve.bevel_depth = self.unit_scale * item.Radius
|
curve.bevel_depth = self.unit_scale * item.Radius
|
||||||
curve.use_fill_caps = True
|
curve.use_fill_caps = True
|
||||||
return curve
|
return curve
|
||||||
|
|
||||||
def create_native_annotation(self, element, mesh_name):
|
|
||||||
# TODO: georeferencing?
|
|
||||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
|
||||||
curve.dimensions = "3D"
|
|
||||||
curve.resolution_u = 2
|
|
||||||
polyline = curve.splines.new("POLY")
|
|
||||||
|
|
||||||
for representation in self.native_data[element.GlobalId]["representations"]:
|
|
||||||
for item in representation["raw"].Items:
|
|
||||||
# TODO: support inner radius, start param, and end param
|
|
||||||
geometry = self.create_generic_shape(item.Directrix)
|
|
||||||
e = geometry.edges
|
|
||||||
v = geometry.verts
|
|
||||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
|
||||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
|
||||||
v2 = None
|
|
||||||
for edge in edges:
|
|
||||||
v1 = vertices[edge[0]]
|
|
||||||
if v1 != v2:
|
|
||||||
polyline = curve.splines.new("POLY")
|
|
||||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
|
|
||||||
v2 = vertices[edge[1]]
|
|
||||||
polyline.points.add(1)
|
|
||||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
|
|
||||||
|
|
||||||
curve.bevel_depth = self.unit_scale * item.Radius
|
|
||||||
return curve
|
|
||||||
|
|
||||||
def merge_by_class(self):
|
def merge_by_class(self):
|
||||||
merge_set = {}
|
merge_set = {}
|
||||||
id_set = {}
|
id_set = {}
|
||||||
@@ -1730,41 +1738,6 @@ class IfcImporter:
|
|||||||
result[2][3] *= self.unit_scale
|
result[2][3] *= self.unit_scale
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_body_representation(self, representations):
|
|
||||||
for representation in representations:
|
|
||||||
if (
|
|
||||||
representation.RepresentationIdentifier == "Body"
|
|
||||||
and representation.RepresentationType == "MappedRepresentation"
|
|
||||||
):
|
|
||||||
if len(representation.Items) > 1:
|
|
||||||
return representation
|
|
||||||
return self.get_body_representation([representation.Items[0].MappingSource.MappedRepresentation])
|
|
||||||
elif representation.RepresentationIdentifier == "Body":
|
|
||||||
return representation
|
|
||||||
|
|
||||||
def get_transformed_body_representations(self, representations, matrix=None):
|
|
||||||
if matrix is None:
|
|
||||||
matrix = mathutils.Matrix()
|
|
||||||
results = []
|
|
||||||
for representation in representations:
|
|
||||||
if (
|
|
||||||
representation.RepresentationIdentifier == "Body"
|
|
||||||
and representation.RepresentationType == "MappedRepresentation"
|
|
||||||
):
|
|
||||||
for item in representation.Items:
|
|
||||||
# TODO: Confirm if this transformation is right
|
|
||||||
transform = self.get_axis2placement(item.MappingSource.MappingOrigin)
|
|
||||||
if item.MappingTarget:
|
|
||||||
transform = transform @ self.get_cartesiantransformationoperator(item.MappingTarget)
|
|
||||||
results.extend(
|
|
||||||
self.get_transformed_body_representations(
|
|
||||||
[item.MappingSource.MappedRepresentation], transform @ matrix
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif representation.RepresentationIdentifier == "Body":
|
|
||||||
results.append({"raw": representation, "matrix": self.scale_matrix(matrix)})
|
|
||||||
return results
|
|
||||||
|
|
||||||
def scale_matrix(self, matrix):
|
def scale_matrix(self, matrix):
|
||||||
matrix[0][3] *= self.unit_scale
|
matrix[0][3] *= self.unit_scale
|
||||||
matrix[1][3] *= self.unit_scale
|
matrix[1][3] *= self.unit_scale
|
||||||
|
|||||||
Reference in New Issue
Block a user