Construction Type Browser, 2nd iteration (#2334)

* Polish a bit the type browser UI

* Remove SELECT from type browser

* model and geometry module tests fixed

* Polishing of type authoring layout redesign
This commit is contained in:
Carlos Villagrasa
2022-08-04 03:04:40 +02:00
committed by GitHub
parent f9ef00fd45
commit bbb3b62b8a
5 changed files with 104 additions and 94 deletions
@@ -42,6 +42,7 @@ class AuthoringData:
cls.props = bpy.context.scene.BIMModelProperties cls.props = bpy.context.scene.BIMModelProperties
cls.load_ifc_classes() cls.load_ifc_classes()
cls.load_relating_types() cls.load_relating_types()
cls.load_relating_types_browser()
cls.load_preview_constr_types() cls.load_preview_constr_types()
@classmethod @classmethod
@@ -52,6 +53,10 @@ class AuthoringData:
def load_relating_types(cls): def load_relating_types(cls):
cls.data["relating_types_ids"] = cls.relating_types() cls.data["relating_types_ids"] = cls.relating_types()
@classmethod
def load_relating_types_browser(cls):
cls.data["relating_types_browser_ids"] = cls.relating_types_browser()
@classmethod @classmethod
def load_preview_constr_types(cls): def load_preview_constr_types(cls):
cls.data["preview_constr_types"] = preview_icon_ids cls.data["preview_constr_types"] = preview_icon_ids
@@ -88,6 +93,10 @@ class AuthoringData:
def relating_types(cls, ifc_class=None): def relating_types(cls, ifc_class=None):
return [(str(e.id()), e.Name, e.Description or "") for e in cls.constr_class_entities(ifc_class=ifc_class)] return [(str(e.id()), e.Name, e.Description or "") for e in cls.constr_class_entities(ifc_class=ifc_class)]
@classmethod
def relating_types_browser(cls):
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
@staticmethod @staticmethod
def new_relating_type_info(ifc_class): def new_relating_type_info(ifc_class):
relating_type_info = bpy.context.scene.ConstrTypeInfo.add() relating_type_info = bpy.context.scene.ConstrTypeInfo.add()
@@ -137,9 +146,9 @@ class AuthoringData:
col.objects.unlink(obj) col.objects.unlink(obj)
@classmethod @classmethod
def assetize_relating_type_from_selection(cls): def assetize_relating_type_from_selection(cls, browser=False):
ifc_class= cls.props.ifc_class ifc_class = cls.props.ifc_class_browser if browser else cls.props.ifc_class
relating_type_id = cls.props.relating_type_id relating_type_id = cls.props.relating_type_id_browser if browser else cls.props.relating_type_id
constr_class_occurrences = cls.constr_class_entities(ifc_class=ifc_class) constr_class_occurrences = cls.constr_class_entities(ifc_class=ifc_class)
constr_class_occurrences = [ constr_class_occurrences = [
entity for entity in constr_class_occurrences if entity.id() == int(relating_type_id) entity for entity in constr_class_occurrences if entity.id() == int(relating_type_id)
@@ -193,13 +193,10 @@ class DisplayConstrTypes(bpy.types.Operator):
if not AuthoringData.is_loaded: if not AuthoringData.is_loaded:
AuthoringData.load() AuthoringData.load()
props = context.scene.BIMModelProperties props = context.scene.BIMModelProperties
if props.unfold_relating_types: ifc_class = props.ifc_class
ifc_class = props.ifc_class relating_type_info = AuthoringData.relating_type_info(ifc_class)
relating_type_info = AuthoringData.relating_type_info(ifc_class) if relating_type_info is None or not relating_type_info.fully_loaded:
if relating_type_info is None or not relating_type_info.fully_loaded: AuthoringData.assetize_constr_class(ifc_class)
AuthoringData.assetize_constr_class(ifc_class)
else:
prop.update_relating_type(props, context)
bpy.ops.bim.display_constr_types_ui("INVOKE_DEFAULT") bpy.ops.bim.display_constr_types_ui("INVOKE_DEFAULT")
return {"FINISHED"} return {"FINISHED"}
@@ -33,16 +33,24 @@ def get_relating_type(self, context):
return AuthoringData.data["relating_types_ids"] return AuthoringData.data["relating_types_ids"]
def update_icon_id(self, context): def get_relating_type_browser(self, context):
ifc_class = self.ifc_class if not AuthoringData.is_loaded:
relating_type_id = self.relating_type_id AuthoringData.load()
return AuthoringData.data["relating_types_browser_ids"]
def update_icon_id(self, context, browser=False):
ifc_class = self.ifc_class_browser if browser else self.ifc_class
relating_type_id = self.relating_type_id_browser if browser else self.relating_type_id
relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id) relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id)
if ( if (
ifc_class not in AuthoringData.data["preview_constr_types"] ifc_class not in AuthoringData.data["preview_constr_types"]
or relating_type_id not in AuthoringData.data["preview_constr_types"][ifc_class] or relating_type_id not in AuthoringData.data["preview_constr_types"][ifc_class]
) and relating_type is not None: ) and relating_type is not None:
if not AuthoringData.assetize_relating_type_from_selection(): if not AuthoringData.assetize_relating_type_from_selection(browser=browser):
return return
if ifc_class not in AuthoringData.data["preview_constr_types"]:
pass
self.icon_id = AuthoringData.data["preview_constr_types"][ifc_class][relating_type_id]["icon_id"] self.icon_id = AuthoringData.data["preview_constr_types"][ifc_class][relating_type_id]["icon_id"]
@@ -51,13 +59,17 @@ def update_ifc_class(self, context):
AuthoringData.load_relating_types() AuthoringData.load_relating_types()
if self.updating: if self.updating:
return return
if self.unfold_relating_types:
ifc_class = self.ifc_class
relating_type_info = AuthoringData.relating_type_info(ifc_class) def update_ifc_class_browser(self, context):
if relating_type_info is None or not relating_type_info.fully_loaded: AuthoringData.load_ifc_classes()
AuthoringData.assetize_constr_class(ifc_class) AuthoringData.load_relating_types_browser()
else: if self.updating:
self.relating_type_id = AuthoringData.data["relating_types_ids"][0][0] return
ifc_class = self.ifc_class_browser
relating_type_info = AuthoringData.relating_type_info(ifc_class)
if relating_type_info is None or not relating_type_info.fully_loaded:
AuthoringData.assetize_constr_class(ifc_class)
def update_relating_type(self, context): def update_relating_type(self, context):
@@ -66,6 +78,12 @@ def update_relating_type(self, context):
update_icon_id(self, context) update_icon_id(self, context)
def update_relating_type_browser(self, context):
AuthoringData.load_relating_types_browser()
if not self.updating:
update_icon_id(self, context, browser=True)
def update_relating_type_by_name(self, context): def update_relating_type_by_name(self, context):
AuthoringData.load_relating_types() AuthoringData.load_relating_types()
relating_type_id = AuthoringData.relating_type_id_by_name(self.ifc_class, self.relating_type) relating_type_id = AuthoringData.relating_type_id_by_name(self.ifc_class, self.relating_type)
@@ -73,8 +91,8 @@ def update_relating_type_by_name(self, context):
self.relating_type_id = relating_type_id self.relating_type_id = relating_type_id
def update_unfold_relating_type(self, context): def update_preview_multiple(self, context):
if self.unfold_relating_types: if self.preview_multiple_constr_types:
ifc_class = self.ifc_class ifc_class = self.ifc_class
relating_type_info = AuthoringData.relating_type_info(ifc_class) relating_type_info = AuthoringData.relating_type_info(ifc_class)
if relating_type_info is None or not relating_type_info.fully_loaded: if relating_type_info is None or not relating_type_info.fully_loaded:
@@ -85,12 +103,17 @@ def update_unfold_relating_type(self, context):
class BIMModelProperties(PropertyGroup): class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class) ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
ifc_class_browser: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class",
update=update_ifc_class_browser)
relating_type: bpy.props.StringProperty(update=update_relating_type_by_name) relating_type: bpy.props.StringProperty(update=update_relating_type_by_name)
relating_type_id: bpy.props.EnumProperty( relating_type_id: bpy.props.EnumProperty(
items=get_relating_type, name="Construction Type", update=update_relating_type items=get_relating_type, name="Construction Type", update=update_relating_type
) )
relating_type_id_browser: bpy.props.EnumProperty(
items=get_relating_type_browser, name="Construction Type", update=update_relating_type_browser
)
icon_id: bpy.props.IntProperty() icon_id: bpy.props.IntProperty()
unfold_relating_types: bpy.props.BoolProperty(update=update_unfold_relating_type) preview_multiple_constr_types: bpy.props.BoolProperty(default=False, update=update_preview_multiple)
updating: bpy.props.BoolProperty(default=False) updating: bpy.props.BoolProperty(default=False)
occurrence_name_style: bpy.props.EnumProperty( occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")], items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import math
from bpy.types import Panel, Operator from bpy.types import Panel, Operator
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.helper import prop_with_search, close_operator_panel from blenderbim.bim.helper import prop_with_search, close_operator_panel
@@ -54,34 +53,19 @@ class DisplayConstrTypesUI(Operator):
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_popup(self) return context.window_manager.invoke_popup(self, width=550)
def draw(self, context): def draw(self, context):
props = context.scene.BIMModelProperties props = context.scene.BIMModelProperties
self.draw_header(props)
if props.unfold_relating_types:
self.draw_by_ifc_class(props)
else:
self.draw_by_ifc_class_and_type(props)
self.draw_footer(props)
def draw_header(self, props):
if AuthoringData.data["ifc_classes"]: if AuthoringData.data["ifc_classes"]:
row = self.layout.row() row = self.layout.row()
row.label(text="", icon="FILE_VOLUME") row.label(text="", icon="FILE_VOLUME")
prop_with_search(row, props, "ifc_class", text="") prop_with_search(row, props, "ifc_class_browser", text="")
ifc_class = props.ifc_class_browser
def draw_footer(self, props):
row = self.layout.row()
row.prop(data=props, property="unfold_relating_types", text="Show more")
row.operator("bim.help_relating_types", text="", icon="QUESTION")
def draw_by_ifc_class(self, props):
ifc_class = props.ifc_class
num_cols = 3 num_cols = 3
self.layout.row().separator(factor=0.25) self.layout.row().separator(factor=0.25)
flow = self.layout.grid_flow(row_major=True, columns=num_cols, even_columns=True, even_rows=True, align=True) flow = self.layout.grid_flow(row_major=True, columns=num_cols, even_columns=True, even_rows=True, align=True)
relating_types = AuthoringData.relating_types() relating_types = AuthoringData.relating_types_browser()
num_types = len(relating_types) num_types = len(relating_types)
for idx, (relating_type_id, name, desc) in enumerate(relating_types): for idx, (relating_type_id, name, desc) in enumerate(relating_types):
outer_col = flow.column() outer_col = flow.column()
@@ -102,27 +86,13 @@ class DisplayConstrTypesUI(Operator):
op.ifc_class = ifc_class op.ifc_class = ifc_class
if relating_type_id.isnumeric(): if relating_type_id.isnumeric():
op.relating_type_id = int(relating_type_id) op.relating_type_id = int(relating_type_id)
factor = 2 if idx + 1 < math.ceil(num_types / num_cols) else 1.5
last_row_cols = num_types % num_cols last_row_cols = num_types % num_cols
if last_row_cols != 0: if last_row_cols != 0:
for _ in range(num_cols - last_row_cols): for _ in range(num_cols - last_row_cols):
flow.column() flow.column()
def draw_by_ifc_class_and_type(self, props):
ifc_class = props.ifc_class
relating_type_id = props.relating_type_id
if AuthoringData.data["relating_types_ids"]:
row = self.layout.row()
row.label(text="", icon="FILE_3D")
prop_with_search(row, props, "relating_type_id", text="")
box = self.layout.box()
box.template_icon(icon_value=props.icon_id, scale=7)
row = self.layout.row() row = self.layout.row()
op = row.operator("bim.add_constr_type_instance", icon="ADD") row.alignment = "RIGHT"
op.from_invoke = True row.operator("bim.help_relating_types", text="", icon="QUESTION")
op.ifc_class = ifc_class
if relating_type_id.isnumeric():
op.relating_type_id = int(relating_type_id)
class HelpConstrTypes(Operator): class HelpConstrTypes(Operator):
@@ -23,6 +23,7 @@ from blenderbim.bim.helper import prop_with_search, close_operator_panel
from bpy.types import WorkSpaceTool from bpy.types import WorkSpaceTool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model import prop
class BimTool(WorkSpaceTool): class BimTool(WorkSpaceTool):
@@ -53,70 +54,80 @@ class BimTool(WorkSpaceTool):
) )
def draw_settings(context, layout, tool): def draw_settings(context, layout, tool):
if not AuthoringData.is_loaded and IfcStore.get_file():
AuthoringData.load()
props = context.scene.BIMModelProperties props = context.scene.BIMModelProperties
is_tool_header = context.region.type == "TOOL_HEADER" is_tool_header = context.region.type == "TOOL_HEADER"
is_sidebar = context.region.type == "UI"
row = layout.row(align=True) row = layout.row(align=True)
if not IfcStore.get_file(): if not IfcStore.get_file():
row.label(text="No IFC Project", icon="ERROR") row.label(text="No IFC Project", icon="ERROR")
return return
ifc_classes = AuthoringData.data["ifc_classes"] if not AuthoringData.is_loaded:
relating_types_ids = AuthoringData.data["relating_types_ids"] AuthoringData.load()
ifc_classes = AuthoringData.data[
"ifc_classes"] if "ifc_classes" in AuthoringData.data else False
relating_types_ids = AuthoringData.data[
"relating_types_ids"] if "relating_types_ids" in AuthoringData.data else False
if ifc_classes and relating_types_ids and not props.icon_id:
# hack Dion won't like to show a preview also on the first time the sidebar is shown
bpy.app.timers.register(lambda: prop.update_relating_type(props, context))
ifc_class = props.ifc_class ifc_class = props.ifc_class
relating_type_id = props.relating_type_id relating_type_id = props.relating_type_id
relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id)
if is_tool_header: if is_tool_header:
row = layout.row(align=True)
if ifc_classes: if ifc_classes:
row.label(text="", icon="FILE_VOLUME") row.label(text="", icon="FILE_VOLUME")
row.label(text=ifc_class) row.prop(data=props, property="ifc_class", text="")
else:
row.label(text="No Construction Class", icon="FILE_VOLUME")
row = layout.row(align=True)
if relating_types_ids:
row.label(text="", icon="FILE_3D") row.label(text="", icon="FILE_3D")
row.label(text=f"{relating_type} ") row.prop(data=props, property="relating_type_id", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
if ifc_classes:
row = layout.row()
row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="") row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="")
row.operator("bim.display_constr_types", icon="VIEW_ORTHO", text="")
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A") row.label(text="", icon="EVENT_A")
row.label(text=f" Add") row.label(text=f" Add")
else: else:
txt_ifc_class = ifc_class if ifc_classes else "No Construction Class"
txt_relating_type = relating_type if relating_types_ids else "No Construction Type"
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=txt_ifc_class, icon="FILE_VOLUME") if ifc_classes:
row = layout.row(align=True)
row.label(text=txt_relating_type, icon="FILE_3D")
if AuthoringData.data["ifc_classes"]:
row = layout.row()
row.label(text="", icon="FILE_VOLUME") row.label(text="", icon="FILE_VOLUME")
prop_with_search(row, props, "ifc_class", text="") prop_with_search(row, props, "ifc_class", text="")
else:
row.label(text="No Construction Class", icon="FILE_VOLUME")
row = layout.row(align=True)
if relating_types_ids:
row.label(text="", icon="FILE_3D")
prop_with_search(row, props, "relating_type_id", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
if is_sidebar and ifc_classes and relating_types_ids:
box = layout.box()
box.template_icon(icon_value=props.icon_id, scale=8)
row = layout.row()
op = row.operator("bim.add_constr_type_instance", icon="ADD")
op.from_invoke = True
op.ifc_class = ifc_class
if relating_type_id.isnumeric():
op.relating_type_id = int(relating_type_id)
if ifc_classes:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A") row.label(text="", icon="EVENT_A")
row.label(text=f" Add Type Instance") row.label(text=f" Add Type Instance")
ifc_class = props.ifc_class if ifc_classes:
relating_type_id = props.relating_type_id
if AuthoringData.data["relating_types_ids"]:
row = layout.row()
row.label(text="", icon="FILE_3D")
prop_with_search(row, props, "relating_type_id", text="")
box = layout.box()
box.template_icon(icon_value=props.icon_id, scale=7)
row = layout.row()
op = row.operator("bim.add_constr_type_instance", icon="ADD")
op.from_invoke = True
op.ifc_class = ifc_class
if relating_type_id.isnumeric():
op.relating_type_id = int(relating_type_id)
if AuthoringData.data["ifc_classes"]:
if ifc_class == "IfcWallType": if ifc_class == "IfcWallType":
row = layout.row() row = layout.row()
row.label(text="Join") row.label(text="Join")