Modeling dimensions now have sensible defaults depending on units and show prior to element creation

This commit is contained in:
Dion Moult
2022-10-15 11:06:09 +11:00
parent 28aab490df
commit b90ab70658
9 changed files with 227 additions and 122 deletions
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -39,6 +39,7 @@ class BimTool(WorkSpaceTool):
bl_keymap = (
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
#("bim.sync_modeling", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
("bim.hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
@@ -56,12 +57,18 @@ class BimTool(WorkSpaceTool):
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
)
def draw_settings(context, layout, something):
props = context.scene.BIMModelProperties
is_tool_header = context.region.type == "TOOL_HEADER"
is_sidebar = context.region.type == "UI"
def draw_settings(context, layout, ws_tool):
# Unlike operators, Blender doesn't treat workspace tools as a class, so we'll create our own.
BimToolUI.draw(context, layout)
row = layout.row(align=True)
class BimToolUI:
@classmethod
def draw(cls, context, layout):
cls.layout = layout
cls.props = context.scene.BIMModelProperties
row = cls.layout.row(align=True)
if not tool.Ifc.get():
row.label(text="No IFC Project", icon="ERROR")
return
@@ -69,121 +76,87 @@ class BimTool(WorkSpaceTool):
if not AuthoringData.is_loaded:
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 and not is_tool_header:
#if ifc_classes and relating_types_ids and not cls.props.icon_id and not is_tool_header:
# # 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_ifc_class(props, "lost_context"))
# bpy.app.timers.register(lambda: prop.update_relating_type(props, "lost_context"))
# bpy.app.timers.register(lambda: prop.update_ifc_class(cls.props, "lost_context"))
# bpy.app.timers.register(lambda: prop.update_relating_type(cls.props, "lost_context"))
ifc_class = props.ifc_class
relating_type_id = props.relating_type_id
if context.region.type == "TOOL_HEADER":
cls.draw_header_interface()
elif context.region.type == "UI":
cls.draw_sidebar_interface()
if is_tool_header:
row = layout.row(align=True)
if ifc_classes:
row.label(text="", icon="FILE_VOLUME")
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")
prop_with_search(row, props, "relating_type_id", text="")
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", 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="")
if context.active_object:
cls.draw_edit_object_interface(context)
elif not context.selected_objects:
cls.draw_create_object_interface()
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
op.ifc_class = ifc_class
if relating_type_id.isnumeric():
op.relating_type_id = int(relating_type_id)
elif is_sidebar:
row = layout.row(align=True)
if ifc_classes:
row.label(text="", icon="FILE_VOLUME")
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")
@classmethod
def draw_create_object_interface(cls):
if not AuthoringData.data["relating_types_ids"]:
return
if cls.props.ifc_class == "IfcWallType":
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="extrusion_depth", text="Height")
box = layout.box()
if AuthoringData.data["type_thumbnail"]:
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
else:
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
op.ifc_class = props.ifc_class
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="length", text="Length")
elif cls.props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="cardinal_point", text="Axis")
if ifc_classes:
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
op.ifc_class = ifc_class
if relating_type_id.isnumeric():
op.relating_type_id = int(relating_type_id)
row = cls.layout.row(align=True)
label = "Height" if cls.props.ifc_class == "IfcColumn" else "Length"
row.prop(data=cls.props, property="extrusion_depth", text=label)
elif cls.props.ifc_class in ("IfcWindowType", "IfcWindowStyle", "IfcDoorType", "IfcDoorStyle"):
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="rl", text="RL")
@classmethod
def draw_edit_object_interface(cls, context):
if AuthoringData.data["active_class"] in ("IfcWall", "IfcWallStandardCase"):
row = layout.row(align=True)
row.prop(data=props, property="extrusion_depth", text="Height")
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="extrusion_depth", text="Height")
op = row.operator("bim.change_extrusion_depth", icon="FILE_REFRESH", text="")
op.depth = props.extrusion_depth
op.depth = cls.props.extrusion_depth
row = layout.row(align=True)
row.prop(data=props, property="length", text="Length")
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="length", text="Length")
op = row.operator("bim.change_layer_length", icon="FILE_REFRESH", text="")
op.length = props.length
op.length = cls.props.length
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_T")
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Y")
row.operator("bim.hotkey", text="Mitre").hotkey = "S_Y"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_M")
row.operator("bim.hotkey", text="Merge").hotkey = "S_M"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_F")
row.operator("bim.hotkey", text="Flip").hotkey = "S_F"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_S")
row.operator("bim.hotkey", text="Split").hotkey = "S_S"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
row.operator("bim.hotkey", text="Regen").hotkey = "S_G"
row.operator("bim.join_wall", icon="X", text="").join_type = ""
elif AuthoringData.data["active_class"] in ("IfcSlab", "IfcSlabStandardCase"):
if not context.active_object:
pass
elif context.active_object.mode == "OBJECT":
row = layout.row(align=True)
if context.active_object.mode == "OBJECT":
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.hotkey", text="Edit Profile").hotkey = "S_E"
@@ -195,34 +168,34 @@ class BimTool(WorkSpaceTool):
"IfcMember",
"IfcMemberStandardCase",
):
row = layout.row(align=True)
row.prop(data=props, property="cardinal_point", text="Axis")
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="cardinal_point", text="Axis")
op = row.operator("bim.change_cardinal_point", icon="FILE_REFRESH", text="")
op.cardinal_point = int(props.cardinal_point)
op.cardinal_point = int(cls.props.cardinal_point)
row = layout.row(align=True)
label = "Height" if ifc_class == "IfcColumnType" else "Length"
row.prop(data=props, property="extrusion_depth", text=label)
row = cls.layout.row(align=True)
label = "Height" if AuthoringData.data["active_class"] in ("IfcColumn", "IfcColumnStandardCase") else "Length"
row.prop(data=cls.props, property="extrusion_depth", text=label)
op = row.operator("bim.change_profile_depth", icon="FILE_REFRESH", text="")
op.depth = props.extrusion_depth
op.depth = cls.props.extrusion_depth
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_T")
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Y")
row.operator("bim.hotkey", text="Mitre").hotkey = "S_Y"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_R")
row.operator("bim.hotkey", text="Rotate 90").hotkey = "S_R"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
row.operator("bim.hotkey", text="Regen").hotkey = "S_G"
@@ -233,25 +206,24 @@ class BimTool(WorkSpaceTool):
"IfcDoor",
"IfcDoorStandardCase",
):
row = layout.row(align=True)
row.prop(data=props, property="rl", text="RL")
row = cls.layout.row(align=True)
row.prop(data=cls.props, property="rl", text="RL")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G")
row.operator("bim.hotkey", text="Regen").hotkey = "S_G"
if props.ifc_class in (
elif AuthoringData.data["active_class"] in (
"IfcCableCarrierSegmentType",
"IfcCableSegmentType",
"IfcDuctSegmentType",
"IfcPipeSegmentType",
):
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O")
if len(context.selected_objects) == 2:
@@ -265,30 +237,90 @@ class BimTool(WorkSpaceTool):
else:
row.operator("bim.show_openings", icon="HIDE_OFF", text="")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="Align")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Exterior", icon="EVENT_X")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Centerline", icon="EVENT_C")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Interior", icon="EVENT_V")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="Mode")
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_ALT")
row.label(text="", icon="EVENT_O")
row.operator("bim.hotkey", text="Void").hotkey = "A_O"
row = layout.row(align=True)
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_ALT")
row.label(text="", icon="EVENT_D")
row.operator("bim.hotkey", text="Decomposition").hotkey = "A_D"
@classmethod
def draw_header_interface(cls):
row = cls.layout.row(align=True)
if AuthoringData.data["ifc_classes"]:
row.label(text="", icon="FILE_VOLUME")
row.prop(data=cls.props, property="ifc_class", text="")
else:
row.label(text="No Construction Class", icon="FILE_VOLUME")
row = cls.layout.row(align=True)
if AuthoringData.data["relating_types_ids"]:
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
if AuthoringData.data["ifc_classes"]:
#row = cls.layout.row()
#row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
op.ifc_class = cls.props.ifc_class
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
@classmethod
def draw_sidebar_interface(cls):
row = cls.layout.row(align=True)
if AuthoringData.data["ifc_classes"]:
row.label(text="", icon="FILE_VOLUME")
prop_with_search(row, cls.props, "ifc_class", text="")
else:
row.label(text="No Construction Class", icon="FILE_VOLUME")
row = cls.layout.row(align=True)
if AuthoringData.data["relating_types_ids"]:
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
box = cls.layout.box()
if AuthoringData.data["type_thumbnail"]:
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
else:
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
op.ifc_class = cls.props.ifc_class
if AuthoringData.data["ifc_classes"]:
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
op = row.operator("bim.add_constr_type_instance", text="Add")
op.from_invoke = True
op.ifc_class = cls.props.ifc_class
if cls.props.relating_type_id.isnumeric():
op.relating_type_id = int(cls.props.relating_type_id)
class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.hotkey"
@@ -56,10 +56,6 @@ class CreateProject(bpy.types.Operator):
props = context.scene.BIMProjectProperties
template = None if props.template_file == "0" else props.template_file
core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template)
# Load thumbnails for the first time
if tool.Ifc.get().by_type("IfcElementType"):
ifc_class = sorted(tool.Ifc.get().by_type("IfcElementType"), key=lambda e: e.is_a())[0].is_a()
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
def rollback(self, data):
IfcStore.file = None
@@ -596,10 +592,9 @@ class LoadProjectElements(bpy.types.Operator):
print("Import finished in {:.2f} seconds".format(time.time() - start))
context.scene.BIMProjectProperties.is_loading = False
# Load thumbnails for the first time
if tool.Ifc.get().by_type("IfcElementType"):
ifc_class = sorted(tool.Ifc.get().by_type("IfcElementType"), key=lambda e: e.is_a())[0].is_a()
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
tool.Project.load_default_thumbnails()
tool.Project.set_default_context()
tool.Project.set_default_modeling_dimensions()
return {"FINISHED"}
def get_decomposition_elements(self):
@@ -74,3 +74,7 @@ def create_project(ifc, project, schema=None, template=None):
if template:
project.append_all_types_from_template(template)
project.load_default_thumbnails()
project.set_default_context()
project.set_default_modeling_dimensions()
+3
View File
@@ -379,6 +379,7 @@ class Owner:
class Project:
def append_all_types_from_template(cls, template): pass
def create_empty(cls, name): pass
def load_default_thumbnails(cls): pass
def run_aggregate_assign_object(cls, relating_obj=None, related_obj=None): pass
def run_context_add_context(cls, context_type=None, context_identifier=None, target_view=None, parent=None): pass
def run_owner_add_organisation(cls): pass
@@ -389,6 +390,8 @@ class Project:
def run_unit_assign_scene_units(cls): pass
def set_active_spatial_element(cls, obj): pass
def set_context(cls, context): pass
def set_default_context(cls): pass
def set_default_modeling_dimensions(cls): pass
@interface
+25
View File
@@ -18,6 +18,8 @@
import os
import bpy
import ifcopenshell
import ifcopenshell.util.unit
import blenderbim.core.tool
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -36,6 +38,12 @@ class Project(blenderbim.core.tool.Project):
def create_empty(cls, name):
return bpy.data.objects.new(name, None)
@classmethod
def load_default_thumbnails(cls):
if tool.Ifc.get().by_type("IfcElementType"):
ifc_class = sorted(tool.Ifc.get().by_type("IfcElementType"), key=lambda e: e.is_a())[0].is_a()
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
@classmethod
def run_aggregate_assign_object(cls, relating_obj=None, related_obj=None):
return blenderbim.core.aggregate.assign_object(
@@ -114,3 +122,20 @@ class Project(blenderbim.core.tool.Project):
def set_context(cls, context):
blenderbim.bim.handler.refresh_ui_data()
bpy.context.scene.BIMRootProperties.contexts = str(context.id())
@classmethod
def set_default_context(cls):
context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
if context:
bpy.context.scene.BIMRootProperties.contexts = str(context.id())
@classmethod
def set_default_modeling_dimensions(cls):
props = bpy.context.scene.BIMModelProperties
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props.extrusion_depth = 3 / unit_scale
props.length = 1 / unit_scale
props.rl = 1 / unit_scale
props.x = 0.5 / unit_scale
props.y = 0.5 / unit_scale
props.z = 0.5 / unit_scale
+12
View File
@@ -77,6 +77,10 @@ class TestCreateProject:
project.set_context("body").should_be_called()
project.set_active_spatial_element("storey").should_be_called()
project.load_default_thumbnails().should_be_called()
project.set_default_context().should_be_called()
project.set_default_modeling_dimensions().should_be_called()
subject.create_project(ifc, project, schema="IFC4", template=None)
def test_appending_project_template_types_if_specified(self, ifc, project):
@@ -132,6 +136,10 @@ class TestCreateProject:
project.append_all_types_from_template("template").should_be_called()
project.load_default_thumbnails().should_be_called()
project.set_default_context().should_be_called()
project.set_default_modeling_dimensions().should_be_called()
subject.create_project(ifc, project, schema="IFC4", template="template")
def test_create_an_ifc2x3_project_with_owner_defaults(self, ifc, project):
@@ -192,4 +200,8 @@ class TestCreateProject:
project.set_context("body").should_be_called()
project.set_active_spatial_element("storey").should_be_called()
project.load_default_thumbnails().should_be_called()
project.set_default_context().should_be_called()
project.set_default_modeling_dimensions().should_be_called()
subject.create_project(ifc, project, schema="IFC2X3", template=None)
+32
View File
@@ -42,6 +42,11 @@ class TestCreateEmpty(NewFile):
assert not bpy.data.objects.get("Foobar").data
class TestLoadDefaultThumbnails(NewFile):
def test_nothing(self):
pass # Not possible to test this headlessly
class TestRunAggregateAssignObject(NewFile):
def test_nothing(self):
pass
@@ -101,3 +106,30 @@ class TestSetContext(NewFile):
context = ifc.createIfcGeometricRepresentationContext()
subject.set_context(context)
assert bpy.context.scene.BIMRootProperties.contexts == str(context.id())
class TestSetDefaultContext(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifc.createIfcProject()
model = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
body = ifcopenshell.api.run("context.add_context", ifc, parent=model, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW")
subject.set_default_context()
assert bpy.context.scene.BIMRootProperties.contexts == str(body.id())
class TestSetDefaultModelingDimensions(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifc.createIfcProject()
ifcopenshell.api.run("unit.assign_unit", ifc)
subject.set_default_modeling_dimensions()
props = bpy.context.scene.BIMModelProperties
assert props.extrusion_depth == 3000
assert props.length == 1000
assert props.rl == 1000
assert props.x == 500
assert props.y == 500
assert props.z == 500
@@ -477,6 +477,8 @@ def calculate_unit_scale(file):
:returns: The scale factor
:rtype: float
"""
if not file.by_type("IfcUnitAssignment"):
return 1
units = file.by_type("IfcUnitAssignment")[0]
unit_scale = 1
for unit in units.Units: