mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-18 03:19:53 +00:00
52762f828c
Previously some of them were saved only for current .blend file, moving them to preferences will make it more consistent with usual Blender UX. If needed we'll be able to add some way to fine-grain them later. There's also a temporary patch that's going to migrate old .blend-props to new preferences-props to make process less disruptive.
728 lines
29 KiB
Python
728 lines
29 KiB
Python
# Bonsai - OpenBIM Blender Add-on
|
|
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
|
#
|
|
# This file is part of Bonsai.
|
|
#
|
|
# Bonsai 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.
|
|
#
|
|
# Bonsai 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import bpy
|
|
import ifcopenshell
|
|
import ifcopenshell.api.geometry
|
|
import ifcopenshell.api.material
|
|
import ifcopenshell.api.root
|
|
import ifcopenshell.api.style
|
|
import ifcopenshell.api.type
|
|
import ifcopenshell.util.element
|
|
import ifcopenshell.util.representation
|
|
import ifcopenshell.util.shape_builder
|
|
import bonsai.core.tool
|
|
import bonsai.tool as tool
|
|
import numpy as np
|
|
import json
|
|
from test.bim.bootstrap import NewFile
|
|
from bonsai.tool.model import Model as subject
|
|
from ifcopenshell.util.shape_builder import V, ShapeBuilder
|
|
|
|
|
|
class TestImplementsTool(NewFile):
|
|
def test_run(self):
|
|
assert isinstance(subject(), bonsai.core.tool.Model)
|
|
|
|
|
|
class TestGenerateOccurrenceName(NewFile):
|
|
def test_generating_based_on_class(self):
|
|
ifc = ifcopenshell.file()
|
|
element_type = ifc.createIfcWallType(Name="Foobar")
|
|
prefs = tool.Blender.get_addon_preferences()
|
|
with tool.Blender.preserve_prop_value(prefs, "occurrence_name_style"):
|
|
prefs.occurrence_name_style = "CLASS"
|
|
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Wall"
|
|
|
|
def test_generating_based_on_type_name(self):
|
|
ifc = ifcopenshell.file()
|
|
element_type = ifc.createIfcWallType()
|
|
prefs = tool.Blender.get_addon_preferences()
|
|
with tool.Blender.preserve_prop_value(prefs, "occurrence_name_style"):
|
|
prefs.occurrence_name_style = "TYPE"
|
|
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Unnamed"
|
|
element_type.Name = "Foobar"
|
|
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
|
|
|
|
def test_generating_based_on_a_custom_function(self):
|
|
ifc = ifcopenshell.file()
|
|
element_type = ifc.createIfcWallType()
|
|
prefs = tool.Blender.get_addon_preferences()
|
|
with tool.Blender.preserve_prop_value(prefs, "occurrence_name_style"):
|
|
prefs.occurrence_name_style = "CUSTOM"
|
|
prefs.occurrence_name_function = '"Foobar"'
|
|
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
|
|
|
|
|
|
class TestGetBooleans(NewFile):
|
|
def test_run(self):
|
|
ifc = ifcopenshell.file()
|
|
tool.Ifc.set(ifc)
|
|
|
|
context = ifc.createIfcGeometricRepresentationContext()
|
|
element = ifc.createIfcWall()
|
|
|
|
items = [ifc.createIfcExtrudedAreaSolid()]
|
|
representation = ifc.createIfcShapeRepresentation(Items=items, ContextOfItems=context)
|
|
ifcopenshell.api.geometry.assign_representation(ifc, product=element, representation=representation)
|
|
|
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc)
|
|
cut1 = builder.half_space_solid(builder.plane())
|
|
cut2 = builder.half_space_solid(builder.plane())
|
|
bools = ifcopenshell.api.geometry.add_boolean(ifc, first_item=items[0], second_items=[cut1, cut2])
|
|
|
|
assert set(subject.get_booleans(element, representation)) == set(bools)
|
|
|
|
|
|
class TestGetManualBooleans(NewFile):
|
|
def test_run(self):
|
|
ifc = ifcopenshell.file()
|
|
tool.Ifc.set(ifc)
|
|
|
|
context = ifc.createIfcGeometricRepresentationContext()
|
|
element = ifc.createIfcWall()
|
|
|
|
items = [ifc.createIfcExtrudedAreaSolid()]
|
|
representation = ifc.createIfcShapeRepresentation(Items=items, ContextOfItems=context)
|
|
ifcopenshell.api.geometry.assign_representation(ifc, product=element, representation=representation)
|
|
|
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc)
|
|
cut1 = builder.half_space_solid(builder.plane())
|
|
cut2 = builder.half_space_solid(builder.plane())
|
|
bools = ifcopenshell.api.geometry.add_boolean(ifc, first_item=items[0], second_items=[cut1, cut2])
|
|
|
|
assert set(subject.get_booleans(element, representation)) == set(bools)
|
|
assert len(subject.get_manual_booleans(element, representation)) == 0
|
|
|
|
bool1 = bools[0]
|
|
|
|
subject.mark_manual_booleans(element, [bool1])
|
|
assert set(subject.get_manual_booleans(element, representation)) == {bool1}
|
|
|
|
|
|
class TestMarkManualBooleans(NewFile):
|
|
def test_run(self):
|
|
ifc = ifcopenshell.file()
|
|
tool.Ifc.set(ifc)
|
|
|
|
element = ifc.createIfcWall()
|
|
boolean = ifc.createIfcBooleanClippingResult()
|
|
subject.mark_manual_booleans(element, [boolean])
|
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
|
assert pset
|
|
value = json.loads(pset["Data"])
|
|
assert set(value) == {boolean.id()}
|
|
|
|
|
|
class TestUnmarkManualBooleans(NewFile):
|
|
def test_run(self):
|
|
ifc = ifcopenshell.file()
|
|
tool.Ifc.set(ifc)
|
|
|
|
element = ifc.createIfcWall()
|
|
boolean = ifc.createIfcBooleanClippingResult()
|
|
boolean2 = ifc.createIfcBooleanClippingResult()
|
|
subject.mark_manual_booleans(element, [boolean, boolean2])
|
|
subject.unmark_manual_booleans(element, [boolean.id()])
|
|
|
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
|
|
assert pset
|
|
value = json.loads(pset["Data"])
|
|
assert set(value) == {boolean2.id()}
|
|
|
|
|
|
class TestStairCalculatedParams(NewFile):
|
|
def compare_data(self, pset_data, expected_calculated_data):
|
|
calculated_data = subject.get_active_stair_calculated_params(pset_data)
|
|
for key, value in expected_calculated_data.items():
|
|
assert tool.Cad.is_x(calculated_data[key], value)
|
|
|
|
def test_run(self):
|
|
bpy.ops.bim.create_project()
|
|
bpy.ops.mesh.add_stair()
|
|
pset_data_base = {
|
|
"number_of_treads": 3,
|
|
"height": 1.0,
|
|
"tread_run": 0.3,
|
|
"custom_first_last_tread_run": (0.0, 0.0),
|
|
"nosing_length": 0.0,
|
|
}
|
|
calculated_data_base = {
|
|
"Number of Risers": 4,
|
|
"Tread Rise": 0.25,
|
|
"Length": 1.2,
|
|
}
|
|
self.compare_data(pset_data_base, calculated_data_base)
|
|
|
|
# custom first and last treads run
|
|
pset_data = pset_data_base.copy()
|
|
calculated_data = calculated_data_base.copy()
|
|
pset_data["custom_first_last_tread_run"] = (0.1, 0.4)
|
|
calculated_data["Length"] += -0.2 + 0.1
|
|
self.compare_data(pset_data, calculated_data)
|
|
|
|
# overlap affects stair length only by first tread
|
|
pset_data = pset_data_base.copy()
|
|
calculated_data = calculated_data_base.copy()
|
|
pset_data["nosing_length"] = 0.1
|
|
calculated_data["Length"] += 0.1
|
|
self.compare_data(pset_data, calculated_data)
|
|
|
|
# tread gap
|
|
pset_data = pset_data_base.copy()
|
|
calculated_data = calculated_data_base.copy()
|
|
pset_data["nosing_length"] = -0.1
|
|
calculated_data["Length"] += 0.1 * pset_data["number_of_treads"]
|
|
self.compare_data(pset_data, calculated_data)
|
|
|
|
|
|
class TestGenerateStair2DProfile(NewFile):
|
|
def compare_data(self, generated_profile, expected_profile):
|
|
verts_gen, edges_gen, faces_gen = generated_profile
|
|
verts, edges, faces = expected_profile
|
|
|
|
assert np.all(edges == np.array(edges_gen))
|
|
assert faces == tuple(tuple(face) for face in faces_gen)
|
|
for vert, vert_gen in zip(verts, verts_gen, strict=True):
|
|
assert np.allclose(vert, V(vert_gen), atol=0.01)
|
|
|
|
def test_create_concrete_stair(self):
|
|
kwargs = {
|
|
"base_slab_depth": 0.25,
|
|
"has_top_nib": False,
|
|
"height": 1.0,
|
|
"number_of_treads": 3,
|
|
"stair_type": "CONCRETE",
|
|
"top_slab_depth": 0.25,
|
|
"tread_depth": 0.25,
|
|
"tread_run": 0.3,
|
|
"width": 1.2,
|
|
}
|
|
verts_data = (
|
|
V(0.0, 0, 0.0),
|
|
V(0.0, 0, 0.25),
|
|
V(0.3, 0, 0.25),
|
|
V(0.3, 0, 0.5),
|
|
V(0.6, 0, 0.5),
|
|
V(0.6, 0, 0.75),
|
|
V(0.9, 0, 0.75),
|
|
V(0.9, 0, 1.0),
|
|
V(1.2, 0, 1.0),
|
|
V(1.2, 0, 0.67457),
|
|
V(0.1, 0, -0.25),
|
|
V(0.0, 0, -0.25),
|
|
)
|
|
edges_data = (
|
|
(0, 1),
|
|
(1, 2),
|
|
(2, 3),
|
|
(3, 4),
|
|
(4, 5),
|
|
(5, 6),
|
|
(6, 7),
|
|
(7, 8),
|
|
(8, 9),
|
|
(11, 0),
|
|
(10, 11),
|
|
(9, 10),
|
|
)
|
|
edges_data = [e[::-1] for e in edges_data]
|
|
faces_data = ()
|
|
expected_profile = (verts_data, edges_data, faces_data)
|
|
generated_profile = subject.generate_stair_2d_profile(**kwargs)
|
|
self.compare_data(generated_profile, expected_profile)
|
|
|
|
def test_create_concrete_stair_nib(self):
|
|
kwargs = {
|
|
"base_slab_depth": 0.25,
|
|
"has_top_nib": True,
|
|
"height": 1.0,
|
|
"number_of_treads": 3,
|
|
"stair_type": "CONCRETE",
|
|
"top_slab_depth": 0.25,
|
|
"tread_depth": 0.25,
|
|
"tread_run": 0.3,
|
|
"width": 1.2,
|
|
}
|
|
verts_data = (
|
|
V(0.0, 0, 0.0),
|
|
V(0.0, 0, 0.25),
|
|
V(0.3, 0, 0.25),
|
|
V(0.3, 0, 0.5),
|
|
V(0.6, 0, 0.5),
|
|
V(0.6, 0, 0.75),
|
|
V(0.9, 0, 0.75),
|
|
V(0.9, 0, 1.0),
|
|
V(1.2, 0, 1.0),
|
|
V(1.2, 0, 0.75),
|
|
V(1.3, 0, 0.75),
|
|
V(0.1, 0, -0.25),
|
|
V(0.0, 0, -0.25),
|
|
)
|
|
edges_data = (
|
|
(0, 1),
|
|
(1, 2),
|
|
(2, 3),
|
|
(3, 4),
|
|
(4, 5),
|
|
(5, 6),
|
|
(6, 7),
|
|
(7, 8),
|
|
(8, 9),
|
|
(9, 10),
|
|
(12, 0),
|
|
(11, 12),
|
|
(10, 11),
|
|
)
|
|
edges_data = [e[::-1] for e in edges_data]
|
|
|
|
faces_data = ()
|
|
expected_profile = (verts_data, edges_data, faces_data)
|
|
generated_profile = subject.generate_stair_2d_profile(**kwargs)
|
|
self.compare_data(generated_profile, expected_profile)
|
|
|
|
def test_create_wood_steel_stair(self):
|
|
kwargs = {
|
|
"height": 1.0,
|
|
"number_of_treads": 3,
|
|
"stair_type": "WOOD/STEEL",
|
|
"tread_depth": 0.25,
|
|
"tread_run": 0.3,
|
|
"width": 1.2,
|
|
}
|
|
verts_data = (
|
|
V(0.0, 0, 0.0),
|
|
V(0.3, 0, 0.0),
|
|
V(0.3, 0, 0.25),
|
|
V(0.0, 0, 0.25),
|
|
V(0.3, 0, 0.25),
|
|
V(0.6, 0, 0.25),
|
|
V(0.6, 0, 0.5),
|
|
V(0.3, 0, 0.5),
|
|
V(0.6, 0, 0.5),
|
|
V(0.9, 0, 0.5),
|
|
V(0.9, 0, 0.75),
|
|
V(0.6, 0, 0.75),
|
|
V(0.9, 0, 0.75),
|
|
V(1.2, 0, 0.75),
|
|
V(1.2, 0, 1.0),
|
|
V(0.9, 0, 1.0),
|
|
)
|
|
edges_data = (
|
|
(0, 1),
|
|
(1, 2),
|
|
(2, 3),
|
|
(3, 0),
|
|
(4, 5),
|
|
(5, 6),
|
|
(6, 7),
|
|
(7, 4),
|
|
(8, 9),
|
|
(9, 10),
|
|
(10, 11),
|
|
(11, 8),
|
|
(12, 13),
|
|
(13, 14),
|
|
(14, 15),
|
|
(15, 12),
|
|
)
|
|
|
|
faces_data = ()
|
|
|
|
expected_profile = (verts_data, edges_data, faces_data)
|
|
generated_profile = subject.generate_stair_2d_profile(**kwargs)
|
|
self.compare_data(generated_profile, expected_profile)
|
|
|
|
def test_create_generic_stair(self):
|
|
kwargs = {"height": 1.0, "number_of_treads": 3, "stair_type": "GENERIC", "tread_run": 0.3, "width": 1.2}
|
|
verts_data = (
|
|
V(0.0, 0, 0.0),
|
|
V(0.0, 0, 0.25),
|
|
V(0.3, 0, 0.25),
|
|
V(0.3, 0, 0.5),
|
|
V(0.6, 0, 0.5),
|
|
V(0.6, 0, 0.75),
|
|
V(0.9, 0, 0.75),
|
|
V(0.9, 0, 1.0),
|
|
V(1.2, 0, 1.0),
|
|
V(1.2, 0, 0.0),
|
|
)
|
|
edges_data = (
|
|
(0, 1),
|
|
(1, 2),
|
|
(2, 3),
|
|
(3, 4),
|
|
(4, 5),
|
|
(5, 6),
|
|
(6, 7),
|
|
(7, 8),
|
|
(8, 9),
|
|
(9, 0),
|
|
)
|
|
edges_data = [e[::-1] for e in edges_data]
|
|
|
|
faces_data = ()
|
|
expected_profile = (verts_data, edges_data, faces_data)
|
|
generated_profile = subject.generate_stair_2d_profile(**kwargs)
|
|
self.compare_data(generated_profile, expected_profile)
|
|
|
|
|
|
class TestUsingArrays(NewFile):
|
|
def setup_array(self, add_second_layer=False, sync_children=False):
|
|
tool.Project.get_project_props().template_file = "0"
|
|
bpy.ops.bim.create_project()
|
|
|
|
bpy.ops.mesh.primitive_cube_add()
|
|
obj = bpy.context.active_object
|
|
assert obj
|
|
rprops = tool.Root.get_root_props()
|
|
rprops.ifc_product = "IfcElement"
|
|
bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="")
|
|
|
|
bpy.ops.bim.add_array()
|
|
bpy.ops.bim.enable_editing_array(item=0)
|
|
props = tool.Model.get_array_props(obj)
|
|
props.count = 4
|
|
props.x = 4
|
|
props.sync_children = sync_children
|
|
bpy.ops.bim.edit_array(item=0)
|
|
|
|
if add_second_layer:
|
|
bpy.ops.bim.add_array()
|
|
bpy.ops.bim.enable_editing_array(item=1)
|
|
props = tool.Model.get_array_props(obj)
|
|
props.count = 3
|
|
props.y = 4
|
|
props.sync_children = sync_children
|
|
bpy.ops.bim.edit_array(item=1)
|
|
|
|
def test_remove_array_last_to_first(self):
|
|
self.setup_array(add_second_layer=True)
|
|
bpy.ops.bim.remove_array(item=1)
|
|
assert len(bpy.context.selected_objects) == 4
|
|
bpy.ops.bim.remove_array(item=0)
|
|
assert len(bpy.context.selected_objects) == 1
|
|
|
|
def test_remove_array_first_to_last(self):
|
|
self.setup_array(add_second_layer=True)
|
|
bpy.ops.bim.remove_array(item=0)
|
|
assert len(bpy.context.selected_objects) == 3
|
|
bpy.ops.bim.remove_array(item=0)
|
|
assert len(bpy.context.selected_objects) == 1
|
|
|
|
def test_apply_array_1_layer(self):
|
|
self.setup_array()
|
|
bpy.ops.bim.apply_array()
|
|
|
|
objs = bpy.context.selected_objects
|
|
assert len(objs) == 4
|
|
# check BBIM_Array psets are removed
|
|
for obj in objs:
|
|
element = tool.Ifc.get_entity(obj)
|
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
|
assert pset is None, (obj, pset)
|
|
|
|
def test_apply_array_multiple_layers(self):
|
|
self.setup_array(add_second_layer=True)
|
|
bpy.ops.bim.apply_array() # apply second layer
|
|
bpy.ops.bim.apply_array() # apply first layer
|
|
|
|
objs = bpy.context.selected_objects
|
|
assert len(objs) == 12
|
|
|
|
# check BBIM_Array psets are removed
|
|
for obj in objs:
|
|
element = tool.Ifc.get_entity(obj)
|
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
|
assert pset is None, (obj, pset)
|
|
|
|
def test_apply_array_with_sync_children(self):
|
|
self.setup_array(sync_children=True)
|
|
bpy.ops.bim.apply_array()
|
|
|
|
objs = bpy.context.selected_objects
|
|
assert len(objs) == 4
|
|
# check BBIM_Array psets are removed
|
|
for obj in objs:
|
|
element = tool.Ifc.get_entity(obj)
|
|
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
|
assert pset is None, (obj, pset)
|
|
|
|
|
|
class TestApplyIfcMaterialChanges(NewFile):
|
|
def get_used_styles(self, obj: bpy.types.Object) -> set[ifcopenshell.entity_instance]:
|
|
ifc_file = tool.Ifc.get()
|
|
return {
|
|
ifc_file.by_id(tool.Blender.get_ifc_definition_id(s.material)) for s in obj.material_slots if s.material
|
|
}
|
|
|
|
def get_mesh(self, obj: bpy.types.Object) -> bpy.types.Mesh:
|
|
mesh = obj.data
|
|
assert isinstance(mesh, bpy.types.Mesh)
|
|
return mesh
|
|
|
|
def setup_test(self, and_elements: bool = True) -> None:
|
|
props = tool.Project.get_project_props()
|
|
props.template_file = "0"
|
|
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
|
|
bpy.ops.bim.create_project()
|
|
ifc_file = tool.Ifc.get()
|
|
|
|
# Setup materials and styles.
|
|
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
|
assert context # Type checker.
|
|
|
|
red_material = ifcopenshell.api.material.add_material(ifc_file, "Red Material")
|
|
bpy.ops.bim.load_styles(style_type="IfcSurfaceStyle")
|
|
bpy.ops.bim.enable_adding_presentation_style()
|
|
sprops = tool.Style.get_style_props()
|
|
sprops.style_name = "Red"
|
|
bpy.ops.bim.add_presentation_style()
|
|
red_style = next(i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Red")
|
|
ifcopenshell.api.style.assign_material_style(ifc_file, red_material, red_style, context)
|
|
|
|
blue_material = ifcopenshell.api.material.add_material(ifc_file, "Blue Material")
|
|
bpy.ops.bim.enable_adding_presentation_style()
|
|
sprops.style_name = "Blue"
|
|
bpy.ops.bim.add_presentation_style()
|
|
blue_style = next(i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Blue")
|
|
ifcopenshell.api.style.assign_material_style(ifc_file, blue_material, blue_style, context)
|
|
|
|
bpy.ops.bim.enable_adding_presentation_style()
|
|
sprops.style_name = "Green"
|
|
bpy.ops.bim.add_presentation_style()
|
|
|
|
if and_elements:
|
|
self.setup_elements()
|
|
|
|
def setup_elements(self) -> None:
|
|
ifc_file = tool.Ifc.get()
|
|
blue_material = next(i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Blue Material")
|
|
blue_style = tool.Material.get_style(blue_material)
|
|
|
|
# Element type.
|
|
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
|
element_type_obj = bpy.data.objects["Cube"]
|
|
bpy.ops.bim.assign_class(ifc_class="IfcActuatorType", predefined_type="ELECTRICACTUATOR", userdefined_type="")
|
|
element_type = tool.Ifc.get_entity(element_type_obj)
|
|
|
|
# Setup occurrences.
|
|
relating_type_id = element_type.id()
|
|
bpy.ops.bim.add_occurrence(relating_type_id=relating_type_id)
|
|
simple = bpy.context.active_object
|
|
simple.name = "Simple"
|
|
|
|
# Occurrence with an opening.
|
|
bpy.ops.bim.add_occurrence(relating_type_id=relating_type_id)
|
|
with_opening = bpy.context.active_object
|
|
with_opening.name = "With Opening"
|
|
props = tool.Root.get_root_props()
|
|
props.representation_obj = with_opening
|
|
bpy.ops.bim.add_element(ifc_product="IfcFeatureElement", ifc_class="IfcOpeningElement")
|
|
|
|
# Occurrence with a material override.
|
|
bpy.ops.bim.add_occurrence(relating_type_id=relating_type_id)
|
|
with_material = bpy.context.active_object
|
|
with_material.name = "With Material"
|
|
tool.Blender.set_objects_selection(bpy.context, active_object=with_material, selected_objects=[with_material])
|
|
|
|
ifcopenshell.api.material.assign_material(
|
|
ifc_file, products=[tool.Ifc.get_entity(with_material)], material=blue_material
|
|
)
|
|
tool.Material.ensure_material_assigned([tool.Ifc.get_entity(with_material)], material=blue_material)
|
|
|
|
assert self.get_used_styles(element_type_obj) == set()
|
|
for element in ifc_file.by_type("IfcActuator"):
|
|
obj = tool.Ifc.get_object(element)
|
|
expected = {blue_style} if obj.name == "With Material" else set()
|
|
assert self.get_used_styles(obj) == expected
|
|
|
|
def test_element_type_and_occurrences(self):
|
|
self.setup_test()
|
|
ifc_file = tool.Ifc.get()
|
|
element_type = next(ifc_file.by_type("IfcActuatorType").__iter__())
|
|
red_material = next(i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material")
|
|
red_style = tool.Material.get_style(red_material)
|
|
blue_style = next(i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Blue")
|
|
|
|
ifcopenshell.api.material.assign_material(ifc_file, material=red_material, products=[element_type])
|
|
tool.Material.ensure_material_assigned([element_type], material=red_material)
|
|
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == {red_style}
|
|
for element in ifc_file.by_type("IfcActuator"):
|
|
obj = tool.Ifc.get_object(element)
|
|
expected = {blue_style} if obj.name == "With Material" else {red_style}
|
|
assert self.get_used_styles(obj) == expected
|
|
|
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element_type])
|
|
tool.Material.ensure_material_unassigned([element_type])
|
|
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == set()
|
|
for element in ifc_file.by_type("IfcActuator"):
|
|
obj = tool.Ifc.get_object(element)
|
|
expected = {blue_style} if obj.name == "With Material" else set()
|
|
assert self.get_used_styles(obj) == expected
|
|
|
|
def test_dont_override_exisiting_styles(self):
|
|
self.setup_test()
|
|
ifc_file = tool.Ifc.get()
|
|
element_type = next(ifc_file.by_type("IfcActuatorType").__iter__())
|
|
red_material = next(i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material")
|
|
green_style = next(i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Green")
|
|
|
|
# Occurrence with a style.
|
|
element_type_obj = tool.Ifc.get_object(element_type)
|
|
with bpy.context.temp_override(selected_objects=[element_type_obj]):
|
|
bpy.ops.bim.assign_style_to_selected(style_id=green_style.id())
|
|
|
|
ifcopenshell.api.material.assign_material(ifc_file, material=red_material, products=[element_type])
|
|
tool.Material.ensure_material_assigned([element_type], material=red_material)
|
|
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == {green_style}
|
|
for element in ifc_file.by_type("IfcActuator"):
|
|
obj = tool.Ifc.get_object(element)
|
|
assert self.get_used_styles(obj) == {green_style}
|
|
|
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element_type])
|
|
tool.Material.ensure_material_unassigned([element_type])
|
|
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == {green_style}
|
|
for element in ifc_file.by_type("IfcActuator"):
|
|
obj = tool.Ifc.get_object(element)
|
|
assert self.get_used_styles(obj) == {green_style}
|
|
|
|
def test_assign_material_to_representation_that_has_2_items_and_1_item_has_a_style(self):
|
|
self.setup_test(and_elements=False)
|
|
ifc_file = tool.Ifc.get()
|
|
red_material = next(i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material")
|
|
red_style = tool.Material.get_style(red_material)
|
|
green_style = next(i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Green")
|
|
|
|
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
|
obj = bpy.data.objects["Cube"]
|
|
bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="")
|
|
element = tool.Ifc.get_entity(obj)
|
|
builder = ShapeBuilder(ifc_file)
|
|
|
|
# Change representation that consists of 2 rep items:
|
|
# 1 with style and other without.
|
|
rep = tool.Geometry.get_active_representation(obj)
|
|
assert rep
|
|
cube = rep.Items[0]
|
|
cube2 = builder.deep_copy(cube)
|
|
rep.Items = [cube, cube2]
|
|
tool.Style.assign_style_to_representation_item(cube, green_style)
|
|
tool.Geometry._reload_representation(obj)
|
|
|
|
def get_material_indices(mesh: bpy.types.Mesh) -> np.ndarray:
|
|
buffer = np.empty(len(mesh.polygons), dtype="I")
|
|
mesh.polygons.foreach_get("material_index", buffer)
|
|
return buffer
|
|
|
|
mesh = self.get_mesh(obj)
|
|
assert len(mesh.materials) == 2
|
|
assert set(mesh.materials) == {bpy.data.materials["Green"], None}
|
|
|
|
ifcopenshell.api.material.assign_material(ifc_file, products=[element], material=red_material)
|
|
tool.Material.ensure_material_assigned([element], material=red_material)
|
|
assert self.get_used_styles(obj) == {green_style, red_style}
|
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
|
tool.Material.ensure_material_unassigned([element])
|
|
mesh = self.get_mesh(obj)
|
|
assert len(mesh.materials) == 2
|
|
assert set(mesh.materials) == {bpy.data.materials["Green"], None}
|
|
|
|
# Test that if style is the same it would just reuse it.
|
|
tool.Style.assign_style_to_representation_item(cube, red_style)
|
|
tool.Geometry._reload_representation(obj)
|
|
mesh = self.get_mesh(obj)
|
|
assert len(mesh.materials) == 2
|
|
assert set(mesh.materials) == {bpy.data.materials["Red"], None}
|
|
|
|
ifcopenshell.api.material.assign_material(ifc_file, products=[element], material=red_material)
|
|
tool.Material.ensure_material_assigned([element], material=red_material)
|
|
mesh = self.get_mesh(obj)
|
|
assert mesh.materials[:] == [bpy.data.materials["Red"]]
|
|
# All polygons are just reassigned to the existing material.
|
|
assert set(get_material_indices(mesh)) == {mesh.materials.find("Red")}
|
|
|
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
|
tool.Material.ensure_material_unassigned([element])
|
|
mesh = self.get_mesh(obj)
|
|
assert len(mesh.materials) == 2
|
|
assert set(mesh.materials) == {bpy.data.materials["Red"], None}
|
|
assert set(get_material_indices(mesh)) == {0, 1}
|
|
|
|
def test_assign_unassign_overriding_occurrence_material(self):
|
|
self.setup_test(and_elements=True)
|
|
ifc_file = tool.Ifc.get()
|
|
element_type = next(ifc_file.by_type("IfcActuatorType").__iter__())
|
|
red_material = next(i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material")
|
|
no_style_material = ifcopenshell.api.material.add_material(ifc_file, "No Style")
|
|
obj = bpy.data.objects["Simple"]
|
|
element = tool.Ifc.get_entity(obj)
|
|
|
|
ifcopenshell.api.material.assign_material(ifc_file, material=red_material, products=[element_type])
|
|
tool.Material.ensure_material_assigned([element_type], material=red_material)
|
|
|
|
# Override type material.
|
|
ifcopenshell.api.material.assign_material(ifc_file, material=no_style_material, products=[element])
|
|
tool.Material.ensure_material_assigned([element], material=no_style_material)
|
|
assert self.get_mesh(obj).materials[:] == []
|
|
|
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
|
tool.Material.ensure_material_unassigned([element])
|
|
assert self.get_mesh(obj).materials[:] == [bpy.data.materials["Red"]]
|
|
|
|
|
|
class TestOffsetWall(NewFile):
|
|
def test_run(self):
|
|
ifc = ifcopenshell.file()
|
|
tool.Ifc.set(ifc)
|
|
|
|
wall_type = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWallType", name="WAL01")
|
|
material_set = ifcopenshell.api.material.add_material_set(ifc, set_type="IfcMaterialLayerSet")
|
|
material = ifcopenshell.api.material.add_material(ifc, name="PB01", category="gypsum")
|
|
layer = ifcopenshell.api.material.add_layer(ifc, layer_set=material_set, material=material)
|
|
ifcopenshell.api.material.edit_layer(ifc, layer=layer, attributes={"LayerThickness": 100})
|
|
ifcopenshell.api.material.assign_material(ifc, products=[wall_type], material=material_set)
|
|
|
|
wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
|
|
ifcopenshell.api.type.assign_type(ifc, related_objects=[wall], relating_type=wall_type)
|
|
rel = ifcopenshell.api.material.assign_material(ifc, products=[wall], type="IfcMaterialLayerSetUsage")
|
|
usage = rel.RelatingMaterial
|
|
obj = bpy.data.objects.new("Wall", None)
|
|
tool.Ifc.link(wall, obj)
|
|
|
|
usage.DirectionSense = "POSITIVE"
|
|
subject.offset_wall(obj, "CENTER")
|
|
assert usage.OffsetFromReferenceLine == -50
|
|
usage.DirectionSense = "NEGATIVE"
|
|
subject.offset_wall(obj, "CENTER")
|
|
assert usage.OffsetFromReferenceLine == 50
|
|
|
|
usage.DirectionSense = "POSITIVE"
|
|
subject.offset_wall(obj, "INTERIOR")
|
|
assert usage.OffsetFromReferenceLine == -100
|
|
usage.DirectionSense = "NEGATIVE"
|
|
subject.offset_wall(obj, "INTERIOR")
|
|
assert usage.OffsetFromReferenceLine == 0
|
|
|
|
usage.DirectionSense = "POSITIVE"
|
|
subject.offset_wall(obj, "EXTERIOR")
|
|
assert usage.OffsetFromReferenceLine == 0
|
|
usage.DirectionSense = "NEGATIVE"
|
|
subject.offset_wall(obj, "EXTERIOR")
|
|
assert usage.OffsetFromReferenceLine == 100
|