mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Representation Items UI - support editing layers
Example - https://imgur.com/a/3fn051N
This commit is contained in:
@@ -34,6 +34,7 @@ classes = (
|
|||||||
operator.DuplicateMoveLinkedAggregate,
|
operator.DuplicateMoveLinkedAggregate,
|
||||||
operator.DuplicateMoveLinkedAggregateMacro,
|
operator.DuplicateMoveLinkedAggregateMacro,
|
||||||
operator.EditObjectPlacement,
|
operator.EditObjectPlacement,
|
||||||
|
operator.EditRepresentationItemLayer,
|
||||||
operator.EditRepresentationItemShapeAspect,
|
operator.EditRepresentationItemShapeAspect,
|
||||||
operator.EditRepresentationItemStyle,
|
operator.EditRepresentationItemStyle,
|
||||||
operator.EnableEditingRepresentationItemShapeAspect,
|
operator.EnableEditingRepresentationItemShapeAspect,
|
||||||
@@ -67,6 +68,7 @@ classes = (
|
|||||||
operator.SelectConnection,
|
operator.SelectConnection,
|
||||||
operator.SelectRepresentationItem,
|
operator.SelectRepresentationItem,
|
||||||
operator.SwitchRepresentation,
|
operator.SwitchRepresentation,
|
||||||
|
operator.UnassignRepresentationItemLayer,
|
||||||
operator.UnassignRepresentationItemStyle,
|
operator.UnassignRepresentationItemStyle,
|
||||||
operator.UpdateItemAttributes,
|
operator.UpdateItemAttributes,
|
||||||
operator.UpdateParametricRepresentation,
|
operator.UpdateParametricRepresentation,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import logging
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.typing as npt
|
import numpy.typing as npt
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.api.layer
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.placement
|
import ifcopenshell.util.placement
|
||||||
import ifcopenshell.util.representation
|
import ifcopenshell.util.representation
|
||||||
@@ -2325,7 +2326,7 @@ class EnableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
obj = tool.Geometry.get_active_or_representation_obj()
|
obj = tool.Geometry.get_active_or_representation_obj()
|
||||||
|
|
||||||
props = obj.BIMGeometryProperties
|
props = tool.Geometry.get_object_geometry_props(obj)
|
||||||
props.is_editing = True
|
props.is_editing = True
|
||||||
|
|
||||||
props.items.clear()
|
props.items.clear()
|
||||||
@@ -3209,3 +3210,51 @@ class OverrideMove(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
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"}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from bpy.props import (
|
|||||||
FloatVectorProperty,
|
FloatVectorProperty,
|
||||||
CollectionProperty,
|
CollectionProperty,
|
||||||
)
|
)
|
||||||
from typing import Optional
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
def get_contexts(self, context):
|
def get_contexts(self, context):
|
||||||
@@ -105,6 +105,14 @@ def get_material_constituents(self, context, edit_text):
|
|||||||
return ObjectMaterialData.data["active_material_constituents"]
|
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):
|
def update_shape_aspect(self, context):
|
||||||
shape_aspect_id = self.representation_item_shape_aspect
|
shape_aspect_id = self.representation_item_shape_aspect
|
||||||
attrs = self.shape_aspect_attrs
|
attrs = self.shape_aspect_attrs
|
||||||
@@ -128,6 +136,16 @@ class RepresentationItem(PropertyGroup):
|
|||||||
shape_aspect_id: IntProperty(name="Shape Aspect IFC ID")
|
shape_aspect_id: IntProperty(name="Shape Aspect IFC ID")
|
||||||
tags: StringProperty(name="Tags")
|
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):
|
class RepresentationItemObject(PropertyGroup):
|
||||||
name: StringProperty(name="Name")
|
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):
|
class BIMObjectGeometryProperties(PropertyGroup):
|
||||||
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
contexts: EnumProperty(items=get_contexts, name="Contexts")
|
||||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
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
|
items=get_shape_aspects, name="Representation Item Shape Aspect", update=update_shape_aspect
|
||||||
)
|
)
|
||||||
shape_aspect_attrs: PointerProperty(type=ShapeAspect)
|
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
|
@property
|
||||||
def active_item(self):
|
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]
|
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):
|
class BIMGeometryProperties(PropertyGroup):
|
||||||
# Revit workaround
|
# Revit workaround
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class BIM_PT_representation_items(Panel):
|
|||||||
RepresentationItemsData.load()
|
RepresentationItemsData.load()
|
||||||
|
|
||||||
obj = context.scene.BIMGeometryProperties.representation_obj or tool.Blender.get_active_object()
|
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 = self.layout.row(align=True)
|
||||||
row.label(text=f"{RepresentationItemsData.data['total_items']} Items Found")
|
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 = active_item.surface_style
|
||||||
surface_style_id = active_item.surface_style_id
|
surface_style_id = active_item.surface_style_id
|
||||||
shape_aspect = active_item.shape_aspect
|
shape_aspect = active_item.shape_aspect
|
||||||
|
layer = active_item.layer
|
||||||
|
|
||||||
|
# Style.
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
if props.is_editing_item_style:
|
if props.is_editing_item_style:
|
||||||
# NOTE: we currently support 1 item having just 1 style
|
# NOTE: we currently support 1 item having just 1 style
|
||||||
@@ -226,9 +228,19 @@ class BIM_PT_representation_items(Panel):
|
|||||||
if surface_style:
|
if surface_style:
|
||||||
row.operator("bim.unassign_representation_item_style", icon="X", text="")
|
row.operator("bim.unassign_representation_item_style", icon="X", text="")
|
||||||
|
|
||||||
row = self.layout.row()
|
# Presentation layer.
|
||||||
row.label(text=active_item.layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
|
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 active_item.name.endswith("FaceSet"):
|
||||||
if "UV" in active_item.tags:
|
if "UV" in active_item.tags:
|
||||||
text = "Has UV mapping"
|
text = "Has UV mapping"
|
||||||
@@ -242,6 +254,7 @@ class BIM_PT_representation_items(Panel):
|
|||||||
text = "Has no colour mapping"
|
text = "Has no colour mapping"
|
||||||
self.layout.label(text=text, icon="COLOR")
|
self.layout.label(text=text, icon="COLOR")
|
||||||
|
|
||||||
|
# Shape aspect.
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
if props.is_editing_item_shape_aspect:
|
if props.is_editing_item_shape_aspect:
|
||||||
row.prop(props, "representation_item_shape_aspect", icon="SHAPEKEY_DATA", text="")
|
row.prop(props, "representation_item_shape_aspect", icon="SHAPEKEY_DATA", text="")
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def refresh():
|
def refresh():
|
||||||
@@ -31,12 +32,31 @@ class LayersData:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
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
|
cls.is_loaded = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def total_layers(cls) -> int:
|
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
|
@classmethod
|
||||||
def active_layers(cls) -> dict[int, str]:
|
def active_layers(cls) -> dict[int, str]:
|
||||||
|
|||||||
Reference in New Issue
Block a user