mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Potentially risky refactor of add_representation in preparation of stabilisation and optimization of geometric operators.
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -16,8 +16,58 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -17,12 +17,162 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user