mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 14:02:27 +00:00
WIP refactor ability to partially write a new shape representation. See #1222.
This commit is contained in:
@@ -162,7 +162,6 @@ if bpy is not None:
|
|||||||
operator.AddVariable,
|
operator.AddVariable,
|
||||||
operator.RemoveVariable,
|
operator.RemoveVariable,
|
||||||
operator.PropagateTextData,
|
operator.PropagateTextData,
|
||||||
operator.PushRepresentation,
|
|
||||||
operator.ConvertLocalToGlobal,
|
operator.ConvertLocalToGlobal,
|
||||||
operator.ConvertGlobalToLocal,
|
operator.ConvertGlobalToLocal,
|
||||||
operator.GuessQuantity,
|
operator.GuessQuantity,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ifcopenshell.geom
|
|||||||
import ifcopenshell.util.geolocation
|
import ifcopenshell.util.geolocation
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
import ifcopenshell.util.unit
|
||||||
import bpy
|
import bpy
|
||||||
import bmesh
|
import bmesh
|
||||||
import os
|
import os
|
||||||
@@ -19,7 +20,6 @@ import tempfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from . import helper
|
|
||||||
from . import ifc
|
from . import ifc
|
||||||
from . import schema
|
from . import schema
|
||||||
|
|
||||||
@@ -953,6 +953,10 @@ class IfcImporter:
|
|||||||
mesh = self.create_native_mesh(element, shape)
|
mesh = self.create_native_mesh(element, shape)
|
||||||
if mesh is None:
|
if mesh is None:
|
||||||
mesh = self.create_mesh(element, shape)
|
mesh = self.create_mesh(element, shape)
|
||||||
|
if "-" in shape.geometry.id:
|
||||||
|
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id.split("-")[0])
|
||||||
|
else:
|
||||||
|
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id)
|
||||||
self.meshes[mesh_name] = mesh
|
self.meshes[mesh_name] = mesh
|
||||||
else:
|
else:
|
||||||
mesh = None
|
mesh = None
|
||||||
@@ -1503,7 +1507,7 @@ class IfcImporter:
|
|||||||
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
|
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
|
||||||
unit = unit.ConversionFactor.UnitComponent
|
unit = unit.ConversionFactor.UnitComponent
|
||||||
if unit.is_a("IfcSIUnit"):
|
if unit.is_a("IfcSIUnit"):
|
||||||
self.unit_scale *= helper.SIUnitHelper.get_prefix_multiplier(unit.Prefix)
|
self.unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
|
||||||
|
|
||||||
def set_units(self):
|
def set_units(self):
|
||||||
units = self.file.by_type("IfcUnitAssignment")[0]
|
units = self.file.by_type("IfcUnitAssignment")[0]
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import ifcopenshell.util.unit
|
||||||
|
|
||||||
|
|
||||||
|
class Usecase:
|
||||||
|
def __init__(self, file, settings=None):
|
||||||
|
# TODO: This usecase currently depends on Blender's data model
|
||||||
|
self.file = file
|
||||||
|
self.settings = {
|
||||||
|
"context": None, # IfcGeometricRepresentationContext
|
||||||
|
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
|
||||||
|
"total_items": 1, # How many representation items to create
|
||||||
|
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
|
||||||
|
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
|
||||||
|
"is_wireframe": False, # If the geometry is a wireframe
|
||||||
|
"is_curve": False, # If the geometry is a Blender curve
|
||||||
|
"is_point_cloud": False, # If the geometry is a point cloud
|
||||||
|
}
|
||||||
|
self.ifc_vertices = []
|
||||||
|
for key, value in settings.items():
|
||||||
|
self.settings[key] = value
|
||||||
|
self.context_of_items = None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if self.settings["unit_scale"] is None:
|
||||||
|
self.settings["unit_scale"] = self.calculate_unit_scale()
|
||||||
|
if self.settings["context"].ContextType == "Model":
|
||||||
|
return self.create_model_representation()
|
||||||
|
elif self.settings["context"].ContextType == "Plan":
|
||||||
|
return self.create_plan_representation()
|
||||||
|
return self.create_variable_representation()
|
||||||
|
|
||||||
|
def calculate_unit_scale(self):
|
||||||
|
units = self.file.by_type("IfcUnitAssignment")[0]
|
||||||
|
unit_scale = 1
|
||||||
|
for unit in units.Units:
|
||||||
|
if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT":
|
||||||
|
continue
|
||||||
|
while unit.is_a("IfcConversionBasedUnit"):
|
||||||
|
unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
|
||||||
|
unit = unit.ConversionFactor.UnitComponent
|
||||||
|
if unit.is_a("IfcSIUnit"):
|
||||||
|
unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
|
||||||
|
return unit_scale
|
||||||
|
|
||||||
|
def create_model_representation(self):
|
||||||
|
if self.settings["context"].is_a() == "IfcGeometricRepresentationContext":
|
||||||
|
return self.create_variable_representation()
|
||||||
|
if self.settings["context"].ContextIdentifier == "Annotation":
|
||||||
|
return self.create_geometric_set_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Axis":
|
||||||
|
return self.create_curve3d_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Body":
|
||||||
|
return self.create_variable_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Box":
|
||||||
|
return self.create_box_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Clearance":
|
||||||
|
return self.create_variable_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "CoG":
|
||||||
|
return self.create_cog_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "FootPrint":
|
||||||
|
return self.create_variable_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Reference":
|
||||||
|
if self.settings["context"].TargetView == "GRAPH_VIEW":
|
||||||
|
return self.create_structural_reference_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Profile":
|
||||||
|
return self.create_curve3d_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "SurveyPoints":
|
||||||
|
return self.create_geometric_curve_set_representation()
|
||||||
|
|
||||||
|
def create_plan_representation(self):
|
||||||
|
if self.settings["context"].ContextIdentifier == "Annotation":
|
||||||
|
if self.settings["is_text"]:
|
||||||
|
return self.create_text_representation()
|
||||||
|
shape_representation = self.create_geometric_curve_set_representation(is_2d=True)
|
||||||
|
shape_representation.RepresentationType = "Annotation2D"
|
||||||
|
return shape_representation
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Axis":
|
||||||
|
return self.create_curve2d_representation()
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Body":
|
||||||
|
pass
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Box":
|
||||||
|
pass
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Clearance":
|
||||||
|
pass
|
||||||
|
elif self.settings["context"].ContextIdentifier == "CoG":
|
||||||
|
pass
|
||||||
|
elif self.settings["context"].ContextIdentifier == "FootPrint":
|
||||||
|
if self.settings["context"].TargetView in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
||||||
|
return self.create_geometric_curve_set_representation(is_2d=True)
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Reference":
|
||||||
|
pass
|
||||||
|
elif self.settings["context"].ContextIdentifier == "Profile":
|
||||||
|
pass
|
||||||
|
elif self.settings["context"].ContextIdentifier == "SurveyPoints":
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_variable_representation(self):
|
||||||
|
if self.settings["is_wireframe"]:
|
||||||
|
return self.create_wireframe_representation()
|
||||||
|
elif self.settings["is_curve"]:
|
||||||
|
return self.create_curve_representation()
|
||||||
|
elif self.settings["is_point_cloud"]:
|
||||||
|
return self.create_point_cloud_representation()
|
||||||
|
return self.create_mesh_representation()
|
||||||
|
|
||||||
|
def create_mesh_representation(self):
|
||||||
|
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
|
||||||
|
return self.create_faceted_brep()
|
||||||
|
return self.create_polygonal_face_set()
|
||||||
|
|
||||||
|
def create_faceted_brep(self):
|
||||||
|
self.create_vertices()
|
||||||
|
ifc_raw_items = [None] * self.settings["total_items"]
|
||||||
|
for i, value in enumerate(ifc_raw_items):
|
||||||
|
ifc_raw_items[i] = []
|
||||||
|
for polygon in self.settings["geometry"].polygons:
|
||||||
|
ifc_raw_items[polygon.material_index % self.settings["total_items"]].append(
|
||||||
|
self.file.createIfcFace(
|
||||||
|
[
|
||||||
|
self.file.createIfcFaceOuterBound(
|
||||||
|
self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# TODO: May not actually be a closed shell, but who checks anyway?
|
||||||
|
items = [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(i)) for i in ifc_raw_items if i]
|
||||||
|
return self.file.createIfcShapeRepresentation(
|
||||||
|
self.settings["context"],
|
||||||
|
self.settings["context"].ContextIdentifier,
|
||||||
|
"Brep",
|
||||||
|
items,
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_vertices(self, is_2d=False):
|
||||||
|
if is_2d:
|
||||||
|
for v in self.settings["geometry"].vertices:
|
||||||
|
co = self.convert_si_to_unit(v.co)
|
||||||
|
self.ifc_vertices.append(self.file.createIfcCartesianPoint((co[0], co[1])))
|
||||||
|
return
|
||||||
|
self.ifc_vertices.extend(
|
||||||
|
[
|
||||||
|
self.file.createIfcCartesianPoint(self.convert_si_to_unit(v.co))
|
||||||
|
for v in self.settings["geometry"].vertices
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def convert_si_to_unit(self, co):
|
||||||
|
return co / self.settings["unit_scale"]
|
||||||
@@ -3362,117 +3362,6 @@ class PropagateTextData(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class PushRepresentation(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.push_representation"
|
|
||||||
bl_label = "Push Representation"
|
|
||||||
|
|
||||||
# Warning: This is an incredibly experimental operator.
|
|
||||||
def execute(self, context):
|
|
||||||
self.file = ifc.IfcStore.get_file()
|
|
||||||
|
|
||||||
logger = logging.getLogger("ExportIFC")
|
|
||||||
output_file = "tmp.ifc"
|
|
||||||
ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
|
||||||
qto_calculator = qto.QtoCalculator()
|
|
||||||
ifc_parser = export_ifc.IfcParser(ifc_export_settings, qto_calculator)
|
|
||||||
ifc_parser.parse([bpy.context.active_object])
|
|
||||||
self.ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser)
|
|
||||||
self.ifc_exporter.file = ifcopenshell.file(schema=self.file.schema)
|
|
||||||
self.ifc_exporter.create_origin()
|
|
||||||
self.ifc_exporter.create_rep_context()
|
|
||||||
self.ifc_exporter.create_representations()
|
|
||||||
|
|
||||||
self.context, self.subcontext, self.target_view, self.mesh_name = bpy.context.active_object.data.name.split("/")
|
|
||||||
rep_context = self.get_geometric_representation_context()
|
|
||||||
|
|
||||||
for key, rep in self.ifc_exporter.ifc_parser.representations.items():
|
|
||||||
if key != bpy.context.active_object.data.name:
|
|
||||||
continue
|
|
||||||
if rep_context:
|
|
||||||
self.ifc_exporter.file.add(rep_context)
|
|
||||||
rep["ifc"].MappedRepresentation.ContextOfItems = rep_context
|
|
||||||
self.push_representation(rep["ifc"])
|
|
||||||
break
|
|
||||||
self.file.write(bpy.context.scene.BIMProperties.ifc_file[0:-4] + "-patch.ifc")
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
def get_geometric_representation_context(self):
|
|
||||||
for element in self.file.by_type("IfcGeometricRepresentationSubContext"):
|
|
||||||
if self.is_current_context(element):
|
|
||||||
return element
|
|
||||||
|
|
||||||
def push_representation(self, new_representation):
|
|
||||||
element = self.file.by_guid(
|
|
||||||
bpy.context.active_object.BIMObjectProperties.attributes.get("GlobalId").string_value
|
|
||||||
)
|
|
||||||
old_shape = None
|
|
||||||
new_shape = self.file.add(new_representation.MappedRepresentation)
|
|
||||||
if element.is_a("IfcProduct"):
|
|
||||||
representations = element.Representation.Representations
|
|
||||||
else:
|
|
||||||
representations = [rm.MappedRepresentation for rm in element.RepresentationMaps]
|
|
||||||
for representation in representations:
|
|
||||||
if self.is_current_context(representation.ContextOfItems):
|
|
||||||
old_shape = self.resolve_mapped_representation(representation)
|
|
||||||
break
|
|
||||||
if old_shape:
|
|
||||||
self.swap_old_representation(old_shape, new_shape)
|
|
||||||
else:
|
|
||||||
self.add_new_representation(element, new_shape)
|
|
||||||
|
|
||||||
def resolve_mapped_representation(self, representation):
|
|
||||||
if representation.RepresentationType == "MappedRepresentation":
|
|
||||||
if representation.Items:
|
|
||||||
return representation.Items[0].MappingSource.MappedRepresentation
|
|
||||||
return representation
|
|
||||||
|
|
||||||
def swap_old_representation(self, old, new):
|
|
||||||
inverse_elements = self.file.get_inverse(old)
|
|
||||||
for element in inverse_elements:
|
|
||||||
for i, attribute in enumerate(element):
|
|
||||||
if (isinstance(attribute, list) or isinstance(attribute, tuple)) and old in attribute:
|
|
||||||
items = list(attribute)
|
|
||||||
for j, item in enumerate(items):
|
|
||||||
if item == old:
|
|
||||||
del items[j]
|
|
||||||
items.append(new)
|
|
||||||
element[i] = items
|
|
||||||
elif attribute == old:
|
|
||||||
element[i] = new
|
|
||||||
|
|
||||||
def add_new_representation(self, element, new):
|
|
||||||
if element.is_a("IfcProduct"):
|
|
||||||
self.add_new_representation_to_product(element, new)
|
|
||||||
return
|
|
||||||
|
|
||||||
if element.RepresentationMaps:
|
|
||||||
representation_maps = list(element.RepresentationMaps)
|
|
||||||
representation_maps.append(self.file.createIfcRepresentationMap(self.ifc_exporter.origin, new))
|
|
||||||
element.RepresentationMaps = representation_maps
|
|
||||||
else:
|
|
||||||
element.RepresentationMaps = self.file.createIfcRepresentationMap(self.ifc_exporter.origin, new)
|
|
||||||
|
|
||||||
if hasattr(element, "Types"):
|
|
||||||
related_objects = element.Types[0].RelatedObjects
|
|
||||||
elif hasattr(element, "ObjectTypeOf"): # IFC2X3
|
|
||||||
related_objects = element.ObjectTypeOf[0].RelatedObjects
|
|
||||||
|
|
||||||
for related_object in related_objects:
|
|
||||||
self.add_new_representation_to_product(related_object, new)
|
|
||||||
|
|
||||||
def add_new_representation_to_product(self, element, new):
|
|
||||||
representations = list(element.Representation.Representations)
|
|
||||||
representations.append(new)
|
|
||||||
element.Representation.Representations = representations
|
|
||||||
|
|
||||||
def is_current_context(self, element):
|
|
||||||
return (
|
|
||||||
element.ContextType == self.context
|
|
||||||
and element.ContextIdentifier == self.subcontext
|
|
||||||
and element.TargetView == self.target_view
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ConvertLocalToGlobal(bpy.types.Operator):
|
class ConvertLocalToGlobal(bpy.types.Operator):
|
||||||
bl_idname = "bim.convert_local_to_global"
|
bl_idname = "bim.convert_local_to_global"
|
||||||
bl_label = "Convert Local To Global"
|
bl_label = "Convert Local To Global"
|
||||||
@@ -4458,16 +4347,23 @@ class BakeParametricGeometry(bpy.types.Operator):
|
|||||||
bl_label = "Bake Parametric Geometry"
|
bl_label = "Bake Parametric Geometry"
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# TODO rewrite, see #1222
|
import blenderbim.bim.module.geometry.add_shape_representation as add_shape_representation
|
||||||
# obj = bpy.context.active_object
|
obj = bpy.context.active_object
|
||||||
# if "/" not in obj.data.name:
|
self.file = ifc.IfcStore.get_file()
|
||||||
# context, subcontext, target_view = ("Model", "Body", "MODEL_VIEW")
|
element = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||||
# else:
|
usecase = add_shape_representation.Usecase(self.file, {
|
||||||
# context, subcontext, target_view = obj.data.name.split("/")[0:3]
|
"context": element.ContextOfItems,
|
||||||
# for representation in obj.BIMObjectProperties.representations:
|
"geometry": obj.data,
|
||||||
# element = self.file.by_id(representation.ifc_definition_id)
|
"total_items": max(1, len(obj.material_slots)),
|
||||||
# if ifcopenshell.util.element.is_representation_of_context(element, context, subcontext, target_view):
|
})
|
||||||
# representation.ifc_definition_id = 0
|
result = usecase.execute()
|
||||||
|
if not result:
|
||||||
|
print("Failed to write shape representation")
|
||||||
|
return {"FINISHED"}
|
||||||
|
for inverse in self.file.get_inverse(element):
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, element, result)
|
||||||
|
obj.data.BIMMeshProperties.ifc_definition_id = int(result.id())
|
||||||
|
print(result)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -809,9 +809,6 @@ class BIM_PT_mesh(Panel):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
props = context.active_object.data.BIMMeshProperties
|
props = context.active_object.data.BIMMeshProperties
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.operator("bim.push_representation")
|
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(props, "geometry_type")
|
row.prop(props, "geometry_type")
|
||||||
layout.label(text="IFC Parameters:")
|
layout.label(text="IFC Parameters:")
|
||||||
|
|||||||
Reference in New Issue
Block a user