Door modifier - basic setup

Also:
- refactored window modifier code - part of it is now reused building a door and can be used by other module in the future
- fixed bug with window lining intersecting frame when lining_depth < panel_depth
- simplified the way lining is built in ifc - previously it was using 4-6 extrusions, now it's 1-2 which is also better for 2D drawings
This commit is contained in:
Andrej730
2023-01-31 15:10:37 +05:00
parent a8400ec963
commit b2bdbdd8c3
8 changed files with 1251 additions and 220 deletions
@@ -33,6 +33,7 @@ from . import (
workspace,
profile,
sverchok_modifier,
door
)
classes = (
@@ -92,11 +93,13 @@ classes = (
prop.BIMStairProperties,
prop.BIMSverchokProperties,
prop.BIMWindowProperties,
prop.BIMDoorProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_array,
ui.BIM_PT_stair,
ui.BIM_PT_sverchok,
ui.BIM_PT_window,
ui.BIM_PT_door,
ui.DisplayConstrTypesUI,
ui.LaunchTypeManager,
ui.HelpConstrTypes,
@@ -125,6 +128,12 @@ classes = (
window.FinishEditingWindow,
window.EnableEditingWindow,
window.RemoveWindow,
door.BIM_OT_add_door,
door.AddDoor,
door.CancelEditingDoor,
door.FinishEditingDoor,
door.EnableEditingDoor,
door.RemoveDoor,
)
addon_keymaps = []
@@ -138,9 +147,11 @@ def register():
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
bpy.types.Object.BIMSverchokProperties = bpy.props.PointerProperty(type=prop.BIMSverchokProperties)
bpy.types.Object.BIMWindowProperties = bpy.props.PointerProperty(type=prop.BIMWindowProperties)
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
bpy.app.handlers.load_post.append(handler.load_post)
wm = bpy.context.window_manager
@@ -159,10 +170,12 @@ def unregister():
del bpy.types.Object.BIMStairProperties
del bpy.types.Object.BIMSverchokProperties
del bpy.types.Object.BIMWindowProperties
del bpy.types.Object.BIMDoorProperties
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
bpy.types.VIEW3D_MT_add.remove(ui.add_menu)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
@@ -34,6 +34,7 @@ def refresh():
StairData.is_loaded = False
SverchokData.is_loaded = False
WindowData.is_loaded = False
DoorData.is_loaded = False
class AuthoringData:
@@ -426,3 +427,23 @@ class WindowData:
if parameters:
parameters["data"] = json.loads(parameters.get("Data", "[]") or "[]")
return parameters
class DoorData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {"parameters": cls.parameters()}
@classmethod
def parameters(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
psets = ifcopenshell.util.element.get_psets(element)
parameters = psets.get("BBIM_Door", None)
if parameters:
parameters["data"] = json.loads(parameters.get("Data", "[]") or "[]")
return parameters
@@ -0,0 +1,504 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 @Andrej730
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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/>.
import bpy
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
import bmesh
from bmesh.types import BMVert
import ifcopenshell
import blenderbim
import blenderbim.tool as tool
import blenderbim.core.geometry as core
from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.window import create_bm_window_frame, create_bm_window, create_bm_box
from mathutils import Vector
from pprint import pprint
from os.path import basename, dirname
import json
import collections
V = lambda *x: Vector([float(i) for i in x])
def update_door_modifier_representation(context):
obj = context.active_object
props = obj.BIMDoorProperties
ifc_file = tool.Ifc.get()
representation_data = {
"partition_type": props.door_type,
"overall_height": props.overall_height,
"overall_width": props.overall_width,
"lining_properties": {
"LiningDepth": props.lining_depth,
"LiningThickness": props.lining_thickness,
"LiningOffset": props.lining_offset,
"LiningToPanelOffsetX": props.lining_to_panel_offset_x,
"LiningToPanelOffsetY": props.lining_to_panel_offset_y,
"TransomThickness": props.transom_thickness,
"TransomOffset": props.transom_offset,
"transomThickness": props.transom_thickness,
"TransomOffset": props.transom_offset,
"CasingThickness": props.casing_thickness,
"CasingDepth": props.casing_depth,
"ThresholdThickness": props.threshold_thickness,
"ThresholdDepth": props.threshold_depth,
"ThresholdOffset": props.threshold_offset,
},
"panel_properties": {
"PanelDepth": props.panel_depth,
"PanelWidth": props.panel_width_ratio,
"FrameDepth": props.frame_depth,
"FrameThickness": props.frame_thickness,
},
}
# ELEVATION_VIEW representation
ifc_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW")
if not ifc_context:
model_context = ifcopenshell.util.representation.get_context(ifc_file, "Model")
ifc_context = ifcopenshell.api.run(
"context.add_context",
ifc_file,
context_type="Model",
context_identifier="Profile",
target_view="ELEVATION_VIEW",
parent=model_context,
)
representation_data["context"] = ifc_context
elevation_representation = ifcopenshell.api.run(
"geometry.add_door_representation", ifc_file, **representation_data
)
replace_representation_for_object(ifc_file, ifc_context, obj, elevation_representation)
# MODEL_VIEW representation
ifc_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
representation_data["context"] = ifc_context
model_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
replace_representation_for_object(ifc_file, ifc_context, obj, model_representation)
def replace_representation_for_object(ifc_file, ifc_context, obj, new_representation):
ifc_element = tool.Ifc.get_entity(obj)
old_representation = ifcopenshell.util.representation.get_representation(
ifc_element, ifc_context.ContextType, ifc_context.ContextIdentifier, ifc_context.TargetView
)
if old_representation:
for inverse in ifc_file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
else:
ifcopenshell.api.run(
"geometry.assign_representation", ifc_file, product=ifc_element, representation=new_representation
)
core.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=new_representation,
should_reload=True,
is_global=False,
should_sync_changes_first=True,
)
def create_bm_door_lining(bm, size: Vector, thickness: Vector, position:Vector=V(0,0,0).freeze()):
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
`thickness` can be also defined just as 1 float value.
"""
if not isinstance(thickness, collections.abc.Iterable):
thickness = [thickness] * 2
th_side, th_up = thickness
width, depth, height = size
verts = [
(0, [width - th_side, 0.0, height-th_up]),
(1, [0.0, 0.0, height]),
(2, [th_side, 0.0, height-th_up]),
(3, [0.0, 0.0, 0.0]),
(4, [width - th_side, 0.0, 0.0]),
(5, [width, 0.0, height]),
(6, [th_side, 0.0, 0.0]),
(7, [width, 0.0, 0.0])
]
edges = [
(0, [5, 7]),
(1, [0, 2]),
(2, [1, 5]),
(3, [4, 0]),
(4, [2, 1]),
(5, [0, 5]),
(6, [4, 7]),
(7, [3, 1]),
(8, [3, 6]),
(9, [2, 6]),
]
faces = [
(0, [5, 0, 2, 1]),
(1, [4, 0, 5, 7]),
(2, [3, 1, 2, 6]),
]
bm.verts.index_update()
bm.edges.index_update()
bm.faces.ensure_lookup_table()
new_verts = [bm.verts.new(v[1]) for v in verts]
new_edges = [bm.edges.new([new_verts[vi] for vi in edge[1]]) for edge in edges]
new_faces = [bm.faces.new([new_verts[vi] for vi in face[1]]) for face in faces]
extruded = bmesh.ops.extrude_face_region(bm, geom=new_faces)
extrusion_vector = Vector((0, 1, 0)) * depth
translate_verts = [v for v in extruded["geom"] if isinstance(v, BMVert)]
bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts)
bmesh.ops.translate(bm, vec=position, verts=new_verts + translate_verts)
return new_verts + translate_verts
def update_door_modifier_bmesh(context):
obj = context.object
props = obj.BIMDoorProperties
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
overall_width = props.overall_width * si_conversion
overall_height = props.overall_height * si_conversion
# lining params
lining_depth = props.lining_depth * si_conversion
lining_thickness_default = props.lining_thickness * si_conversion
lining_offset = props.lining_offset * si_conversion
lining_to_panel_offset_x = props.lining_to_panel_offset_x * si_conversion
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion
transom_thickness = props.transom_thickness * si_conversion / 2
transfom_offset = props.transom_offset * si_conversion
if transom_thickness == 0:
transfom_offset = 0
window_lining_height = overall_height - transfom_offset - transom_thickness
top_lining_thickness = transom_thickness or lining_thickness_default
panel_lining_overlap_x = max(lining_thickness_default - lining_to_panel_offset_x, 0)
panel_top_lining_overlap_x = max(top_lining_thickness - lining_to_panel_offset_x, 0)
door_opening_width = (overall_width - lining_to_panel_offset_x * 2)
threshold_thickness = props.threshold_thickness * si_conversion
threshold_depth = props.threshold_depth * si_conversion
threshold_offset = props.threshold_offset * si_conversion
threshold_width = overall_width - lining_thickness_default * 2
casing_thickness = props.casing_thickness * si_conversion
casing_depth = props.casing_depth * si_conversion
# panel params
panel_depth = props.panel_depth * si_conversion
panel_width = door_opening_width * props.panel_width_ratio
frame_depth = props.frame_depth * si_conversion
frame_thickness = props.frame_thickness * si_conversion
frame_height = window_lining_height - lining_to_panel_offset_x * 2
glass_thickness = 0.01 * si_conversion
if transfom_offset:
panel_height = transfom_offset + transom_thickness - lining_to_panel_offset_x - threshold_thickness
lining_height = transfom_offset + transom_thickness
else:
panel_height = overall_height - lining_to_panel_offset_x - threshold_thickness
lining_height = overall_height
bm = bmesh.new()
# add lining
lining_size = V(overall_width, lining_depth, lining_height)
lining_thickness = [lining_thickness_default, top_lining_thickness]
lining_verts = create_bm_door_lining(bm, lining_size, lining_thickness)
# add threshold
if not threshold_thickness:
threshold_verts = []
else:
threshold_size = V(threshold_width, threshold_depth, threshold_thickness)
threshold_position = V(lining_thickness_default, threshold_offset, 0)
threshold_verts = create_bm_box(bm, threshold_size, threshold_position)
# add casings
casing_verts = []
if not lining_offset and casing_thickness:
casing_wall_overlap = max(casing_thickness - lining_thickness_default, 0)
casing_size = V(overall_width + casing_wall_overlap*2, casing_depth, overall_height+casing_wall_overlap)
casing_position = V(-casing_wall_overlap, -casing_depth, 0)
outer_casing_verts = create_bm_door_lining(bm, casing_size, casing_thickness, casing_position)
inner_casing_thickness = [
casing_thickness-panel_lining_overlap_x,
casing_thickness-panel_top_lining_overlap_x
]
inner_casing_position = V(-casing_wall_overlap, lining_depth, 0)
inner_casing_verts = create_bm_door_lining(bm, casing_size, inner_casing_thickness, inner_casing_position)
casing_verts.extend([outer_casing_verts, inner_casing_verts])
# add door panel
panel_size = V(
panel_width,
panel_depth,
panel_height
)
panel_position = V(
lining_to_panel_offset_x,
lining_to_panel_offset_y,
threshold_thickness
)
panel_verts = create_bm_box(bm, panel_size, panel_position)
# add on top window
if not transom_thickness:
window_lining_verts = []
frame_verts = []
glass_verts = []
else:
window_lining_thickness = [lining_thickness_default] * 3
window_lining_thickness.append(transom_thickness)
window_lining_size = V(
overall_width,
lining_depth,
window_lining_height
)
window_position = V(0, 0, overall_height-window_lining_height)
frame_size = V(
door_opening_width,
frame_depth,
frame_height
)
window_lining_verts, frame_verts, glass_verts = create_bm_window(
bm,
window_lining_size,
window_lining_thickness,
lining_to_panel_offset_x,
lining_to_panel_offset_y,
frame_size,
frame_thickness,
glass_thickness,
window_position
)
lining_offset_verts = lining_verts+panel_verts+window_lining_verts+frame_verts+glass_verts
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=lining_offset_verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
if bpy.context.object.mode == "EDIT":
bmesh.update_edit_mesh(obj.data)
else:
bm.to_mesh(obj.data)
bm.free()
obj.data.update()
class BIM_OT_add_door(Operator):
bl_idname = "mesh.add_door"
bl_label = "Door"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
ifc_file = tool.Ifc.get()
if not ifc_file:
self.report({"ERROR"}, "You need to start IFC project first to create a door.")
return {"CANCELLED"}
if context.object is not None:
spawn_location = context.object.location.copy()
context.object.select_set(False)
else:
spawn_location = bpy.context.scene.cursor.location.copy()
mesh = bpy.data.meshes.new("IfcDoor")
obj = bpy.data.objects.new("IfcDoor", mesh)
obj.location = spawn_location
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
element = blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj,
ifc_class="IfcDoor",
should_add_representation=False
)
bpy.ops.object.select_all(action="DESELECT")
bpy.context.view_layer.objects.active = None
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.bim.add_door()
return {"FINISHED"}
# UI operators
class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_door"
bl_label = "Add Door"
bl_options = {"REGISTER"}
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMDoorProperties
if element.is_a() not in ("IfcDoor", "IfcDoorType"):
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType type to add a stair.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.door_added_previously:
convert_property_group_from_si(props, skip_props=("is_editing", "door_type", "door_added_previously", 'convert_property_group_from_si'))
door_data = props.get_general_kwargs()
lining_props = props.get_lining_kwargs()
panel_props = props.get_panel_kwargs()
door_data["lining_properties"] = lining_props
door_data["panel_properties"] = panel_props
psets = ifcopenshell.util.element.get_psets(element)
pset = psets.get("BBIM_Door", None)
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Door")
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Data": json.dumps(door_data, default=list)},
)
update_door_modifier_representation(context)
return {"FINISHED"}
class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.cancel_editing_door"
bl_label = "Cancel editing Door"
bl_options = {"REGISTER"}
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
psets = ifcopenshell.util.element.get_psets(element)
data = json.loads(psets["BBIM_Door"]["Data"])
props = obj.BIMDoorProperties
# restore previous settings since editing was canceled
for prop_name in data:
setattr(props, prop_name, data[prop_name])
update_door_modifier_representation(context)
props.is_editing = -1
return {"FINISHED"}
class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.finish_editing_door"
bl_label = "Finish editing door"
bl_options = {"REGISTER"}
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMDoorProperties
psets = ifcopenshell.util.element.get_psets(element)
pset = psets["BBIM_Door"]
door_data = props.get_general_kwargs()
lining_props = props.get_lining_kwargs()
panel_props = props.get_panel_kwargs()
door_data["lining_properties"] = lining_props
door_data["panel_properties"] = panel_props
props.is_editing = -1
update_door_modifier_representation(context)
pset = tool.Ifc.get().by_id(pset["id"])
door_data = json.dumps(door_data, default=list)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": door_data})
return {"FINISHED"}
class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_door"
bl_label = "Enable Editing Door"
bl_options = {"REGISTER"}
def _execute(self, context):
obj = context.active_object
props = obj.BIMDoorProperties
element = tool.Ifc.get_entity(obj)
pset = ifcopenshell.util.element.get_psets(element)
data = json.loads(pset["BBIM_Door"]["Data"])
data.update(data.pop("lining_properties"))
data.update(data.pop("panel_properties"))
# required since we could load pset from .ifc and BIMDoorProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = ("is_editing", "door_type", "door_added_previously", 'convert_property_group_from_si')
skip_props += tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.is_editing = 1
return {"FINISHED"}
class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_door"
bl_label = "Remove Door"
bl_options = {"REGISTER"}
def _execute(self, context):
obj = context.active_object
props = obj.BIMDoorProperties
element = tool.Ifc.get_entity(obj)
obj.BIMDoorProperties.is_editing = -1
pset = ifcopenshell.util.element.get_psets(element)
pset = tool.Ifc.get().by_id(pset["BBIM_Door"]["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
props.door_added_previously = True
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(BIM_OT_add_door.bl_idname, icon="PLUGIN")
@@ -356,13 +356,25 @@ class BIMWindowProperties(PropertyGroup):
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050)
first_mullion_offset: bpy.props.FloatProperty(name="First Mullion Offset", default=0.3)
second_mullion_offset: bpy.props.FloatProperty(name="Second Mullion Offset", default=0.45)
first_mullion_offset: bpy.props.FloatProperty(
name="First Mullion Offset",
description="Distance from the first lining to the first mullion center",
default=0.3)
second_mullion_offset: bpy.props.FloatProperty(
name="Second Mullion Offset",
description="Distance from the first lining to the second mullion center",
default=0.45)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050)
first_transom_offset: bpy.props.FloatProperty(name="First Transom Offset", default=0.3)
second_transom_offset: bpy.props.FloatProperty(name="Second Transom Offset", default=0.6)
first_transom_offset: bpy.props.FloatProperty(
name="First Transom Offset",
description="Distance from the first lining to the first transom center",
default=0.3)
second_transom_offset: bpy.props.FloatProperty(
name="Second Transom Offset",
description="Distance from the first lining to the second transom center",
default=0.6)
# panel_properties
# panel properties
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3)
frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3)
@@ -417,3 +429,93 @@ class BIMWindowProperties(PropertyGroup):
"frame_depth": self.frame_depth,
"frame_thickness": self.frame_thickness,
}
class BIMDoorProperties(PropertyGroup):
door_types = (
("DOUBLE_SWING_LEFT", "DOUBLE_SWING_LEFT", ""),
("DOUBLE_SWING_RIGHT", "DOUBLE_SWING_RIGHT", ""),
)
door_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
door_type: bpy.props.EnumProperty(
name="Door Operation Type", items=door_types, default="DOUBLE_SWING_LEFT"
)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=1.2)
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6)
# lining properties
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.0)
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050)
transom_offset: bpy.props.FloatProperty(
name="Transom Offset",
description="Distance from the bottom door opening " \
"to the beginning of the transom (unlike windows)",
default=0.875)
casing_thickness: bpy.props.FloatProperty(name="Casing Thickness", default=0.075)
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005)
threshold_thickness: bpy.props.FloatProperty(name="Threshold Thickness", default=0.025)
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1)
threshold_offset: bpy.props.FloatProperty(name="Threshold Offset", default=0.025)
# panel properties
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035)
panel_width_ratio: bpy.props.FloatProperty(
name="Panel Width Ratio",
description="Width of this panel, given as ratio " \
"relative to the total clear opening width of the door",
default=1.0, soft_min=0, soft_max=1)
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035)
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035)
def get_general_kwargs(self):
return {
"door_type": self.door_type,
"overall_height": self.overall_height,
"overall_width": self.overall_width,
}
def get_lining_kwargs(self):
kwargs = {
"lining_depth": self.lining_depth,
"lining_thickness": self.lining_thickness,
"lining_offset": self.lining_offset,
"lining_to_panel_offset_x": self.lining_to_panel_offset_x,
"lining_to_panel_offset_y": self.lining_to_panel_offset_y,
}
kwargs['transom_thickness'] = self.transom_thickness
if self.transom_thickness:
kwargs['transom_offset'] = self.transom_offset
if not self.lining_offset:
kwargs['casing_thickness'] = self.casing_thickness
if self.casing_thickness:
kwargs['casing_depth'] = self.casing_depth
kwargs['threshold_thickness'] = self.threshold_thickness
if self.threshold_thickness:
kwargs['threshold_depth'] = self.threshold_depth
kwargs['threshold_offset'] = self.threshold_offset
return kwargs
def get_panel_kwargs(self):
kwargs = {
"panel_depth": self.panel_depth,
"panel_width_ratio": self.panel_width_ratio
}
if self.transom_thickness:
kwargs['frame_thickness'] = self.frame_thickness
kwargs['frame_depth'] = self.frame_depth
return kwargs
@@ -19,10 +19,11 @@
import bpy
import blenderbim.tool as tool
from bpy.types import Panel, Operator, Menu
from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData, SverchokData, WindowData
from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData, SverchokData, WindowData, DoorData
from blenderbim.bim.module.model.prop import store_cursor_position
from blenderbim.bim.module.model.stair import update_stair_modifier
from blenderbim.bim.module.model.window import update_window_modifier_bmesh
from blenderbim.bim.module.model.door import update_door_modifier_bmesh
from blenderbim.bim.helper import prop_with_search
@@ -521,6 +522,89 @@ class BIM_PT_window(bpy.types.Panel):
row.operator("bim.add_window", icon="ADD", text="")
class BIM_PT_door(bpy.types.Panel):
bl_label = "IFC Door"
bl_idname = "BIM_PT_door"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
@classmethod
def poll(cls, context):
# always display modifier if it's IFC object
return tool.Ifc.get() and tool.Ifc.get_entity(context.active_object)
def draw(self, context):
if not DoorData.is_loaded:
DoorData.load()
props = context.active_object.BIMDoorProperties
if DoorData.data["parameters"]:
row = self.layout.row(align=True)
row.label(text="Door parameters", icon="OUTLINER_OB_LATTICE")
door_data = DoorData.data["parameters"]["data"]
if props.is_editing != -1:
row = self.layout.row(align=True)
row.operator("bim.finish_editing_door", icon="CHECKMARK", text="Finish editing")
row.operator("bim.cancel_editing_door", icon="CANCEL", text="")
general_props = props.get_general_kwargs()
for prop in general_props:
self.layout.prop(props, prop)
lining_props = props.get_lining_kwargs()
self.layout.label(text="Lining properties")
for prop in lining_props:
self.layout.prop(props, prop)
panel_props = props.get_panel_kwargs()
self.layout.label(text="Panel properties")
for prop in panel_props:
self.layout.prop(props, prop)
update_door_modifier_bmesh(context)
else:
row.operator("bim.enable_editing_door", icon="GREASEPENCIL", text="")
row.operator("bim.remove_door", icon="X", text="")
box = self.layout.box()
general_props = props.get_general_kwargs()
for prop in general_props:
prop_value = door_data[prop]
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
row = box.row(align=True)
row.label(text=f"{props.bl_rna.properties[prop].name}")
row.label(text=str(prop_value))
lining_props = props.get_lining_kwargs()
self.layout.label(text="Lining properties")
lining_box = self.layout.box()
for prop in lining_props:
prop_value = door_data["lining_properties"][prop]
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
row = lining_box.row(align=True)
row.label(text=f"{props.bl_rna.properties[prop].name}")
row.label(text=str(prop_value))
panel_props = props.get_panel_kwargs()
self.layout.label(text="Panel properties")
panel_box = self.layout.box()
for prop in panel_props:
prop_value = door_data["panel_properties"][prop]
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
row = panel_box.row(align=True)
row.label(text=f"{props.bl_rna.properties[prop].name}")
row.label(text=str(prop_value))
else:
row = self.layout.row()
row.label(text="No Door Found")
row.operator("bim.add_door", icon="ADD", text="")
class BIM_MT_model(Menu):
bl_idname = "BIM_MT_model"
bl_label = "IFC Objects"
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>, @Andrej730
# Copyright (C) 2023 @Andrej730
#
# This file is part of BlenderBIM Add-on.
#
@@ -125,10 +125,11 @@ def replace_representation_for_object(ifc_file, ifc_context, obj, new_representa
)
def create_bm_window_closed_profile(bm, size: Vector, thickness: Vector, position: Vector):
"""thickness of the profile is defined as list in the following order: (LEFT, TOP, RIGHT, BOTTOM)
def create_bm_window_frame(bm, size: Vector, thickness: Vector, position: Vector = V(0,0,0)):
"""`thickness` of the profile is defined as list in the following order:
`(LEFT, TOP, RIGHT, BOTTOM)`
thickness can be also defined just as 1 float value.
`thickness` can be also defined just as 1 float value.
"""
if not isinstance(thickness, collections.abc.Iterable):
@@ -189,6 +190,52 @@ def create_bm_window_closed_profile(bm, size: Vector, thickness: Vector, positio
return new_verts + translate_verts
def create_bm_box(bm, size:Vector=V(1,1,1).freeze(), position:Vector=V(0,0,0).freeze()):
"""create a box of `size`, position box first vertex at `position`"""
box_verts = bmesh.ops.create_cube(bm, size=1)['verts']
bmesh.ops.translate(bm, vec=-box_verts[0].co, verts=box_verts)
bmesh.ops.scale(bm, vec=size, verts=box_verts)
bmesh.ops.translate(bm, vec=position, verts=box_verts)
return box_verts
def create_bm_window(bm,
lining_size: Vector,
lining_thickness,
lining_to_panel_offset_x,
lining_to_panel_offset_y_full,
frame_size,
frame_thickness,
glass_thickness,
position: Vector):
# window lining
window_lining_verts = create_bm_window_frame(bm, lining_size, lining_thickness)
# window frame
frame_position = V(
lining_to_panel_offset_x,
lining_to_panel_offset_y_full,
lining_to_panel_offset_x
)
frame_verts = create_bm_window_frame(bm, frame_size, frame_thickness, frame_position)
# window glass
glass_size = frame_size - V(frame_thickness*2, 0, frame_thickness*2)
glass_size.y = glass_thickness
glass_position = frame_position + V(
frame_thickness,
frame_size.y / 2 - glass_thickness / 2,
frame_thickness
)
glass_verts = create_bm_box(bm, glass_size, glass_position)
translated_verts = window_lining_verts + frame_verts + glass_verts
bmesh.ops.translate(bm, vec=position, verts=translated_verts)
return (window_lining_verts, frame_verts, glass_verts)
def update_window_modifier_bmesh(context):
obj = context.object
props = obj.BIMWindowProperties
@@ -201,7 +248,9 @@ def update_window_modifier_bmesh(context):
lining_depth = props.lining_depth * si_conversion
overall_height = props.overall_height * si_conversion
lining_to_panel_offset_x = props.lining_to_panel_offset_x * si_conversion
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion
lining_thickness = props.lining_thickness * si_conversion
lining_offset = props.lining_offset
mullion_thickness = props.mullion_thickness * si_conversion / 2
first_mullion_offset = props.first_mullion_offset * si_conversion
@@ -251,8 +300,8 @@ def update_window_modifier_bmesh(context):
frame_depth = props.frame_depth[panel_i] * si_conversion
frame_thickness = props.frame_thickness[panel_i] * si_conversion
# create lining
lining_size = V(
# add window
window_lining_size = V(
panel_width,
lining_depth,
panel_height,
@@ -260,59 +309,43 @@ def update_window_modifier_bmesh(context):
# calculate lining thickness
# taking into account mullions and transoms
thickness = [lining_thickness] * 4
window_lining_thickness = [lining_thickness] * 4
# mullion thickness
if unique_cols > 1:
if column_i != 0:
thickness[0] = mullion_thickness # left column
window_lining_thickness[0] = mullion_thickness # left column
if column_i != unique_cols - 1:
thickness[2] = mullion_thickness # right column
window_lining_thickness[2] = mullion_thickness # right column
# transom thickness
if unique_rows_in_col[column_i] > 1:
if row_i != 0:
thickness[3] = transom_thickness # bottom row
window_lining_thickness[3] = transom_thickness # bottom row
if row_i != unique_rows_in_col[column_i] - 1:
thickness[1] = transom_thickness # top row
window_lining_thickness[1] = transom_thickness # top row
lining_verts = create_bm_window_closed_profile(bm, lining_size, thickness, V(0, 0, 0))
# add panel
panel_size = lining_size.copy()
panel_size.y = frame_depth
panel_size = panel_size - V(lining_to_panel_offset_x * 2, 0, lining_to_panel_offset_x * 2)
panel_position = V(
props.lining_to_panel_offset_x * si_conversion,
((props.lining_depth - props.frame_depth[panel_i]) + props.lining_to_panel_offset_y) * si_conversion,
props.lining_to_panel_offset_x * si_conversion,
)
thickness = props.frame_thickness[panel_i] * si_conversion
panel_verts = create_bm_window_closed_profile(bm, panel_size, thickness, panel_position)
# add glass
glass_verts = bmesh.ops.create_cube(bm, size=1)["verts"]
bmesh.ops.translate(bm, vec=-glass_verts[0].co, verts=glass_verts)
glass_size = panel_size
glass_size.y = glass_thickness
glass_size = glass_size - V(frame_thickness * 2, 0, frame_thickness)
glass_position = panel_position + V(
frame_thickness,
frame_depth / 2 - glass_thickness / 2,
frame_size = window_lining_size.copy()
frame_size.y = frame_depth
frame_size = frame_size - V(lining_to_panel_offset_x * 2, 0, lining_to_panel_offset_x * 2)
window_position = V(accumulated_width, 0, accumulated_height[column_i])
lining_verts, panel_verts, glass_verts = create_bm_window(
bm,
window_lining_size,
window_lining_thickness,
lining_to_panel_offset_x,
(lining_depth - frame_depth) + lining_to_panel_offset_y,
frame_size,
frame_thickness,
glass_thickness,
window_position
)
bmesh.ops.scale(bm, vec=glass_size, verts=glass_verts)
bmesh.ops.translate(bm, vec=glass_position, verts=glass_verts)
# translate panel
accumulated_offset = V(accumulated_width, 0, accumulated_height[column_i])
bmesh.ops.translate(bm, vec=accumulated_offset, verts=lining_verts + panel_verts + glass_verts)
built_panels.append(panel_i)
accumulated_height[column_i] += panel_height
accumulated_width += panel_width
bmesh.ops.translate(bm, vec=V(0, props.lining_offset * si_conversion, 0), verts=bm.verts)
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=bm.verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
if bpy.context.object.mode == "EDIT":