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:
Gorgious56
2021-08-03 01:07:19 +02:00
committed by GitHub
parent 557f8e6d69
commit 7c0405fab0
4 changed files with 148 additions and 115 deletions
@@ -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")