mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Improve UI for multi input string fields (#1617)
* Add utility operator for CollectionProperty * Support Multi input string fields * Simplify people & organisation UI code * General-purpose code cleanup * Use more compact and user friendly UI
This commit is contained in:
@@ -2,6 +2,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddOrRemoveElementFromCollection,
|
||||
operator.EnableEditingPerson,
|
||||
operator.DisableEditingPerson,
|
||||
operator.AddPerson,
|
||||
|
||||
@@ -1,10 +1,49 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.owner.data import Data
|
||||
|
||||
|
||||
def flatten_collection(collection):
|
||||
return [v.name for v in collection] if collection else None
|
||||
|
||||
|
||||
def populate_collection(collection, collection_data):
|
||||
collection.clear()
|
||||
if collection_data:
|
||||
for value in collection_data:
|
||||
collection.add().name = value
|
||||
else:
|
||||
collection.add()
|
||||
|
||||
|
||||
class AddOrRemoveElementFromCollection(bpy.types.Operator):
|
||||
bl_idname = "bim.add_or_remove_element_from_collection"
|
||||
bl_label = "Add or Remove Element From Collection"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
operation : bpy.props.EnumProperty(
|
||||
items=(
|
||||
("+", 'Add', "Add item to collection"),
|
||||
("-", 'Remove', "Remove item from collection")
|
||||
),
|
||||
default="+",
|
||||
)
|
||||
collection_path : bpy.props.StringProperty()
|
||||
selected_item_idx : bpy.props.IntProperty(default=-1)
|
||||
|
||||
def execute(self, context):
|
||||
# Ugly but I hate using eval()
|
||||
collection = context.scene
|
||||
for attr in self.collection_path.split("."):
|
||||
if hasattr(collection, attr):
|
||||
collection = getattr(collection, attr)
|
||||
if self.operation == "+" and hasattr(collection, "add"):
|
||||
collection.add()
|
||||
elif hasattr(collection, "remove") and 0 <= self.selected_item_idx < len(collection):
|
||||
collection.remove(self.selected_item_idx)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingPerson(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_person"
|
||||
bl_label = "Enable Editing Person"
|
||||
@@ -17,12 +56,13 @@ class EnableEditingPerson(bpy.types.Operator):
|
||||
props.active_person_id = self.person_id
|
||||
data = Data.people[self.person_id]
|
||||
name = data["Id"] if self.file.schema == "IFC2X3" else data["Identification"]
|
||||
props.person.name = name or ""
|
||||
props.person.family_name = data["FamilyName"] or ""
|
||||
props.person.given_name = data["GivenName"] or ""
|
||||
props.person.middle_names = json.dumps(data["MiddleNames"]) if data["MiddleNames"] else ""
|
||||
props.person.prefix_titles = json.dumps(data["PrefixTitles"]) if data["PrefixTitles"] else ""
|
||||
props.person.suffix_titles = json.dumps(data["SuffixTitles"]) if data["SuffixTitles"] else ""
|
||||
person = props.person
|
||||
person.name = name or ""
|
||||
person.family_name = data["FamilyName"] or ""
|
||||
person.given_name = data["GivenName"] or ""
|
||||
populate_collection(person.middle_names, data.get("MiddleNames", None))
|
||||
populate_collection(person.prefix_titles, data.get("PrefixTitles", None))
|
||||
populate_collection(person.suffix_titles, data.get("SuffixTitles", None))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -61,13 +101,14 @@ class EditPerson(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMOwnerProperties
|
||||
person = props.person
|
||||
attributes = {
|
||||
"Identification": props.person.name or None,
|
||||
"FamilyName": props.person.family_name or None,
|
||||
"GivenName": props.person.given_name or None,
|
||||
"MiddleNames": json.loads(props.person.middle_names) if props.person.middle_names else None,
|
||||
"PrefixTitles": json.loads(props.person.prefix_titles) if props.person.prefix_titles else None,
|
||||
"SuffixTitles": json.loads(props.person.suffix_titles) if props.person.suffix_titles else None,
|
||||
"Identification": person.name or None,
|
||||
"FamilyName": person.family_name or None,
|
||||
"GivenName": person.given_name or None,
|
||||
"MiddleNames": flatten_collection(person.middle_names),
|
||||
"PrefixTitles": flatten_collection(person.prefix_titles),
|
||||
"SuffixTitles": flatten_collection(person.suffix_titles),
|
||||
}
|
||||
if self.file.schema == "IFC2X3":
|
||||
attributes["Id"] = attributes["Identification"]
|
||||
@@ -215,29 +256,28 @@ class EnableEditingAddress(bpy.types.Operator):
|
||||
props = context.scene.BIMOwnerProperties
|
||||
props.active_address_id = self.address_id
|
||||
data = Data.addresses[self.address_id]
|
||||
props.address.name = data["type"]
|
||||
props.address.purpose = data["Purpose"] or "None"
|
||||
props.address.description = data["Description"] or ""
|
||||
props.address.user_defined_purpose = data["UserDefinedPurpose"] or ""
|
||||
address = props.address
|
||||
address.name = data["type"]
|
||||
address.purpose = data["Purpose"] or "None"
|
||||
address.description = data["Description"] or ""
|
||||
address.user_defined_purpose = data["UserDefinedPurpose"] or ""
|
||||
|
||||
if data["type"] == "IfcTelecomAddress":
|
||||
props.address.telephone_numbers = json.dumps(data["TelephoneNumbers"]) if data["TelephoneNumbers"] else ""
|
||||
props.address.facsimile_numbers = json.dumps(data["FacsimileNumbers"]) if data["FacsimileNumbers"] else ""
|
||||
props.address.pager_number = data["PagerNumber"] or ""
|
||||
props.address.electronic_mail_addresses = (
|
||||
json.dumps(data["ElectronicMailAddresses"]) if data["ElectronicMailAddresses"] else ""
|
||||
)
|
||||
props.address.www_home_page_url = data["WWWHomePageURL"] or ""
|
||||
if data["type"] == "IfcTelecomAddress":
|
||||
populate_collection(address.telephone_numbers, data.get("TelephoneNumbers", None))
|
||||
populate_collection(address.facsimile_numbers, data.get("FacsimileNumbers", None))
|
||||
address.pager_number = data["PagerNumber"] or ""
|
||||
populate_collection(address.electronic_mail_addresses, data.get("ElectronicMailAddresses", None))
|
||||
address.www_home_page_url = data["WWWHomePageURL"] or ""
|
||||
if self.file.schema != "IFC2X3":
|
||||
props.address.messaging_ids = json.dumps(data["MessagingIDs"]) if data["MessagingIDs"] else ""
|
||||
populate_collection(address.messaging_ids, data.get("MessagingIDs", None))
|
||||
elif data["type"] == "IfcPostalAddress":
|
||||
props.address.internal_location = data["InternalLocation"] or ""
|
||||
props.address.address_lines = json.dumps(data["AddressLines"]) if data["AddressLines"] else ""
|
||||
props.address.postal_box = data["PostalBox"] or ""
|
||||
props.address.town = data["Town"] or ""
|
||||
props.address.region = data["Region"] or ""
|
||||
props.address.postal_code = data["PostalCode"] or ""
|
||||
props.address.country = data["Country"] or ""
|
||||
address.internal_location = data["InternalLocation"] or ""
|
||||
populate_collection(address.address_lines, data.get("AddressLines", None))
|
||||
address.postal_box = data["PostalBox"] or ""
|
||||
address.town = data["Town"] or ""
|
||||
address.region = data["Region"] or ""
|
||||
address.postal_code = data["PostalCode"] or ""
|
||||
address.country = data["Country"] or ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -273,18 +313,12 @@ class EditAddress(bpy.types.Operator):
|
||||
if address.is_a("IfcTelecomAddress"):
|
||||
attributes.update(
|
||||
{
|
||||
"TelephoneNumbers": json.loads(props.address.telephone_numbers)
|
||||
if props.address.telephone_numbers
|
||||
else None,
|
||||
"FacsimileNumbers": json.loads(props.address.facsimile_numbers)
|
||||
if props.address.facsimile_numbers
|
||||
else None,
|
||||
"TelephoneNumbers": flatten_collection(props.address.telephone_numbers),
|
||||
"FacsimileNumbers": flatten_collection(props.address.facsimile_numbers),
|
||||
"PagerNumber": props.address.pager_number or None,
|
||||
"ElectronicMailAddresses": json.loads(props.address.electronic_mail_addresses)
|
||||
if props.address.electronic_mail_addresses
|
||||
else None,
|
||||
"ElectronicMailAddresses": flatten_collection(props.address.electronic_mail_addresses),
|
||||
"WWWHomePageURL": props.address.www_home_page_url or None,
|
||||
"MessagingIDs": json.loads(props.address.messaging_ids) if props.address.messaging_ids else None,
|
||||
"MessagingIDs": flatten_collection(props.address.messaging_ids),
|
||||
}
|
||||
)
|
||||
if self.file.schema == "IFC2X3":
|
||||
@@ -293,7 +327,7 @@ class EditAddress(bpy.types.Operator):
|
||||
attributes.update(
|
||||
{
|
||||
"InternalLocation": props.address.internal_location or None,
|
||||
"AddressLines": json.loads(props.address.address_lines) if props.address.address_lines else None,
|
||||
"AddressLines": flatten_collection(props.address.address_lines),
|
||||
"PostalBox": props.address.postal_box or None,
|
||||
"Town": props.address.town or None,
|
||||
"Region": props.address.region or None,
|
||||
|
||||
@@ -54,19 +54,19 @@ class Address(PropertyGroup):
|
||||
user_defined_purpose: StringProperty(name="Custom Purpose")
|
||||
|
||||
internal_location: StringProperty(name="Internal Location")
|
||||
address_lines: StringProperty(name="Address")
|
||||
address_lines: CollectionProperty(type=StrProperty, name="Address")
|
||||
postal_box: StringProperty(name="Postal Box")
|
||||
town: StringProperty(name="Town")
|
||||
region: StringProperty(name="Region")
|
||||
postal_code: StringProperty(name="Postal Code")
|
||||
country: StringProperty(name="Country")
|
||||
|
||||
telephone_numbers: StringProperty(name="Telephone Numbers")
|
||||
facsimile_numbers: StringProperty(name="Facsimile Numbers")
|
||||
telephone_numbers: CollectionProperty(type=StrProperty, name="Telephone Numbers")
|
||||
facsimile_numbers: CollectionProperty(type=StrProperty, name="Facsimile Numbers")
|
||||
pager_number: StringProperty(name="Pager Number")
|
||||
electronic_mail_addresses: StringProperty(name="Emails")
|
||||
www_home_page_url: StringProperty(name="Websites")
|
||||
messaging_ids: StringProperty(name="IMs")
|
||||
electronic_mail_addresses: CollectionProperty(type=StrProperty, name="Emails")
|
||||
www_home_page_url: StringProperty(name="Website")
|
||||
messaging_ids: CollectionProperty(type=StrProperty, name="IMs")
|
||||
|
||||
|
||||
class Role(PropertyGroup):
|
||||
@@ -112,9 +112,9 @@ class Person(PropertyGroup):
|
||||
name: StringProperty(name="Identification")
|
||||
family_name: StringProperty(name="Family Name")
|
||||
given_name: StringProperty(name="Given Name")
|
||||
middle_names: StringProperty(name="Middle Names")
|
||||
prefix_titles: StringProperty(name="Prefixes")
|
||||
suffix_titles: StringProperty(name="Suffixes")
|
||||
middle_names: CollectionProperty(type=StrProperty, name="Middle Names")
|
||||
prefix_titles: CollectionProperty(type=StrProperty, name="Prefixes")
|
||||
suffix_titles: CollectionProperty(type=StrProperty, name="Suffixes")
|
||||
|
||||
|
||||
class BIMOwnerProperties(PropertyGroup):
|
||||
|
||||
@@ -2,6 +2,35 @@ import bpy
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.owner.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from .operator import AddOrRemoveElementFromCollection
|
||||
|
||||
|
||||
def draw_string_collection(layout, owner, collection_name):
|
||||
column = layout.column(align=True)
|
||||
collection = getattr(owner, collection_name)
|
||||
for i in range(len(collection)):
|
||||
if i == 0:
|
||||
row = draw_prop_on_new_row(
|
||||
column,
|
||||
collection[i],
|
||||
"name",
|
||||
align=True,
|
||||
text=f"{owner.bl_rna.properties[collection_name].name}")
|
||||
add_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="ADD", text="")
|
||||
add_op.operation = "+"
|
||||
add_op.collection_path = collection.path_from_id()
|
||||
else:
|
||||
row = draw_prop_on_new_row(column, collection[i], "name", align=True, text=f"#{i + 1}")
|
||||
rem_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="REMOVE", text="")
|
||||
rem_op.operation = "-"
|
||||
rem_op.collection_path = collection.path_from_id()
|
||||
rem_op.selected_item_idx = i
|
||||
|
||||
|
||||
def draw_prop_on_new_row(layout, owner, attribute, align=False, **kwargs):
|
||||
row = layout.row(align=align)
|
||||
row.prop(owner, attribute, **kwargs)
|
||||
return row
|
||||
|
||||
|
||||
def draw_roles_ui(box, assigned_object_id, roles, context):
|
||||
@@ -14,15 +43,12 @@ def draw_roles_ui(box, assigned_object_id, roles, context):
|
||||
if props.active_role_id == role_id:
|
||||
blender_role = props.role
|
||||
box2 = box.box()
|
||||
row = box2.row(align=True)
|
||||
row.prop(blender_role, "name", icon="MOD_CLOTH", text="")
|
||||
row = draw_prop_on_new_row(box2, blender_role, "name", align=True, icon="MOD_CLOTH", text="")
|
||||
row.operator("bim.edit_role", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_role", icon="X", text="")
|
||||
if blender_role.name == "USERDEFINED":
|
||||
row = box2.row()
|
||||
row.prop(blender_role, "user_defined_role")
|
||||
row = box2.row()
|
||||
row.prop(blender_role, "description")
|
||||
draw_prop_on_new_row(box2, blender_role, "user_defined_role")
|
||||
draw_prop_on_new_row(box2, blender_role, "description")
|
||||
else:
|
||||
row = box.row(align=True)
|
||||
row.label(text=role["UserDefinedRole"] or role["Role"])
|
||||
@@ -45,45 +71,29 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file, context):
|
||||
if props.active_address_id == address_id:
|
||||
blender_address = props.address
|
||||
box2 = box.box()
|
||||
row = box2.row(align=True)
|
||||
row.prop(blender_address, "purpose", icon="MOD_CLOTH", text="")
|
||||
row = draw_prop_on_new_row(box2, blender_address, "purpose", align=True, icon="MOD_CLOTH", text="")
|
||||
row.operator("bim.edit_address", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_address", icon="X", text="")
|
||||
if blender_address.purpose == "USERDEFINED":
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "user_defined_purpose")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "description")
|
||||
draw_prop_on_new_row(box2, blender_address, "user_defined_purpose")
|
||||
draw_prop_on_new_row(box2, blender_address, "description")
|
||||
|
||||
if address["type"] == "IfcTelecomAddress":
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "telephone_numbers")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "facsimile_numbers")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "pager_number")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "electronic_mail_addresses")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "www_home_page_url")
|
||||
if file.schema != "IFC2X3":
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "messaging_ids")
|
||||
draw_string_collection(box2, blender_address, "telephone_numbers")
|
||||
draw_string_collection(box2, blender_address, "facsimile_numbers")
|
||||
draw_prop_on_new_row(box2, blender_address, "pager_number")
|
||||
draw_string_collection(box2, blender_address, "electronic_mail_addresses")
|
||||
draw_prop_on_new_row(box2, blender_address, "www_home_page_url")
|
||||
if file.schema != "IFC2X3":
|
||||
draw_string_collection(box2, blender_address, "messaging_ids")
|
||||
elif address["type"] == "IfcPostalAddress":
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "internal_location")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "address_lines")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "postal_box")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "town")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "region")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "postal_code")
|
||||
row = box2.row()
|
||||
row.prop(blender_address, "country")
|
||||
draw_prop_on_new_row(box2, blender_address, "internal_location")
|
||||
draw_string_collection(box2, blender_address, "address_lines")
|
||||
draw_prop_on_new_row(box2, blender_address, "postal_box")
|
||||
draw_prop_on_new_row(box2, blender_address, "town")
|
||||
draw_prop_on_new_row(box2, blender_address, "region")
|
||||
draw_prop_on_new_row(box2, blender_address, "postal_code")
|
||||
draw_prop_on_new_row(box2, blender_address, "country")
|
||||
else:
|
||||
row = box.row(align=True)
|
||||
row.label(text=address["type"])
|
||||
@@ -91,7 +101,6 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file, context):
|
||||
row.operator("bim.remove_address", icon="X", text="").address_id = address_id
|
||||
|
||||
|
||||
|
||||
class BIM_PT_people(Panel):
|
||||
bl_label = "IFC People"
|
||||
bl_idname = "BIM_PT_people"
|
||||
@@ -120,21 +129,14 @@ class BIM_PT_people(Panel):
|
||||
if props.active_person_id == person_id:
|
||||
blender_person = props.person
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.prop(blender_person, "name", icon="USER", text="")
|
||||
row = draw_prop_on_new_row(box, blender_person, "name", align=True, icon="USER", text="")
|
||||
row.operator("bim.edit_person", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_person", icon="X", text="")
|
||||
row = box.row()
|
||||
row.prop(blender_person, "family_name")
|
||||
row = box.row()
|
||||
row.prop(blender_person, "given_name")
|
||||
row = box.row()
|
||||
row.prop(blender_person, "middle_names")
|
||||
row = box.row()
|
||||
row.prop(blender_person, "prefix_titles")
|
||||
row = box.row()
|
||||
row.prop(blender_person, "suffix_titles")
|
||||
|
||||
draw_prop_on_new_row(box, blender_person, "family_name")
|
||||
draw_prop_on_new_row(box, blender_person, "given_name")
|
||||
draw_string_collection(box, blender_person, "middle_names")
|
||||
draw_string_collection(box, blender_person, "prefix_titles")
|
||||
draw_string_collection(box, blender_person, "suffix_titles")
|
||||
draw_roles_ui(box, person_id, person["Roles"], context)
|
||||
draw_addresses_ui(box, person_id, person["Addresses"], self.file, context)
|
||||
else:
|
||||
@@ -183,11 +185,9 @@ class BIM_PT_organisations(Panel):
|
||||
row = box.row(align=True)
|
||||
row.prop(blender_organisation, "name", icon="USER", text="")
|
||||
row.operator("bim.edit_organisation", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_organisation", icon="X", text="")
|
||||
row = box.row()
|
||||
row.prop(blender_organisation, "identification")
|
||||
row = box.row()
|
||||
row.prop(blender_organisation, "description")
|
||||
row.operator("bim.disable_editing_organisation", icon="X", text="")
|
||||
draw_prop_on_new_row(box, blender_organisation, "identification")
|
||||
draw_prop_on_new_row(box, blender_organisation, "description")
|
||||
|
||||
draw_roles_ui(box, organisation_id, organisation["Roles"], context)
|
||||
draw_addresses_ui(box, organisation_id, organisation["Addresses"], self.file, context)
|
||||
@@ -224,11 +224,9 @@ class BIM_PT_owner(Panel):
|
||||
if not Data.people:
|
||||
self.layout.label(text="No people found.")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.prop(props, "user_person")
|
||||
draw_prop_on_new_row(self.layout, props, "user_person")
|
||||
|
||||
if not Data.organisations:
|
||||
self.layout.label(text="No organisations found.")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.prop(props, "user_organisation")
|
||||
draw_prop_on_new_row(self.layout, props, "user_organisation")
|
||||
|
||||
Reference in New Issue
Block a user