Fix #2898. You can now copy materials from the materials tab.

This commit is contained in:
Dion Moult
2023-03-26 19:04:32 +11:00
parent fb3e4016ec
commit e9a3630595
4 changed files with 104 additions and 77 deletions
@@ -664,35 +664,23 @@ class CopyMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() blender_material = context.active_object.active_material
material = ifcopenshell.util.element.get_material( material = tool.Ifc.get_entity(blender_material)
self.file.by_id(context.active_object.BIMObjectProperties.ifc_definition_id) copied_material = ifcopenshell.api.run("material.copy_material", tool.Ifc.get(), material=material)
) copied_blender_material = bpy.data.materials.new(blender_material.name)
for obj in tool.Blender.get_selected_objects(): copied_style = self.get_style(copied_material)
if obj == context.active_object: tool.Ifc.link(copied_material, copied_blender_material)
continue if copied_style:
if not obj.BIMObjectProperties.ifc_definition_id: tool.Ifc.link(copied_style, copied_blender_material)
continue context.active_object.active_material = copied_blender_material
ifcopenshell.api.run(
"material.copy_material",
self.file,
**{
"material": material,
"element": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
},
)
self.set_default_material(obj, material)
def set_default_material(self, obj, material): def get_style(self, material):
object_material_ids = [ for material_representation in material.HasRepresentation:
om.BIMObjectProperties.ifc_definition_id for representation in material_representation.Representations:
for om in obj.data.materials for item in representation.Items:
if om is not None and om.BIMObjectProperties.ifc_definition_id for style in item.Styles:
] if style.is_a("IfcSurfaceStyle"):
return style
if material.id() in object_material_ids:
return
obj.data.materials.append(IfcStore.get_element(material.id()))
class ExpandMaterialCategory(bpy.types.Operator): class ExpandMaterialCategory(bpy.types.Operator):
@@ -92,6 +92,7 @@ class BIM_PT_material(Panel):
material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
if bool(material_id): if bool(material_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id
row.operator("bim.copy_material", icon="DUPLICATE", text="")
row.operator("bim.unlink_material", icon="UNLINKED", text="") row.operator("bim.unlink_material", icon="UNLINKED", text="")
else: else:
op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material") op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
@@ -158,8 +159,6 @@ class BIM_PT_object_material(Panel):
op.material_set_usage = ObjectMaterialData.data["material_id"] op.material_set_usage = ObjectMaterialData.data["material_id"]
row.operator("bim.disable_editing_assigned_material", icon="CANCEL", text="") row.operator("bim.disable_editing_assigned_material", icon="CANCEL", text="")
else: else:
if ObjectMaterialData.data["material_class"] == "IfcMaterial":
row.operator("bim.copy_material", icon="COPYDOWN", text="")
row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="") row.operator("bim.enable_editing_assigned_material", icon="GREASEPENCIL", text="")
row.operator("bim.unassign_material", icon="X", text="") row.operator("bim.unassign_material", icon="X", text="")
@@ -21,65 +21,44 @@ import ifcopenshell.util.element
class Usecase: class Usecase:
def __init__(self, file, material=None, element=None): def __init__(self, file, material=None):
"""Copies a material to an element """Copies a material
This convenience function exists to allow you to easily unassign any All material psets and styles are copied. The copied material is not
existing material an element has, and assign a new material to it. associated to any elements.
Essentially, it is a form a copy / pasting a material from one element
to another.
It's a bit out of place, and will likely be redesigned or removed in the :param material: The IfcMaterial to copy
future. It's not recommended to use this function, and
ifcopenshell.api.material.assign_material should be used instead.
:param material: The IfcMaterial to assign to the element.
:type material: ifcopenshell.entity_instance.entity_instance :type material: ifcopenshell.entity_instance.entity_instance
:param element: The IfcElement or IfcElementType to remove all previous :return: The new copy of the material
materials from and assign the new material to. :rtype: ifcopenshell.entity_instance.entity_instance
:type element: ifcopenshell.entity_instance.entity_instance
:return: None
:rtype: None
Example: Example:
.. code:: python .. code:: python
concrete = ifcopenshell.api.run("material.add_material", model, name="CON01", category="concrete") concrete = ifcopenshell.api.run("material.add_material", model, name="CON01", category="concrete")
wood = ifcopenshell.api.run("material.add_material", model, name="TIM01", category="wood")
# Let's imagine a concrete bench made out of concrete. # Let's duplicate the concrete material
bench = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurniture") concrete_copy = ifcopenshell.api.run("material.copy_material", model, material=concrete)
ifcopenshell.api.run("material.assign_material", model,
product=bench, type="IfcMaterial", material=concrete)
# And some street furniture made from wood.
street_furniture = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurniture")
ifcopenshell.api.run("material.assign_material", model,
product=street_furniture, type="IfcMaterial", material=wood)
# Let's copy the concrete over to the street furniture.
ifcopenshell.api.run("material.copy_material", model, material=wood, element=street_furniture)
""" """
self.file = file self.file = file
self.settings = {"material": material, "element": element} self.settings = {"material": material}
def execute(self): def execute(self):
ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["element"])
if self.settings["material"].is_a("IfcMaterial"): if self.settings["material"].is_a("IfcMaterial"):
ifcopenshell.api.run( new = ifcopenshell.util.element.copy(self.file, self.settings["material"])
"material.assign_material", for inverse in self.file.get_inverse(self.settings["material"]):
self.file, if inverse.is_a("IfcMaterialProperties"):
product=self.settings["element"], # Properties must not be shared between objects for convenience of authoring
type="IfcMaterial", inverse = ifcopenshell.util.element.copy(self.file, inverse)
material=self.settings["material"], properties = []
) for pset in inverse.Properties:
# No other material type can be copied right now. properties.append(ifcopenshell.util.element.copy_deep(self.file, pset))
# 1. Material lists and constituents may have shape aspects and I inverse.Properties = properties
# haven't implemented it yet. inverse.Material = new
# 2. Material layer and profile sets implicitly define parametric elif inverse.is_a("IfcMaterialDefinitionRepresentation"):
# geometry and we have no way of guaranteeing that this constraint is inverse = ifcopenshell.util.element.copy_deep(
# satisfied. self.file, inverse, exclude=["IfcRepresentationContext", "IfcMaterial"]
# 3. Material set usages follow an unofficial constraint that all )
# instances must have a usage of their type's material set. We cannot inverse.RepresentedMaterial = new
# guarantee that constraint. return new
@@ -0,0 +1,61 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.element
class TestCopyMaterial(test.bootstrap.IFC4):
def test_copy_a_single_material(self):
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
new = ifcopenshell.api.run("material.copy_material", self.file, material=material)
assert new.Name == "CON01"
assert len(self.file.by_type("IfcMaterial")) == 2
def test_assignments_are_not_copied(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material)
new = ifcopenshell.api.run("material.copy_material", self.file, material=material)
assert material.AssociatedTo
assert not new.AssociatedTo
def test_copy_a_material_with_properties(self):
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=material, name="Foo_Bar")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"foo": "bar"})
new = ifcopenshell.api.run("material.copy_material", self.file, material=material)
assert new.Name == "CON01"
assert len(self.file.by_type("IfcMaterial")) == 2
assert new.HasProperties[0] != material.HasProperties[0]
assert new.HasProperties[0].Properties[0] != material.HasProperties[0].Properties[0]
assert ifcopenshell.util.element.get_pset(new, "Foo_Bar", "foo") == "bar"
def test_copy_a_material_with_a_style_representation(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
context = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
material = ifcopenshell.api.run("material.add_material", self.file, name="CON01")
style = ifcopenshell.api.run("style.add_style", self.file)
ifcopenshell.api.run("style.assign_material_style", self.file, material=material, style=style, context=context)
new = ifcopenshell.api.run("material.copy_material", self.file, material=material)
assert new.Name == "CON01"
assert len(self.file.by_type("IfcMaterialDefinitionRepresentation")) == 2
assert new.HasRepresentation[0] != material.HasRepresentation[0]
assert new.HasRepresentation[0].Representations[0] != material.HasRepresentation[0].Representations[0]
assert new.HasRepresentation[0].Representations[0].ContextOfItems == context