diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py
index 2e4e2098c9..5a6683df0d 100644
--- a/src/ifcbimtester/bimtester/reports.py
+++ b/src/ifcbimtester/bimtester/reports.py
@@ -21,12 +21,12 @@ class ReportGenerator:
self.generate_feature_report(feature, output_file)
def generate_feature_report(self, feature, output_file):
- file_name = os.path.basename(feature["location"]).split(":")[0]
+ # file_name = os.path.basename(feature["location"]).split(":")[0]
data = {
- "file_name": file_name,
+ # "file_name": file_name,
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"name": feature["name"],
- "description": feature["description"],
+ "description": feature.get("description", ""),
"is_success": feature["status"] == "passed",
"scenarios": [],
}
diff --git a/src/ifcbimtester/bimtester/resources/reports/template.html b/src/ifcbimtester/bimtester/resources/reports/template.html
index 98761cc5e1..94a0a31f34 100644
--- a/src/ifcbimtester/bimtester/resources/reports/template.html
+++ b/src/ifcbimtester/bimtester/resources/reports/template.html
@@ -35,11 +35,11 @@
{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}
{{_tests_passed}}: {{total_passes}} / {{total_steps}} ({{pass_rate}}%)
+ {{#description}}
- {{#description}}
{{.}}
- {{/description}}
+ {{/description}}
{{#scenarios}}
diff --git a/src/ifcbimtester/bimtester/run.py b/src/ifcbimtester/bimtester/run.py
index a95dd6af42..a80867ffeb 100644
--- a/src/ifcbimtester/bimtester/run.py
+++ b/src/ifcbimtester/bimtester/run.py
@@ -1,6 +1,8 @@
import os
import sys
+import json
import shutil
+import logging
import tempfile
import ifcopenshell
@@ -14,6 +16,46 @@ from distutils.dir_util import copy_tree
from behave.__main__ import main as behave_main
+# TODO: refactor when this isn't super experimental
+from logging import StreamHandler
+
+class IDSHandler(StreamHandler):
+ def __init__(self):
+ StreamHandler.__init__(self)
+ self.results = {
+ "name": "Specification name",
+ "status": "passed",
+ "location": "filename.xml",
+ "elements": [
+ {
+ "keyword": "Scenario",
+ "name": "Checking IDS specifications",
+ "status": "passed",
+ "steps": []
+ }
+ ]
+ }
+
+ def emit(self, record):
+ msg = self.format(record)
+ # Obviously, not a final product
+ is_fail = "is compliant" not in msg
+ if is_fail:
+ self.results["status"] = "failed"
+ self.results["elements"][0]["status"] = "failed"
+ self.results["elements"][0]["steps"].append({
+ "keyword": "*",
+ "match": {},
+ "name": msg,
+ "result": {
+ "duration": 0.0,
+ "error_message": "Assertion Failed",
+ "status": "failed" if is_fail else "passed"
+ },
+ "step_type": "given"
+ })
+
+
class TestRunner:
def __init__(self, ifc_path, schema_path=None, ifc=None):
IfcStore.path = ifc_path
@@ -34,6 +76,27 @@ class TestRunner:
self.locale_path = os.path.join(self.base_path, "locale")
def run(self, args):
+ if args["feature"][-4:].lower() == ".xml":
+ return self.test_ids(args)
+ return self.test_feature(args)
+
+ def test_ids(self, args):
+ # Local import whilst this is experimental
+ import ifcopenshell.ids
+
+ logger = logging.getLogger("IDS")
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
+ ids_handler = IDSHandler()
+ logger.addHandler(ids_handler)
+ ids_file = ifcopenshell.ids.ids(args["feature"])
+ ids_file.validate(IfcStore.file, logger)
+
+ tmpdir = tempfile.mkdtemp()
+ report_json = os.path.join(tmpdir, "report.json")
+ json.dump([ids_handler.results], open(report_json, "w"))
+ return report_json
+
+ def test_feature(self, args):
tmpdir = tempfile.mkdtemp()
features_path = os.path.join(tmpdir, "features")
steps_path = os.path.join(features_path, "steps")
diff --git a/src/ifcbimtester/cli.py b/src/ifcbimtester/cli.py
index 38179de6b6..b7609d2398 100644
--- a/src/ifcbimtester/cli.py
+++ b/src/ifcbimtester/cli.py
@@ -10,7 +10,7 @@ parser = argparse.ArgumentParser(description="Runs unit tests for BIM data")
parser.add_argument("-a", "--action", type=str, help="Action to perform, from run/purge", default="run")
parser.add_argument("--advanced-arguments", type=str, help="Specify arguments to Behave", default="")
parser.add_argument("-c", "--console", action="store_true", help="Show results in the console")
-parser.add_argument("-f", "--feature", type=str, help="Specify a feature file to test", required=True)
+parser.add_argument("-f", "--feature", type=str, help="Specify a feature file or IDS to test", required=True)
parser.add_argument("-i", "--ifc", type=str, help="Specify an IFC file to test", required=True)
parser.add_argument("-p", "--path", type=str, help="Define a path for use in test steps that use relative paths")
parser.add_argument("-r", "--report", type=str, help="Specify an output file for a HTML report")
diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py
index 31c9113e61..0310719317 100644
--- a/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py
+++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py
@@ -2,6 +2,7 @@ import bpy
import bmesh
import ifcopenshell.util.unit
from mathutils import Vector
+from blenderbim.bim.module.geometry.helper import Helper
class Usecase:
@@ -20,6 +21,11 @@ class Usecase:
"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
+ # Possible IFC representation classes:
+ # IfcExtrudedAreaSolid/IfcRectangleProfileDef
+ # IfcExtrudedAreaSolid/IfcCircleProfileDef
+ # IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
+ "ifc_representation_class": None, # Whether to cast a mesh into a particular class
}
self.ifc_vertices = []
for key, value in settings.items():
@@ -124,6 +130,12 @@ class Usecase:
return self.create_curve_representation()
elif self.settings["is_point_cloud"]:
return self.create_point_cloud_representation()
+ elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcRectangleProfileDef":
+ return self.create_rectangle_extrusion_representation()
+ elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcCircleProfileDef":
+ return self.create_circle_extrusion_representation()
+ elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef":
+ return self.create_arbitrary_extrusion_representation()
return self.create_mesh_representation()
def create_curve3d_representation(self):
@@ -215,6 +227,42 @@ class Usecase:
results.append(self.file.createIfcPolyline(points))
return results
+ def create_rectangle_extrusion_representation(self):
+ helper = Helper(self.file)
+ indices = helper.auto_detect_rectangle_profile_extruded_area_solid(self.settings["geometry"])
+ profile_def = helper.create_rectangle_profile_def(self.settings["geometry"], indices["profile"])
+ 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_circle_extrusion_representation(self):
+ helper = Helper(self.file)
+ indices = helper.auto_detect_circle_profile_extruded_area_solid(self.settings["geometry"])
+ profile_def = helper.create_circle_profile_def(self.settings["geometry"], indices["profile"])
+ 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_arbitrary_extrusion_representation(self):
+ helper = Helper(self.file)
+ indices = helper.auto_detect_arbitrary_closed_profile_extruded_area_solid(self.settings["geometry"])
+ profile_def = helper.create_arbitrary_closed_profile_def(self.settings["geometry"], indices["profile"])
+ 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):
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
return self.create_faceted_brep()
diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/helper.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/helper.py
new file mode 100644
index 0000000000..7cf1bb78ac
--- /dev/null
+++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/helper.py
@@ -0,0 +1,253 @@
+import bpy
+import bmesh
+import ifcopenshell
+import ifcopenshell.util.unit
+from math import pi
+from mathutils import Vector, Matrix
+
+
+class Helper:
+ def __init__(self, file):
+ self.file = file
+ self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
+
+ # We can detect a rectangular extrusion by picking any face, then find an
+ # edge that shares a single vertex only with that face to find the extrusion
+ # edge. A face with the normal facing down is prioritised. A limited
+ # dissolve ensure that faces are quads and not tris.
+ def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
+
+ bm.faces.ensure_lookup_table()
+ face = None
+ for face in bm.faces:
+ if face.normal.z < -0.1:
+ break
+ profile = [l.vert.index for l in face.loops]
+ extrusion = self.detect_extrusion_edge(bm, face)
+
+ bm.to_mesh(mesh)
+ mesh.update()
+ bm.free()
+
+ return {"profile": profile, "extrusion": extrusion}
+
+ # After a limited dissolve, we detect the circle profile as it should be the
+ # only ngon (this assumes the circle has at least a facetation of > 4 edges.
+ # The extrusion direction is any edge that only shares a single vertex with
+ # the profile. We prioritise the profile that has a downwards normal.
+ def auto_detect_circle_profile_extruded_area_solid(self, mesh):
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
+
+ bm.faces.ensure_lookup_table()
+ potential_faces = []
+ for face in bm.faces:
+ if len(face.verts) > 4:
+ potential_faces.append(face)
+ for face in potential_faces:
+ if face.normal.z < -0.1:
+ break
+
+ profile = [l.vert.index for l in face.loops]
+ extrusion = self.detect_extrusion_edge(bm, face)
+
+ bm.to_mesh(mesh)
+ mesh.update()
+ bm.free()
+
+ return {"profile": profile, "extrusion": extrusion}
+
+ # After a limited dissolve, the arbitrary profile is any ngon or tri.
+ # Failing that, it is equivalent to a rectangular profile. The extrusion
+ # direction is any edge that only shares a single vertex with the profile.
+ # We prioritise the profile that has a downwards normal.
+ def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh):
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+ bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, 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 or total_verts == 3:
+ potential_faces.append(face)
+
+ if not potential_faces:
+ potential_faces = bm.faces
+
+ for face in potential_faces:
+ if face.normal.z < -0.1:
+ break
+
+ profile = [l.vert.index for l in face.loops]
+ extrusion = self.detect_extrusion_edge(bm, face)
+
+ bm.to_mesh(mesh)
+ mesh.update()
+ bm.free()
+
+ return {"profile": profile, "extrusion": extrusion}
+
+ def detect_extrusion_edge(self, bm, profile_face):
+ bm.edges.ensure_lookup_table()
+ extrusion = None
+ face_verts_set = set(profile_face.verts)
+ for edge in bm.edges:
+ unshared_verts = set(edge.verts) - face_verts_set
+ if len(unshared_verts) == 1:
+ if unshared_verts.pop() == edge.verts[1]:
+ return [edge.verts[0].index, edge.verts[1].index]
+ return [edge.verts[1].index, edge.verts[0].index]
+
+ def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def):
+ position = self.create_ifc_axis_2_placement_3d(
+ profile_def["curve_ucs"]["center"], profile_def["curve_ucs"]["z_axis"], profile_def["curve_ucs"]["x_axis"]
+ )
+ direction = self.get_extrusion_direction(mesh, extrusion_indices, profile_def["curve_ucs"])
+ unit_direction = direction.normalized()
+ return self.file.createIfcExtrudedAreaSolid(
+ profile_def["curve"],
+ position,
+ self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)),
+ self.convert_si_to_unit(direction.length),
+ )
+
+ def create_arbitrary_closed_profile_def(self, mesh, profile_indices):
+ curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
+ outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs)
+ curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve)
+ return {"curve_ucs": curve_ucs, "curve": curve}
+
+ def create_rectangle_profile_def(self, mesh, profile_indices):
+ curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
+ xdim = self.convert_si_to_unit(
+ (mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[1]].co).length
+ )
+ ydim = self.convert_si_to_unit(
+ (mesh.vertices[profile_indices[1]].co - mesh.vertices[profile_indices[2]].co).length
+ )
+ curve = self.file.createIfcRectangleProfileDef("AREA", None, None, xdim, ydim)
+ return {"curve_ucs": curve_ucs, "curve": curve}
+
+ def create_circle_profile_def(self, mesh, profile_indices):
+ curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices)
+ 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
+ )
+ center = Vector((0, 0))
+ position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
+ curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius)
+ return {"curve_ucs": curve_ucs, "curve": curve}
+
+ # Not used anywhere, but probably useful in the future
+ def get_loop_from_v_indices(self, obj, indices):
+ edges = self.get_edges_in_v_indices(obj, indices)
+ loop = self.get_loop_from_edges(edges)
+ loop.pop(-1)
+ return loop
+
+ def get_loop_from_edges(self, edges):
+ while edges:
+ currentEdge = edges.pop()
+ startVert = currentEdge.vertices[0]
+ endVert = currentEdge.vertices[1]
+ polyLine = [startVert, endVert]
+ ok = 1
+ while ok:
+ ok = 0
+ i = len(edges)
+ while i:
+ i -= 1
+ ed = edges[i]
+ if ed.vertices[0] == endVert:
+ polyLine.append(ed.vertices[1])
+ endVert = polyLine[-1]
+ ok = 1
+ del edges[i]
+ elif ed.vertices[1] == endVert:
+ polyLine.append(ed.vertices[0])
+ endVert = polyLine[-1]
+ ok = 1
+ del edges[i]
+ elif ed.vertices[0] == startVert:
+ polyLine.insert(0, ed.vertices[1])
+ startVert = polyLine[0]
+ ok = 1
+ del edges[i]
+ elif ed.vertices[1] == startVert:
+ polyLine.insert(0, ed.vertices[0])
+ startVert = polyLine[0]
+ ok = 1
+ del edges[i]
+ return polyLine
+
+ def get_edges_in_v_indices(self, obj, indices):
+ return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)]
+
+ def get_curve_profile_coordinate_system(self, mesh, loop):
+ profile_face = bpy.data.meshes.new("profile_face")
+ profile_verts = [(mesh.vertices[p].co.x, mesh.vertices[p].co.y, mesh.vertices[p].co.z) for p in loop]
+ profile_faces = [tuple(range(0, len(profile_verts)))]
+ profile_face.from_pydata(profile_verts, [], profile_faces)
+ center = profile_face.polygons[0].center
+ if (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).length < 0.01:
+ x_axis = (mesh.vertices[loop[0]].co - center).normalized()
+ else:
+ x_axis = (mesh.vertices[loop[1]].co - mesh.vertices[loop[0]].co).normalized()
+ z_axis = profile_face.polygons[0].normal.normalized()
+ y_axis = z_axis.cross(x_axis).normalized()
+ matrix = Matrix((x_axis, y_axis, z_axis))
+ matrix.normalize()
+ return {
+ "center": center,
+ "x_axis": x_axis,
+ "y_axis": y_axis,
+ "z_axis": z_axis,
+ "matrix": matrix.to_4x4() @ Matrix.Translation(-center),
+ }
+
+ def convert_si_to_unit(self, co):
+ return co / self.unit_scale
+
+ def create_polyline_from_loop(self, mesh, loop, curve_ucs):
+ points = []
+ for point in loop:
+ transformed_point = curve_ucs["matrix"] @ mesh.vertices[point].co
+ points.append(self.create_cartesian_point(transformed_point.x, transformed_point.y))
+ points.append(points[0])
+ return self.file.createIfcPolyline(points)
+
+ def create_cartesian_point(self, x, y, z=None):
+ x = self.convert_si_to_unit(x)
+ y = self.convert_si_to_unit(y)
+ if z is None:
+ return self.file.createIfcCartesianPoint((x, y))
+ z = self.convert_si_to_unit(z)
+ return self.file.createIfcCartesianPoint((x, y, z))
+
+ def get_extrusion_direction(self, mesh, extrusion_indices, curve_ucs):
+ return curve_ucs["matrix"] @ (
+ curve_ucs["center"] + (mesh.vertices[extrusion_indices[1]].co - mesh.vertices[extrusion_indices[0]].co)
+ )
+
+ def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge):
+ if extrusion_edge.vertices[0] in profile_points:
+ return (extrusion_edge.vertices[0], extrusion_edge.vertices[1])
+ return (extrusion_edge.vertices[1], extrusion_edge.vertices[0])
+
+ def create_ifc_axis_2_placement_2d(self, point, forward):
+ return self.file.createIfcAxis2Placement2D(
+ self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y))
+ )
+
+ def create_ifc_axis_2_placement_3d(self, point, up, forward):
+ return self.file.createIfcAxis2Placement3D(
+ self.create_cartesian_point(point.x, point.y, point.z),
+ self.file.createIfcDirection((up.x, up.y, up.z)),
+ self.file.createIfcDirection((forward.x, forward.y, forward.z)),
+ )
diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py
index cb76cceee8..60d6f0a86a 100644
--- a/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py
+++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py
@@ -19,11 +19,11 @@ from blenderbim.bim.module.void.data import Data as VoidData
from mathutils import Vector
-def get_box_context_id():
+def get_context_id(context_type, context_identifier, target_view):
for context in ContextData.contexts.values():
- if context["ContextType"] == "Model":
+ if context["ContextType"] == context_type:
for i, subcontext in context["HasSubContexts"].items():
- if subcontext["ContextIdentifier"] == "Box" and subcontext["TargetView"] == "MODEL_VIEW":
+ if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
return i
@@ -120,7 +120,7 @@ class AddRepresentation(bpy.types.Operator):
print("Failed to write shape representation")
return {"FINISHED"}
- box_context_id = get_box_context_id()
+ box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
if (
box_context_id
and context_of_items.ContextType == "Model"
@@ -306,6 +306,7 @@ 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:
@@ -315,81 +316,98 @@ class UpdateMeshRepresentation(bpy.types.Operator):
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 product.is_a("IfcGridAxis"):
- create_axis_curve.Usecase(self.file, {"AxisCurve": obj, "grid_axis": product}).execute()
- continue
-
- 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,
- }
-
- new_representation = add_representation.Usecase(self.file, representation_data).execute()
-
- if not new_representation:
- print("Failed to write shape representation")
- return {"FINISHED"}
-
- box_context_id = get_box_context_id()
- 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)
+ 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"
diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py
index da3804fcc6..7f3762c1d4 100644
--- a/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py
+++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py
@@ -80,6 +80,19 @@ class BIM_PT_mesh(Panel):
row = layout.row()
row.operator("bim.update_mesh_representation")
+
+ row = layout.row()
+ op = row.operator("bim.update_mesh_representation", text="Update Mesh As Rectangle Extrusion")
+ op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
+
+ row = layout.row()
+ op = row.operator("bim.update_mesh_representation", text="Update Mesh As Circle Extrusion")
+ op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcCircleProfileDef"
+
+ row = layout.row()
+ op = row.operator("bim.update_mesh_representation", text="Update Mesh As Arbitrary Extrusion")
+ op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
+
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(props.ifc_parameters):