You can now assign and unassign styles to materials

This commit is contained in:
Dion Moult
2023-10-21 15:23:43 +11:00
parent ca1925cc9f
commit f9236f8b42
8 changed files with 286 additions and 20 deletions
@@ -40,10 +40,12 @@ classes = (
operator.EditMaterial,
operator.EditMaterialSetItem,
operator.EditMaterialSetItemProfile,
operator.EditMaterialStyle,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterial,
operator.EnableEditingMaterialSetItem,
operator.EnableEditingMaterialSetItemProfile,
operator.EnableEditingMaterialStyle,
operator.ExpandMaterialCategory,
operator.LoadMaterials,
operator.RemoveConstituent,
@@ -55,6 +57,7 @@ classes = (
operator.ReorderMaterialSetItem,
operator.SelectByMaterial,
operator.UnassignMaterial,
operator.UnassignMaterialStyle,
operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
@@ -38,6 +38,9 @@ class MaterialsData:
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
"profiles": cls.profiles(),
"styles": cls.styles(),
"contexts": cls.contexts(),
"active_styles": cls.active_styles(),
}
cls.is_loaded = True
@@ -79,6 +82,58 @@ class MaterialsData:
if p.ProfileName
]
@classmethod
def contexts(cls):
results = []
for element in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
results.append((str(element.id()), element.ContextType or "Unnamed", ""))
for element in tool.Ifc.get().by_type("IfcGeometricRepresentationSubContext", include_subtypes=False):
results.append(
(
str(element.id()),
"{}/{}/{}".format(
element.ContextType or "Unnamed",
element.ContextIdentifier or "Unnamed",
element.TargetView or "Unnamed",
),
"",
)
)
return results
@classmethod
def styles(cls):
return [(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcSurfaceStyle") if s.Name]
@classmethod
def active_styles(cls):
props = bpy.context.scene.BIMMaterialProperties
results = []
if props.materials and props.active_material_index < len(props.materials):
material = props.materials[props.active_material_index].ifc_definition_id
if not material:
return results
material = tool.Ifc.get().by_id(material)
for definition in material.HasRepresentation:
for representation in definition.Representations:
if not representation.is_a("IfcStyledRepresentation"):
continue
context = representation.ContextOfItems
for item in representation.Items:
if not item.is_a("IfcStyledItem"):
continue
for style in item.Styles:
if style.is_a("IfcSurfaceStyle"):
results.append({
"context_type": context.ContextType,
"context_identifier": getattr(context, "ContextIdentifier", ""),
"target_view": getattr(context, "TargetView", ""),
"name": style.Name or "Unnamed",
"id": style.id(),
"context_id": context.id(),
})
return results
class ObjectMaterialData:
data = {}
@@ -715,3 +715,50 @@ class ContractMaterialCategory(bpy.types.Operator):
category.is_expanded = False
core.load_materials(tool.Material, props.material_type)
return {"FINISHED"}
class EnableEditingMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_material_style"
bl_label = "Enable Editing Material Style"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
props = bpy.context.scene.BIMMaterialProperties
props.active_material_id = self.material
props.editing_material_type = "STYLE"
class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_material_style"
bl_label = "Edit Material Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMMaterialProperties
material = tool.Ifc.get().by_id(props.active_material_id)
style = tool.Ifc.get().by_id(int(props.styles))
context = tool.Ifc.get().by_id(int(props.contexts))
ifcopenshell.api.run(
"style.assign_material_style", tool.Ifc.get(), material=material, style=style, context=context
)
tool.Material.disable_editing_material()
core.load_materials(tool.Material, props.material_type)
class UnassignMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_material_style"
bl_label = "Unassign Material Style"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
context: bpy.props.IntProperty()
def _execute(self, context):
props = bpy.context.scene.BIMMaterialProperties
material = tool.Ifc.get().by_id(props.materials[props.active_material_index].ifc_definition_id)
style = tool.Ifc.get().by_id(self.style)
context = tool.Ifc.get().by_id(self.context)
ifcopenshell.api.run(
"style.unassign_material_style", tool.Ifc.get(), material=material, style=style, context=context
)
core.load_materials(tool.Material, props.material_type)
@@ -103,11 +103,24 @@ def get_profiles(self, context):
return MaterialsData.data["profiles"]
def get_styles(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["styles"]
def get_contexts(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["contexts"]
class Material(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_category: BoolProperty(name="Is Category", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=True)
has_style: BoolProperty(name="Has Style", default=True)
total_elements: IntProperty(name="Total Elements")
@@ -119,7 +132,9 @@ class BIMMaterialProperties(PropertyGroup):
profiles: EnumProperty(items=get_profiles, name="Profiles")
active_material_id: IntProperty(name="Active Material ID")
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
editing_material_type = StringProperty(name="Editing Material Type")
editing_material_type: StringProperty(name="Editing Material Type")
styles: EnumProperty(items=get_styles, name="Styles")
contexts: EnumProperty(items=get_contexts, name="Contexts")
class BIMObjectMaterialProperties(PropertyGroup):
@@ -57,26 +57,19 @@ class BIM_PT_materials(Panel):
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
if not self.props.active_material_id:
row.operator("bim.add_material", text="", icon="ADD")
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
if material.ifc_definition_id:
if self.props.active_material_id:
row.operator(
"bim.edit_material", text="", icon="CHECKMARK"
).material = material.ifc_definition_id
row.operator(
"bim.disable_editing_material", text="", icon="CANCEL"
).material = material.ifc_definition_id
self.draw_editable_material_attributes_ui()
else:
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator(
"bim.enable_editing_material", text="", icon="GREASEPENCIL"
).material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
op = row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL")
op.material = material.ifc_definition_id
op = row.operator("bim.enable_editing_material_style", text="", icon="SHADING_RENDERED")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
self.draw_editing_ui()
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
@@ -88,8 +81,33 @@ class BIM_PT_materials(Panel):
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
def draw_editable_material_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
for style in MaterialsData.data["active_styles"]:
row = self.layout.row(align=True)
row.label(text="", icon="SHADING_RENDERED")
row.label(text=style["context_type"])
row.label(text=style["context_identifier"])
row.label(text=style["target_view"])
row.label(text=style["name"])
op = row.operator("bim.unassign_material_style", text="", icon="X")
op.style = style["id"]
op.context = style["context_id"]
def draw_editing_ui(self):
if not self.props.active_material_id:
return
ifc_definition_id = self.props.active_material_id
if self.props.editing_material_type == "ATTRIBUTES":
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
row = self.layout.row(align=True)
row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
elif self.props.editing_material_type == "STYLE":
row = self.layout.row(align=True)
row.prop(self.props, "contexts", text="")
row.prop(self.props, "styles", text="")
row = self.layout.row(align=True)
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
class BIM_PT_material(Panel):
@@ -343,3 +361,6 @@ class BIM_UL_materials(UIList):
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))
if item.has_style:
row2.label(text="", icon="SHADING_RENDERED")
@@ -80,6 +80,7 @@ class Material(blenderbim.core.tool.Material):
new.total_elements = len(
ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material)
)
new.has_style = bool(material.HasRepresentation)
return
for material in materials:
new = props.materials.add()
@@ -0,0 +1,54 @@
# 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/>.
class Usecase:
def __init__(self, file, representation=None):
"""Removes a styled representation
Styled representations are typically associated with materials. This
removes the representation but not the underlying styles.
:param representation: The IfcStyledRepresentation to remove.
:type representation: ifcopenshell.entity_instance.entity_instance
:return: None
:rtype: None
Example:
.. code:: python
# Remove a styled representation
ifcopenshell.api.run("style.remove_styled_representation", model, representation=representation)
"""
self.file = file
self.settings = {"representation": representation}
def execute(self):
for inverse in self.file.get_inverse(self.settings["representation"]):
if inverse.is_a("IfcMaterialDefinitionRepresentation") and len(inverse.Representations) == 1:
self.file.remove(inverse)
for item in self.settings["representation"].Items:
if item.is_a("IfcStyledItem") and self.file.get_total_inverses(item) == 1:
for style in item.Styles:
if style.is_a("IfcPresentationStyleAssignment"):
self.file.remove(style)
self.file.remove(item)
self.file.remove(self.settings["representation"])
@@ -0,0 +1,70 @@
# 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/>.
class Usecase:
def __init__(self, file, material=None, style=None, context=None):
"""Unassigns a style to a material
This does the inverse of assign_material_style.
:param material: The IfcMaterial which you want to unassign the style from.
:type material: ifcopenshell.entity_instance.entity_instance
:param style: The IfcPresentationStyle (typically IfcSurfaceStyle) that
you want to unassign from material. This will then be applied to all
objects that have that material.
:type style: ifcopenshell.entity_instance.entity_instance
:param context: The IfcGeometricRepresentationSubContext at which this
style should be unassigned. Typically this is the Model BODY context.
:type context: ifcopenshell.entity_instance.entity_instance
:return: None
:rtype: None
Example:
.. code:: python
ifcopenshell.api.run("style.unassign_material_style", model, material=concrete, style=style, context=body)
"""
self.file = file
self.settings = {
"material": material,
"style": style,
"context": context,
}
def execute(self):
to_delete = set()
for definition in self.settings["material"].HasRepresentation:
for representation in definition.Representations:
if not representation.is_a("IfcStyledRepresentation"):
continue
if representation.ContextOfItems != self.settings["context"]:
continue
for item in representation.Items:
if not item.is_a("IfcStyledItem"):
continue
styles = [s for s in item.Styles if s != self.settings["style"]]
if not styles:
self.file.remove(item)
elif len(styles) != len(item.Styles):
item.Styles = styles
if not representation.Items:
self.file.remove(representation)
if not definition.Representations:
self.file.remove(definition)