mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 11:43:53 +00:00
Potentially risky refactor of add_representation in preparation of stabilisation and optimization of geometric operators.
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user