diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
index 7b74547cab..677b59b9e5 100644
--- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py
@@ -66,87 +66,30 @@ class AddRepresentation(bpy.types.Operator, Operator):
profile_set_usage: bpy.props.IntProperty()
def _execute(self, context):
+ ifc_context = self.context_id or int(context.scene.BIMProperties.contexts or "0") or None
+ if ifc_context:
+ ifc_context = tool.Ifc.get().by_id(ifc_context)
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
- self.file = IfcStore.get_file()
-
- core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
-
- if not obj.data:
- return {"FINISHED"}
-
- product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
-
- context_id = self.context_id or int(context.scene.BIMProperties.contexts)
- context_of_items = self.file.by_id(context_id)
-
- gprop = context.scene.BIMGeoreferenceProperties
- coordinate_offset = None
- if gprop.has_blender_offset and obj.BIMObjectProperties.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,
- "profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None,
- }
-
- result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
-
- if not result:
- print("Failed to write shape representation")
- return {"FINISHED"}
-
- [
- blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material)
- for s in obj.material_slots
- if s.material and not s.material.BIMMaterialProperties.ifc_style_id
- ]
-
- if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
- ifcopenshell.api.run(
- "style.assign_representation_styles",
- 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,
- },
- )
- ifcopenshell.api.run(
- "geometry.assign_representation", self.file, **{"product": product, "representation": result}
+ core.add_representation(
+ tool.Ifc,
+ tool.Geometry,
+ tool.Style,
+ tool.Surveyor,
+ obj=obj,
+ context=ifc_context,
+ ifc_representation_class=self.ifc_representation_class,
+ profile_set_usage=tool.Ifc.get().by_id(self.profile_set_usage) if self.profile_set_usage else None,
)
-
- mesh = obj.data.copy()
- mesh.name = "{}/{}".format(context_id, result.id())
- mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
- obj.data = mesh
- Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
-
- if product.is_a("IfcTypeProduct"):
- if self.file.schema == "IFC2X3":
- types = product.ObjectTypeOf
+ Data.load(tool.Ifc.get(), obj.BIMObjectProperties.ifc_definition_id)
+ element = tool.Ifc.get_entity(obj)
+ if element.is_a("IfcTypeProduct"):
+ if tool.Ifc.get_schema() == "IFC2X3":
+ types = element.ObjectTypeOf
else:
- types = product.Types
+ types = element.Types
if types:
for element in types[0].RelatedObjects:
- Data.load(self.file, element.id())
- return {"FINISHED"}
+ Data.load(tool.Ifc.get(), element.id())
class SwitchRepresentation(bpy.types.Operator):
@@ -430,7 +373,16 @@ class CopyRepresentation(bpy.types.Operator, Operator):
continue
if obj.data:
bm.to_mesh(obj.data)
- bpy.ops.bim.add_representation(obj=obj.name)
+ core.add_representation(
+ tool.Ifc,
+ tool.Geometry,
+ tool.Style,
+ tool.Surveyor,
+ obj=obj,
+ context=tool.Ifc.get().by_id(int(context.scene.BIMProperties.contexts)),
+ ifc_representation_class=None,
+ profile_set_usage=None,
+ )
class OverrideDelete(bpy.types.Operator):
diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py
index cfa7ed47ce..d7ecf7dc5e 100644
--- a/src/blenderbim/blenderbim/bim/module/model/profile.py
+++ b/src/blenderbim/blenderbim/bim/module/model/profile.py
@@ -25,6 +25,8 @@ import ifcopenshell.util.unit
import ifcopenshell.util.element
import mathutils.geometry
import blenderbim.bim.handler
+import blenderbim.tool as tool
+import blenderbim.core.geometry
from blenderbim.bim.ifc import IfcStore
from math import pi, degrees, inf
from mathutils import Vector, Matrix
@@ -169,11 +171,15 @@ class DumbProfileGenerator:
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
profile_set_usage = ifcopenshell.util.element.get_material(element)
- bpy.ops.bim.add_representation(
- obj=obj.name,
- context_id=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id(),
+ blenderbim.core.geometry.add_representation(
+ tool.Ifc,
+ tool.Geometry,
+ tool.Style,
+ tool.Surveyor,
+ obj=obj,
+ context=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW"),
ifc_representation_class="IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
- profile_set_usage=profile_set_usage.id(),
+ profile_set_usage=profile_set_usage,
)
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
bpy.ops.bim.switch_representation(
diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py
index 1c9aaccbc6..f9d2c428a7 100644
--- a/src/blenderbim/blenderbim/bim/module/project/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/project/operator.py
@@ -32,6 +32,7 @@ import blenderbim.core.owner
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
from blenderbim.bim import export_ifc
+from ifcopenshell.api.context.data import Data as ContextData
class CreateProject(bpy.types.Operator):
@@ -76,7 +77,7 @@ class CreateProject(bpy.types.Operator):
model = blenderbim.core.context.add_context(
tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0
)
- blenderbim.core.context.add_context(
+ body_context = blenderbim.core.context.add_context(
tool.Ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
blenderbim.core.context.add_context(
@@ -89,9 +90,8 @@ class CreateProject(bpy.types.Operator):
tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan
)
- context.scene.BIMProperties.contexts = str(
- ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id()
- )
+ ContextData.load(tool.Ifc.get())
+ context.scene.BIMProperties.contexts = str(body_context.id())
bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite")
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding")
diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py
index bcd4b380d0..95ad3c6bcf 100644
--- a/src/blenderbim/blenderbim/bim/module/root/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/root/operator.py
@@ -24,6 +24,7 @@ import ifcopenshell.util.element
import blenderbim.bim.handler
import blenderbim.core.spatial
import blenderbim.core.style
+import blenderbim.core.geometry
import blenderbim.core.material
import blenderbim.tool as tool
from ifcopenshell.api.void.data import Data as VoidData
@@ -144,8 +145,18 @@ class AssignClass(bpy.types.Operator):
IfcStore.link_element(product, obj)
if self.should_add_representation:
- bpy.ops.bim.add_representation(
- obj=obj.name, context_id=self.context_id, ifc_representation_class=self.ifc_representation_class
+ ifc_context = self.context_id or int(context.scene.BIMProperties.contexts or "0") or None
+ if ifc_context:
+ ifc_context = tool.Ifc.get().by_id(ifc_context)
+ blenderbim.core.geometry.add_representation(
+ tool.Ifc,
+ tool.Geometry,
+ tool.Style,
+ tool.Surveyor,
+ obj=obj,
+ context=ifc_context,
+ ifc_representation_class=self.ifc_representation_class,
+ profile_set_usage=None,
)
if product.is_a("IfcElementType"):
@@ -351,7 +362,16 @@ class CopyClass(bpy.types.Operator):
if relating_type and relating_type.RepresentationMaps:
bpy.ops.bim.assign_type(relating_type=relating_type.id(), related_object=obj.name)
else:
- bpy.ops.bim.add_representation(obj=obj.name)
+ blenderbim.core.geometry.add_representation(
+ tool.Ifc,
+ tool.Geometry,
+ tool.Style,
+ tool.Surveyor,
+ obj=obj,
+ context=tool.Ifc.get().by_id(int(context.scene.BIMProperties.contexts)),
+ ifc_representation_class=None,
+ profile_set_usage=None,
+ )
if result.is_a("IfcSpatialElement") or result.is_a("IfcSpatialStructureElement"):
tool.Collector.assign(obj)
elif result.is_a("IfcOpeningElement"):
diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py
index 3df8ff5273..54ea40ecea 100644
--- a/src/blenderbim/blenderbim/core/geometry.py
+++ b/src/blenderbim/blenderbim/core/geometry.py
@@ -16,8 +16,58 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see .
+import blenderbim.core.style
+
def edit_object_placement(ifc, surveyor, obj=None):
element = ifc.get_entity(obj)
if element:
ifc.run("geometry.edit_object_placement", product=element, matrix=surveyor.get_absolute_matrix(obj))
+
+
+def add_representation(
+ ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
+):
+ element = ifc.get_entity(obj)
+ if not element:
+ return
+
+ edit_object_placement(ifc, surveyor, obj=obj)
+ data = geometry.get_object_data(obj)
+
+ if not data:
+ return
+
+ representation = ifc.run(
+ "geometry.add_representation",
+ context=context,
+ blender_object=obj,
+ geometry=data,
+ coordinate_offset=geometry.get_cartesian_point_coordinate_offset(obj),
+ total_items=geometry.get_total_representation_items(obj),
+ should_force_faceted_brep=geometry.should_force_faceted_brep(),
+ should_force_triangulation=geometry.should_force_triangulation(),
+ ifc_representation_class=ifc_representation_class,
+ profile_set_usage=profile_set_usage,
+ )
+
+ if geometry.does_object_have_mesh_with_faces(obj):
+ styles = [
+ blenderbim.core.style.add_style(ifc, style, obj=material)
+ for material in geometry.get_object_materials_without_styles(obj)
+ ]
+ ifc.run(
+ "style.assign_representation_styles",
+ shape_representation=representation,
+ styles=styles,
+ should_use_presentation_style_assignment=geometry.should_use_presentation_style_assignment(),
+ )
+
+ ifc.run("geometry.assign_representation", product=element, representation=representation)
+
+ data = geometry.duplicate_object_data(obj)
+ name = geometry.get_representation_name(context, representation)
+ geometry.rename_object_data(data, name)
+ geometry.link(representation, data)
+
+ return representation
diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py
index 583b93b696..7c9670d601 100644
--- a/src/blenderbim/blenderbim/core/tool.py
+++ b/src/blenderbim/blenderbim/core/tool.py
@@ -63,6 +63,22 @@ class Context:
def set_context(cls, context): pass
+@interface
+class Geometry:
+ def does_object_have_mesh_with_faces(cls, obj): pass
+ def duplicate_object_data(cls, obj): pass
+ def get_object_data(cls, obj): pass
+ def get_object_materials_without_styles(cls, obj): pass
+ def get_representation_name(cls, context, representation): pass
+ def get_cartesian_point_coordinate_offset(cls, obj): pass
+ def get_total_representation_items(cls, obj): pass
+ def link(cls, element, obj): pass
+ def rename_object_data(cls, data, name): pass
+ def should_force_faceted_brep(cls): pass
+ def should_force_triangulation(cls): pass
+ def should_use_presentation_style_assignment(cls): pass
+
+
@interface
class Ifc:
def get(cls): pass
diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py
index a6a4f85086..094198a394 100644
--- a/src/blenderbim/blenderbim/tool/__init__.py
+++ b/src/blenderbim/blenderbim/tool/__init__.py
@@ -21,6 +21,7 @@ from blenderbim.tool.blender import Blender
from blenderbim.tool.collector import Collector
from blenderbim.tool.container import Container
from blenderbim.tool.context import Context
+from blenderbim.tool.geometry import Geometry
from blenderbim.tool.ifc import Ifc
from blenderbim.tool.material import Material
from blenderbim.tool.misc import Misc
diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py
new file mode 100644
index 0000000000..6718de77ea
--- /dev/null
+++ b/src/blenderbim/blenderbim/tool/geometry.py
@@ -0,0 +1,84 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import blenderbim.core.tool
+import blenderbim.tool as tool
+from mathutils import Vector
+from blenderbim.bim.ifc import IfcStore
+
+
+class Geometry(blenderbim.core.tool.Geometry):
+ @classmethod
+ def does_object_have_mesh_with_faces(cls, obj):
+ return bool(isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons))
+
+ @classmethod
+ def duplicate_object_data(cls, obj):
+ obj.data = obj.data.copy()
+ return obj.data
+
+ @classmethod
+ def get_object_data(cls, obj):
+ return obj.data
+
+ @classmethod
+ def get_object_materials_without_styles(cls, obj):
+ return [
+ s.material for s in obj.material_slots if s.material and not s.material.BIMMaterialProperties.ifc_style_id
+ ]
+
+ @classmethod
+ def get_representation_name(cls, context, representation):
+ return f"{context.id()}/{representation.id()}"
+
+ @classmethod
+ def get_cartesian_point_coordinate_offset(cls, obj):
+ props = bpy.context.scene.BIMGeoreferenceProperties
+ if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
+ return Vector(
+ (
+ float(props.blender_eastings),
+ float(props.blender_northings),
+ float(props.blender_orthogonal_height),
+ )
+ )
+
+ @classmethod
+ def get_total_representation_items(cls, obj):
+ return max(1, len(obj.material_slots))
+
+ @classmethod
+ def link(cls, element, obj):
+ obj.BIMMeshProperties.ifc_definition_id = element.id()
+
+ @classmethod
+ def rename_object_data(cls, data, name):
+ data.name = name
+
+ @classmethod
+ def should_force_faceted_brep(cls):
+ return bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep
+
+ @classmethod
+ def should_force_triangulation(cls):
+ return bpy.context.scene.BIMGeometryProperties.should_force_triangulation
+
+ @classmethod
+ def should_use_presentation_style_assignment(cls):
+ return bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment
diff --git a/src/blenderbim/blenderbim/tool/misc.py b/src/blenderbim/blenderbim/tool/misc.py
index fe37f1080c..25f844e080 100644
--- a/src/blenderbim/blenderbim/tool/misc.py
+++ b/src/blenderbim/blenderbim/tool/misc.py
@@ -83,7 +83,9 @@ class Misc(blenderbim.core.tool.Misc):
min_z = min([c[2] for c in absolute_bound_box])
current_absolute_height = max_z - min_z
scale_factor = height / current_absolute_height
- obj.matrix_world @= Matrix.Scale(scale_factor, 4, obj.matrix_world.to_quaternion() @ Vector((0, 0, 1)))
+ obj.matrix_world @= Matrix.Scale(
+ scale_factor, 4, obj.matrix_world.inverted().to_quaternion() @ Vector((0, 0, 1))
+ )
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
@classmethod
diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature
index 282ae30212..0b57df3fae 100644
--- a/src/blenderbim/test/bim/feature/geometry.feature
+++ b/src/blenderbim/test/bim/feature/geometry.feature
@@ -11,6 +11,18 @@ Scenario: Edit object placement
When I press "bim.edit_object_placement"
Then nothing happens
+Scenario: Add representation
+ Given an empty IFC project
+ And I add a cube
+ And the object "Cube" is selected
+ And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
+ And I press "bim.assign_class"
+ And the object "IfcWall/Cube" is selected
+ When the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()"
+ And I set "scene.BIMProperties.contexts" to "{context}"
+ And I press "bim.add_representation"
+ Then nothing happens
+
Scenario: Copy representation
Given an empty IFC project
And I add a cube
diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py
index 4e002a33af..bc8a918bf1 100644
--- a/src/blenderbim/test/bim/test_feature.py
+++ b/src/blenderbim/test/bim/test_feature.py
@@ -130,6 +130,7 @@ def additionally_the_object_name_is_selected(name):
@given(parsers.parse('I set "{prop}" to "{value}"'))
@when(parsers.parse('I set "{prop}" to "{value}"'))
def i_set_prop_to_value(prop, value):
+ value = replace_variables(value)
try:
eval(f"bpy.context.{prop}")
except:
diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py
index 3149e3fddc..e392ad89e5 100644
--- a/src/blenderbim/test/core/bootstrap.py
+++ b/src/blenderbim/test/core/bootstrap.py
@@ -49,6 +49,13 @@ def context():
prophet.verify()
+@pytest.fixture
+def geometry():
+ prophet = Prophecy(blenderbim.core.tool.Geometry)
+ yield prophet
+ prophet.verify()
+
+
@pytest.fixture
def material():
prophet = Prophecy(blenderbim.core.tool.Material)
diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py
index 2de9749a52..09a21b8b94 100644
--- a/src/blenderbim/test/core/test_geometry.py
+++ b/src/blenderbim/test/core/test_geometry.py
@@ -17,12 +17,162 @@
# along with BlenderBIM Add-on. If not, see .
import blenderbim.core.geometry as subject
-from test.core.bootstrap import ifc, surveyor
+import test.core.test_style
+from test.core.bootstrap import ifc, surveyor, geometry, style
class TestEditObjectPlacement:
- def test_run(self, ifc, surveyor):
+ def predict(self, ifc, surveyor):
ifc.get_entity("obj").should_be_called().will_return("element")
surveyor.get_absolute_matrix("obj").should_be_called().will_return("matrix")
ifc.run("geometry.edit_object_placement", product="element", matrix="matrix").should_be_called()
+
+ def test_run(self, ifc, surveyor):
+ self.predict(ifc, surveyor)
subject.edit_object_placement(ifc, surveyor, obj="obj")
+
+
+class TestAddRepresentation:
+ def test_run(self, ifc, geometry, style, surveyor):
+ TestEditObjectPlacement.predict(self, ifc, surveyor)
+
+ # Add representation
+ geometry.get_object_data("obj").should_be_called().will_return("data")
+ geometry.get_cartesian_point_coordinate_offset("obj").should_be_called().will_return("coordinate_offset")
+ geometry.get_total_representation_items("obj").should_be_called().will_return(1)
+ geometry.should_force_faceted_brep().should_be_called().will_return(False)
+ geometry.should_force_triangulation().should_be_called().will_return(True)
+ ifc.run(
+ "geometry.add_representation",
+ context="context",
+ blender_object="obj",
+ geometry="data",
+ coordinate_offset="coordinate_offset",
+ total_items=1,
+ should_force_faceted_brep=False,
+ should_force_triangulation=True,
+ ifc_representation_class="ifc_representation_class",
+ profile_set_usage="profile_set_usage",
+ ).should_be_called().will_return("representation")
+
+ # Styles are relevant for meshes with faces only
+ geometry.does_object_have_mesh_with_faces("obj").should_be_called().will_return(True)
+
+ # Add styles
+ geometry.get_object_materials_without_styles("obj").should_be_called().will_return(["material"])
+ test.core.test_style.TestAddStyle.predict(self, ifc, style, obj="material")
+
+ # Link style to representation items
+ geometry.should_use_presentation_style_assignment().should_be_called().will_return(False)
+ ifc.run(
+ "style.assign_representation_styles",
+ shape_representation="representation",
+ styles=["style"],
+ should_use_presentation_style_assignment=False,
+ ).should_be_called()
+
+ # Assign representation to product
+ ifc.run("geometry.assign_representation", product="element", representation="representation").should_be_called()
+
+ # Update mesh
+ geometry.duplicate_object_data("obj").should_be_called().will_return("data")
+ geometry.get_representation_name("context", "representation").should_be_called().will_return("name")
+ geometry.rename_object_data("data", "name").should_be_called()
+ geometry.link("representation", "data").should_be_called()
+
+ assert (
+ subject.add_representation(
+ ifc,
+ geometry,
+ style,
+ surveyor,
+ obj="obj",
+ context="context",
+ ifc_representation_class="ifc_representation_class",
+ profile_set_usage="profile_set_usage",
+ )
+ == "representation"
+ )
+
+ def test_not_handling_styles_if_representation_has_no_faces(self, ifc, geometry, style, surveyor):
+ TestEditObjectPlacement.predict(self, ifc, surveyor)
+
+ # Add representation
+ geometry.get_object_data("obj").should_be_called().will_return("data")
+ geometry.get_cartesian_point_coordinate_offset("obj").should_be_called().will_return("coordinate_offset")
+ geometry.get_total_representation_items("obj").should_be_called().will_return(1)
+ geometry.should_force_faceted_brep().should_be_called().will_return(False)
+ geometry.should_force_triangulation().should_be_called().will_return(True)
+ ifc.run(
+ "geometry.add_representation",
+ context="context",
+ blender_object="obj",
+ geometry="data",
+ coordinate_offset="coordinate_offset",
+ total_items=1,
+ should_force_faceted_brep=False,
+ should_force_triangulation=True,
+ ifc_representation_class="ifc_representation_class",
+ profile_set_usage="profile_set_usage",
+ ).should_be_called().will_return("representation")
+
+ # Styles are relevant for meshes with faces only
+ geometry.does_object_have_mesh_with_faces("obj").should_be_called().will_return(False)
+
+ # Assign representation to product
+ ifc.run("geometry.assign_representation", product="element", representation="representation").should_be_called()
+
+ # Update mesh
+ geometry.duplicate_object_data("obj").should_be_called().will_return("data")
+ geometry.get_representation_name("context", "representation").should_be_called().will_return("name")
+ geometry.rename_object_data("data", "name").should_be_called()
+ geometry.link("representation", "data").should_be_called()
+
+ assert (
+ subject.add_representation(
+ ifc,
+ geometry,
+ style,
+ surveyor,
+ obj="obj",
+ context="context",
+ ifc_representation_class="ifc_representation_class",
+ profile_set_usage="profile_set_usage",
+ )
+ == "representation"
+ )
+
+ def test_only_updating_the_placement_if_there_is_no_object_data(self, ifc, geometry, style, surveyor):
+ TestEditObjectPlacement.predict(self, ifc, surveyor)
+
+ # Add representation
+ geometry.get_object_data("obj").should_be_called().will_return(None)
+ assert (
+ subject.add_representation(
+ ifc,
+ geometry,
+ style,
+ surveyor,
+ obj="obj",
+ context="context",
+ ifc_representation_class="ifc_representation_class",
+ profile_set_usage="profile_set_usage",
+ )
+ is None
+ )
+
+ def test_doing_nothing_if_not_an_ifc_element(self, ifc, geometry, style, surveyor):
+ ifc.get_entity("obj").should_be_called().will_return(None)
+ assert (
+ subject.add_representation(
+ ifc,
+ geometry,
+ style,
+ surveyor,
+ obj="obj",
+ context="context",
+ ifc_representation_class="ifc_representation_class",
+ profile_set_usage="profile_set_usage",
+ )
+ is None
+ )
diff --git a/src/blenderbim/test/core/test_style.py b/src/blenderbim/test/core/test_style.py
index 78a46b6019..298434335c 100644
--- a/src/blenderbim/test/core/test_style.py
+++ b/src/blenderbim/test/core/test_style.py
@@ -21,15 +21,18 @@ from test.core.bootstrap import ifc, style
class TestAddStyle:
- def test_it_adds_a_style_with_rendering_attributes(self, ifc, style):
- style.get_name("obj").should_be_called().will_return("name")
+ def predict(self, ifc, style, obj="obj"):
+ style.get_name(obj).should_be_called().will_return("name")
ifc.run("style.add_style", name="name").should_be_called().will_return("style")
- style.link("style", "obj").should_be_called()
- style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes")
+ style.link("style", obj).should_be_called()
+ style.get_surface_rendering_attributes(obj).should_be_called().will_return("attributes")
ifc.run(
"style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes"
).should_be_called()
- ifc.get_entity("obj").should_be_called().will_return(None)
+ ifc.get_entity(obj).should_be_called().will_return(None)
+
+ def test_it_adds_a_style_with_rendering_attributes(self, ifc, style):
+ self.predict(ifc, style)
assert subject.add_style(ifc, style, obj="obj") == "style"
def test_adding_a_style_linked_to_a_material(self, ifc, style):
diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py
new file mode 100644
index 0000000000..d874128452
--- /dev/null
+++ b/src/blenderbim/test/tool/test_geometry.py
@@ -0,0 +1,161 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2021 Dion Moult
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+import math
+import numpy
+import ifcopenshell
+import test.bim.bootstrap
+import blenderbim.core.tool
+import blenderbim.tool as tool
+from mathutils import Vector
+from blenderbim.tool.geometry import Geometry as subject
+from blenderbim.bim.ifc import IfcStore
+
+
+class TestImplementsTool(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ assert isinstance(subject(), blenderbim.core.tool.Geometry)
+
+
+class TestDoesObjectHaveMeshWithFaces(test.bim.bootstrap.NewFile):
+ def test_empties_return_false(self):
+ obj = bpy.data.objects.new("Object", None)
+ assert subject.does_object_have_mesh_with_faces(obj) is False
+
+ def test_non_meshes_return_false(self):
+ obj = bpy.data.objects.new("Object", bpy.data.cameras.new("Curve"))
+ assert subject.does_object_have_mesh_with_faces(obj) is False
+
+ def test_meshes_without_faces_return_false(self):
+ obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
+ assert subject.does_object_have_mesh_with_faces(obj) is False
+
+ def test_meshes_with_faces_return_true(self):
+ bpy.ops.mesh.primitive_cube_add()
+ obj = bpy.data.objects.get("Cube")
+ assert subject.does_object_have_mesh_with_faces(obj) is True
+
+
+class TestDuplicateObjectData(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ data = bpy.data.meshes.new("Mesh")
+ obj = bpy.data.objects.new("Object", data)
+ assert subject.duplicate_object_data(obj) == obj.data
+ assert obj.data != data
+ assert isinstance(obj.data, bpy.types.Mesh)
+
+
+class TestGetObjectData(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ data = bpy.data.meshes.new("Mesh")
+ obj = bpy.data.objects.new("Object", data)
+ assert subject.get_object_data(obj) == obj.data
+
+
+class TestGetObjectMaterialsWithoutStyles(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ material1 = bpy.data.materials.new("Material")
+ material2 = bpy.data.materials.new("Material")
+ material3 = bpy.data.materials.new("Material")
+ material3.BIMMaterialProperties.ifc_style_id = 1
+ obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
+ obj.data.materials.append(material1)
+ obj.data.materials.append(material2)
+ obj.data.materials.append(material3)
+ assert subject.get_object_materials_without_styles(obj) == [material1, material2]
+
+
+class TestGetRepresentationName(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ ifc = ifcopenshell.file()
+ context = ifc.createIfcGeometricRepresentationContext()
+ representation = ifc.createIfcShapeRepresentation()
+ assert subject.get_representation_name(context, representation) == f"{context.id()}/{representation.id()}"
+
+
+class TestGetCartesianPointCoordinateOffset(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ obj = bpy.data.objects.new("Object", None)
+ obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
+ props = bpy.context.scene.BIMGeoreferenceProperties
+ props.has_blender_offset = True
+ props.blender_eastings = "1"
+ props.blender_northings = "2"
+ props.blender_orthogonal_height = "3"
+ assert subject.get_cartesian_point_coordinate_offset(obj) == Vector((1.0, 2.0, 3.0))
+
+ def test_get_null_if_not_a_cartesian_point_offset_type(self):
+ obj = bpy.data.objects.new("Object", None)
+ props = bpy.context.scene.BIMGeoreferenceProperties
+ props.has_blender_offset = True
+ props.blender_eastings = "1"
+ props.blender_northings = "2"
+ props.blender_orthogonal_height = "3"
+ assert subject.get_cartesian_point_coordinate_offset(obj) is None
+
+ def test_get_null_if_no_blender_offset(self):
+ obj = bpy.data.objects.new("Object", None)
+ obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
+ props = bpy.context.scene.BIMGeoreferenceProperties
+ props.has_blender_offset = False
+ assert subject.get_cartesian_point_coordinate_offset(obj) is None
+
+
+class TestGetTotalRepresentationItems(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ material1 = bpy.data.materials.new("Material")
+ material2 = bpy.data.materials.new("Material")
+ obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
+ obj.data.materials.append(material1)
+ obj.data.materials.append(material2)
+ assert subject.get_total_representation_items(obj) == 2
+
+
+class TestLink(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ ifc = ifcopenshell.file()
+ element = ifc.createIfcShapeRepresentation()
+ obj = bpy.data.meshes.new("Mesh")
+ subject.link(element, obj)
+ assert obj.BIMMeshProperties.ifc_definition_id == element.id()
+
+
+class TestRenameObjectData(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ obj = bpy.data.meshes.new("Mesh")
+ subject.rename_object_data(obj, "name")
+ assert obj.name == "name"
+
+
+class TestShouldForceFacetedBrep(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ result = bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep
+ assert subject.should_force_faceted_brep() is result
+
+
+class TestShouldForceTriangulation(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ result = bpy.context.scene.BIMGeometryProperties.should_force_triangulation
+ assert subject.should_force_triangulation() is result
+
+
+class TestShouldUsePresentationStyleAssignment(test.bim.bootstrap.NewFile):
+ def test_run(self):
+ result = bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment
+ assert subject.should_use_presentation_style_assignment() is result
diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py
index 2a1fdf1d03..fdbba11544 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py
@@ -10,7 +10,6 @@ class Usecase:
self.settings[key] = value
def execute(self):
- self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.file.schema)
result = ifcopenshell.util.element.copy(self.file, self.settings["product"])
self.copy_direct_attributes(result)
self.copy_indirect_attributes(self.settings["product"], result)