Files
IfcOpenShell/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py
T

448 lines
20 KiB
Python

import bpy
import numpy as np
import ifcopenshell
import ifcopenshell.util.element
import logging
import blenderbim.bim.module.geometry.edit_object_placement as edit_object_placement
import blenderbim.bim.module.geometry.add_representation as add_representation
import blenderbim.bim.module.geometry.map_representation as map_representation
import blenderbim.bim.module.geometry.assign_styles as assign_styles
import blenderbim.bim.module.geometry.assign_representation as assign_representation
import blenderbim.bim.module.geometry.unassign_representation as unassign_representation
import blenderbim.bim.module.geometry.remove_representation as remove_representation
import blenderbim.bim.module.grid.create_axis_curve as create_axis_curve
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.data import Data
from blenderbim.bim.module.context.data import Data as ContextData
from blenderbim.bim.module.void.data import Data as VoidData
from mathutils import Vector
def get_context_id(context_type, context_identifier, target_view):
for context in ContextData.contexts.values():
if context["ContextType"] == context_type:
for i, subcontext in context["HasSubContexts"].items():
if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
return i
class EditObjectPlacement(bpy.types.Operator):
bl_idname = "bim.edit_object_placement"
bl_label = "Edit Object Placement"
obj: bpy.props.StringProperty()
def execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
# TODO: determine how to deal with this module dependency
props = bpy.context.scene.BIMGeoreferenceProperties
for obj in objs:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
matrix = np.array(obj.matrix_world)
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
self.calculate_unit_scale()
# TODO: np.array? Why not matrix?
matrix = np.array(
ifcopenshell.util.geolocation.local2global(
np.matrix(obj.matrix_world),
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
)
edit_object_placement.Usecase(
self.file,
{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"matrix": matrix,
},
).execute()
return {"FINISHED"}
def calculate_unit_scale(self):
self.unit_scale = 1
units = self.file.by_type("IfcUnitAssignment")[0]
for unit in units.Units:
if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT":
continue
while unit.is_a("IfcConversionBasedUnit"):
self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue
unit = unit.ConversionFactor.UnitComponent
if unit.is_a("IfcSIUnit"):
self.unit_scale *= ifcopenshell.util.unit.get_prefix_multiplier(unit.Prefix)
class AddRepresentation(bpy.types.Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
obj: bpy.props.StringProperty()
context_id: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
context_id = self.context_id or int(bpy.context.scene.BIMProperties.contexts)
bpy.ops.bim.edit_object_placement(obj=obj.name)
if obj.data:
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
context_of_items = self.file.by_id(context_id)
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
float(gprop.blender_northings),
float(gprop.blender_orthogonal_height),
)
)
representation_data = {
"context": context_of_items,
"blender_object": obj,
"geometry": obj.data,
"coordinate_offset": coordinate_offset,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
}
result = add_representation.Usecase(self.file, representation_data).execute()
if not result:
print("Failed to write shape representation")
return {"FINISHED"}
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
if (
box_context_id
and context_of_items.ContextType == "Model"
and context_of_items.ContextIdentifier
and context_of_items.ContextIdentifier == "Body"
):
representation_data["context"] = self.file.by_id(box_context_id)
new_box = add_representation.Usecase(self.file, representation_data).execute()
assign_representation.Usecase(self.file, {"product": product, "representation": new_box}).execute()
assign_styles.Usecase(
self.file,
{
"shape_representation": result,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
).execute()
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
existing_mesh = obj.data
mesh = obj.data.copy()
mesh.name = "{}/{}".format(context_id, result.id())
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
obj.data = mesh
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class SwitchRepresentation(bpy.types.Operator):
bl_idname = "bim.switch_representation"
bl_label = "Switch Representation"
ifc_definition_id: bpy.props.IntProperty()
disable_opening_subtractions: bpy.props.BoolProperty()
def execute(self, context):
self.obj = bpy.context.active_object
self.oprops = self.obj.BIMObjectProperties
self.file = IfcStore.get_file()
self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
self.mesh_name = "{}/{}".format(self.context_of_items.id(), self.ifc_definition_id)
mesh = bpy.data.meshes.get(self.mesh_name)
if mesh:
self.obj.data.user_remap(mesh)
self.pull_mesh_from_ifc()
return {"FINISHED"}
def pull_mesh_from_ifc(self):
self.file = IfcStore.get_file()
logger = logging.getLogger("ImportIFC")
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger)
element = self.file.by_id(self.oprops.ifc_definition_id)
settings = ifcopenshell.geom.settings()
if self.context_of_items.ContextIdentifier == "Body":
if self.disable_opening_subtractions:
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
else:
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.oprops.ifc_definition_id))
else:
settings.set(settings.INCLUDE_CURVES, True)
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
ifc_importer.file = self.file
mesh = ifc_importer.create_mesh(element, shape)
mesh.name = self.mesh_name
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
self.obj.data.user_remap(mesh)
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
material_creator.create(element, self.obj, mesh)
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
if self.oprops.ifc_definition_id not in VoidData.products:
VoidData.load(self.oprops.ifc_definition_id)
for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
if opening_id in IfcStore.id_map:
opening = IfcStore.id_map[opening_id]
modifier = self.obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
modifier.operation = "DIFFERENCE"
modifier.object = opening
else:
for modifier in self.obj.modifiers:
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
self.obj.modifiers.remove(modifier)
class RemoveRepresentation(bpy.types.Operator):
bl_idname = "bim.remove_representation"
bl_label = "Remove Representation"
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
representation = self.file.by_id(self.representation_id)
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
if is_mapped_representation:
mesh_name = "{}/{}".format(
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
)
else:
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = bpy.data.meshes.get(mesh_name)
if mesh:
if obj.data == mesh:
# TODO we can do better than this
void_mesh = bpy.data.meshes.get("Void")
if not void_mesh:
void_mesh = bpy.data.meshes.new("Void")
obj.data = void_mesh
if not is_mapped_representation:
bpy.data.meshes.remove(mesh)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
unassign_representation.Usecase(self.file, {"product": product, "representation": representation}).execute()
remove_representation.Usecase(self.file, {"representation": representation}).execute()
Data.load(product.id())
return {"FINISHED"}
class MapRepresentations(bpy.types.Operator):
bl_idname = "bim.map_representations"
bl_label = "Map Representations"
product_id: bpy.props.IntProperty()
type_product_id: bpy.props.IntProperty()
def execute(self, context):
related_object = IfcStore.id_map[self.product_id]
if self.product_id not in Data.products:
Data.load(self.product_id)
for representation_id in Data.products[self.product_id]:
bpy.ops.bim.remove_representation(obj=related_object.name, representation_id=representation_id)
if self.type_product_id not in Data.products:
Data.load(self.type_product_id)
for representation_id in Data.products[self.type_product_id]:
bpy.ops.bim.map_representation(
obj=related_object.name,
representation_id=representation_id,
obj_data=IfcStore.id_map[self.type_product_id].data.name,
)
return {"FINISHED"}
class MapRepresentation(bpy.types.Operator):
bl_idname = "bim.map_representation"
bl_label = "Map Representation"
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty()
obj_data: bpy.props.StringProperty()
def execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else None
self.file = IfcStore.get_file()
for obj in objs:
bpy.ops.bim.edit_object_placement(obj=obj.name)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if obj_data:
obj.data = obj_data
result = map_representation.Usecase(
self.file, {"representation": self.file.by_id(self.representation_id)}
).execute()
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UpdateMeshRepresentation(bpy.types.Operator):
bl_idname = "bim.update_mesh_representation"
bl_label = "Update Mesh Representation"
obj: bpy.props.StringProperty()
ifc_representation_class: bpy.props.StringProperty()
def execute(self, context):
if not ContextData.is_loaded:
ContextData.load()
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
for obj in objs:
self.update_obj_mesh_representation(context, obj)
return {"FINISHED"}
def update_obj_mesh_representation(self, context, obj):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if product.is_a("IfcGridAxis"):
create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": product}).execute()
return
bpy.ops.bim.edit_object_placement(obj=obj.name)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
context_of_items = old_representation.ContextOfItems
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
if gprop.has_blender_offset and gprop.blender_offset_type == "CARTESIAN_POINT":
coordinate_offset = Vector(
(
float(gprop.blender_eastings),
float(gprop.blender_northings),
float(gprop.blender_orthogonal_height),
)
)
representation_data = {
"context": context_of_items,
"blender_object": obj,
"geometry": obj.data,
"coordinate_offset": coordinate_offset,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
"ifc_representation_class": self.ifc_representation_class
}
new_representation = add_representation.Usecase(self.file, representation_data).execute()
#if product.is_a("IfcWall"):
# # Generate axis representation
# axis_context_id = get_context_id("Model", "Axis", "MODEL_VIEW")
# old_axis = ifcopenshell.util.element.get_representation(product, "Model", "Axis", "MODEL_VIEW")
# if (
# axis_context_id
# and old_axis
# and context_of_items.ContextType == "Model"
# and context_of_items.ContextIdentifier
# and context_of_items.ContextIdentifier == "Body"
# ):
# has_axis_generator = False
# if has_axis_generator:
# # TODO, just pseudocode for now
# representation_data["geometry"] = axis_generator_function_call
# pass
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
old_box = ifcopenshell.util.element.get_representation(product, "Model", "Box", "MODEL_VIEW")
if (
box_context_id
and old_box
and context_of_items.ContextType == "Model"
and context_of_items.ContextIdentifier
and context_of_items.ContextIdentifier == "Body"
):
representation_data["context"] = self.file.by_id(box_context_id)
new_box = add_representation.Usecase(self.file, representation_data).execute()
for inverse in self.file.get_inverse(old_box):
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
assign_styles.Usecase(
self.file,
{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
).execute()
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(representation_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
class UpdateParametricRepresentation(bpy.types.Operator):
bl_idname = "bim.update_parametric_representation"
bl_label = "Update Parametric Representation"
index: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
props = obj.data.BIMMeshProperties
parameter = props.ifc_parameters[self.index]
element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value
bpy.ops.bim.switch_representation(ifc_definition_id=props.ifc_definition_id)
return {"FINISHED"}
class GetRepresentationIfcParameters(bpy.types.Operator):
bl_idname = "bim.get_representation_ifc_parameters"
bl_label = "Get Representation IFC Parameters"
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.context.active_object
props = obj.data.BIMMeshProperties
elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id))
for element in elements:
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
for i in range(0, len(element)):
if element.attribute_type(i) == "DOUBLE":
new = props.ifc_parameters.add()
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
new.step_id = element.id()
new.type = element.attribute_type(i)
new.index = i
if element[i]:
new.value = element[i]
return {"FINISHED"}