Representation Items UI - support editing layers

Example - https://imgur.com/a/3fn051N
This commit is contained in:
Andrej730
2025-01-27 15:16:45 +05:00
parent 56af23b89f
commit 13a182cd95
5 changed files with 144 additions and 8 deletions
@@ -34,6 +34,7 @@ classes = (
operator.DuplicateMoveLinkedAggregate,
operator.DuplicateMoveLinkedAggregateMacro,
operator.EditObjectPlacement,
operator.EditRepresentationItemLayer,
operator.EditRepresentationItemShapeAspect,
operator.EditRepresentationItemStyle,
operator.EnableEditingRepresentationItemShapeAspect,
@@ -67,6 +68,7 @@ classes = (
operator.SelectConnection,
operator.SelectRepresentationItem,
operator.SwitchRepresentation,
operator.UnassignRepresentationItemLayer,
operator.UnassignRepresentationItemStyle,
operator.UpdateItemAttributes,
operator.UpdateParametricRepresentation,
@@ -23,6 +23,7 @@ import logging
import numpy as np
import numpy.typing as npt
import ifcopenshell
import ifcopenshell.api.layer
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
@@ -2325,7 +2326,7 @@ class EnableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = tool.Geometry.get_active_or_representation_obj()
props = obj.BIMGeometryProperties
props = tool.Geometry.get_object_geometry_props(obj)
props.is_editing = True
props.items.clear()
@@ -3209,3 +3210,51 @@ class OverrideMove(bpy.types.Operator):
return {"FINISHED"}
return {"FINISHED"}
class EditRepresentationItemLayer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_representation_item_layer"
bl_label = "Edit Representation Item Layer"
bl_description = "Edit presentation layer for the active representation item."
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
ifc_file = tool.Ifc.get()
obj = context.active_object
assert obj
props = tool.Geometry.get_object_geometry_props(obj)
new_layer = ifc_file.by_id(int(props.representation_item_layer))
item = ifc_file.by_id(int(props.active_item.ifc_definition_id))
item_layer = next(iter(item.LayerAssignment), None)
# We assume just 1 layer can be assigned to representation item.
if item_layer:
# Same layer is already assigned.
if item_layer == new_layer:
props.is_editing_item_layer = False
return {"FINISHED"}
ifcopenshell.api.layer.unassign_layer(ifc_file, [item], item_layer)
ifcopenshell.api.layer.assign_layer(ifc_file, [item], new_layer)
props.is_editing_item_layer = False
bpy.ops.bim.enable_editing_representation_items()
return {"FINISHED"}
class UnassignRepresentationItemLayer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_representation_item_layer"
bl_label = "Unassign Representation Item Layer"
bl_description = "Unassign presentation layer from the active representation item."
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
ifc_file = tool.Ifc.get()
obj = context.active_object
assert obj
props = tool.Geometry.get_object_geometry_props(obj)
item = ifc_file.by_id(props.active_item.ifc_definition_id)
layer = item.LayerAssignment[0] # If there is no layer, then button is not visible in UI.
ifcopenshell.api.layer.unassign_layer(ifc_file, [item], layer)
bpy.ops.bim.enable_editing_representation_items()
return {"FINISHED"}
+54 -2
View File
@@ -32,7 +32,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import Optional
from typing import Optional, TYPE_CHECKING
def get_contexts(self, context):
@@ -105,6 +105,14 @@ def get_material_constituents(self, context, edit_text):
return ObjectMaterialData.data["active_material_constituents"]
def get_layers(self, context):
from bonsai.bim.module.layer.data import LayersData
if not LayersData.is_loaded:
LayersData.load()
return LayersData.data["layers_enum"]
def update_shape_aspect(self, context):
shape_aspect_id = self.representation_item_shape_aspect
attrs = self.shape_aspect_attrs
@@ -128,6 +136,16 @@ class RepresentationItem(PropertyGroup):
shape_aspect_id: IntProperty(name="Shape Aspect IFC ID")
tags: StringProperty(name="Tags")
if TYPE_CHECKING:
name: str
surface_style: str
surface_style_id: int
layer: str
ifc_definition_id: int
shape_aspect: str
shape_aspect_id: int
tags: str
class RepresentationItemObject(PropertyGroup):
name: StringProperty(name="Name")
@@ -149,6 +167,20 @@ class ShapeAspect(PropertyGroup):
)
def update_is_editing_item_layer(self: "BIMObjectGeometryProperties", context: bpy.types.Context) -> None:
if self.is_editing_item_layer:
ifc_file = tool.Ifc.get()
active_ui_item = self.active_item
assert active_ui_item
item = ifc_file.by_id(active_ui_item.ifc_definition_id)
if layer := next(iter(item.LayerAssignment), None):
self.representation_item_layer = str(layer.id())
return
if "representation_item_layer" in self:
del self["representation_item_layer"]
class BIMObjectGeometryProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts")
is_editing: BoolProperty(name="Is Editing", default=False)
@@ -161,12 +193,32 @@ class BIMObjectGeometryProperties(PropertyGroup):
items=get_shape_aspects, name="Representation Item Shape Aspect", update=update_shape_aspect
)
shape_aspect_attrs: PointerProperty(type=ShapeAspect)
is_editing_item_layer: BoolProperty(
name="Is Editing Item's Presentation Layer",
description="Toggle editing presentation layer for the item.",
default=False,
update=update_is_editing_item_layer,
)
representation_item_layer: EnumProperty(items=get_layers, name="Representation Item's Layer")
@property
def active_item(self):
if self.active_item_index < len(self.items):
if 0 <= self.active_item_index < len(self.items):
return self.items[self.active_item_index]
if TYPE_CHECKING:
contexts: str
is_editing: bool
items: bpy.types.bpy_prop_collection_idprop[RepresentationItem]
active_item_index: int
is_editing_item_style: bool
representation_item_style: str
is_editing_item_shape_aspect: bool
representation_item_shape_aspect: str
shape_aspect_attrs: ShapeAspect
is_editing_item_layer: bool
representation_item_layer: str
class BIMGeometryProperties(PropertyGroup):
# Revit workaround
+16 -3
View File
@@ -179,7 +179,7 @@ class BIM_PT_representation_items(Panel):
RepresentationItemsData.load()
obj = context.scene.BIMGeometryProperties.representation_obj or tool.Blender.get_active_object()
props = obj.BIMGeometryProperties
props = tool.Geometry.get_object_geometry_props(obj)
row = self.layout.row(align=True)
row.label(text=f"{RepresentationItemsData.data['total_items']} Items Found")
@@ -207,7 +207,9 @@ class BIM_PT_representation_items(Panel):
surface_style = active_item.surface_style
surface_style_id = active_item.surface_style_id
shape_aspect = active_item.shape_aspect
layer = active_item.layer
# Style.
row = self.layout.row(align=True)
if props.is_editing_item_style:
# NOTE: we currently support 1 item having just 1 style
@@ -226,9 +228,19 @@ class BIM_PT_representation_items(Panel):
if surface_style:
row.operator("bim.unassign_representation_item_style", icon="X", text="")
row = self.layout.row()
row.label(text=active_item.layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
# Presentation layer.
row = self.layout.row(align=True)
if props.is_editing_item_layer:
prop_with_search(row, props, "representation_item_layer", icon="STICKY_UVS_LOC", text="")
row.operator("bim.edit_representation_item_layer", icon="CHECKMARK", text="")
row.prop(props, "is_editing_item_layer", icon="CANCEL", text="")
else:
row.label(text=layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
row.prop(props, "is_editing_item_layer", icon="GREASEPENCIL", text="")
if layer:
row.operator("bim.unassign_representation_item_layer", icon="X", text="")
# Mappings.
if active_item.name.endswith("FaceSet"):
if "UV" in active_item.tags:
text = "Has UV mapping"
@@ -242,6 +254,7 @@ class BIM_PT_representation_items(Panel):
text = "Has no colour mapping"
self.layout.label(text=text, icon="COLOR")
# Shape aspect.
row = self.layout.row(align=True)
if props.is_editing_item_shape_aspect:
row.prop(props, "representation_item_shape_aspect", icon="SHAPEKEY_DATA", text="")
+22 -2
View File
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell
import bonsai.tool as tool
from typing import Any
def refresh():
@@ -31,12 +32,31 @@ class LayersData:
@classmethod
def load(cls):
cls.data = {"total_layers": cls.total_layers(), "active_layers": cls.active_layers()}
cls.data = {
"active_layers": cls.active_layers(),
"layers": cls.layers(),
}
# After .layers().
cls.data["total_layers"] = cls.total_layers()
cls.data["layers_enum"] = cls.layers_enum()
cls.is_loaded = True
@classmethod
def total_layers(cls) -> int:
return len(tool.Ifc.get().by_type("IfcPresentationLayerAssignment"))
return len(cls.data["layers"])
@classmethod
def layers(cls) -> dict[int, dict[str, Any]]:
results = dict()
KEEP_ATTRS = set(("id", "Name", "Description"))
for layer in tool.Ifc.get().by_type("IfcPresentationLayerAssignment"):
results[layer.id()] = {k: v for k, v in layer.get_info().items() if k in KEEP_ATTRS}
return results
@classmethod
def layers_enum(cls) -> list[tuple[str, str, str]]:
return list((str(data["id"]), data["Name"], data["Description"] or "") for data in cls.data["layers"].values())
@classmethod
def active_layers(cls) -> dict[int, str]: