mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 23:00:53 +00:00
9c39ac0a48
UI is in the addon preferences.
680 lines
26 KiB
Python
680 lines
26 KiB
Python
# Bonsai - OpenBIM Blender Add-on
|
|
# Copyright (C) 2023 @Andrej730
|
|
#
|
|
# This file is part of Bonsai.
|
|
#
|
|
# Bonsai 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.
|
|
#
|
|
# Bonsai 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
import bpy
|
|
import bmesh
|
|
import ifcopenshell
|
|
import ifcopenshell.api
|
|
import ifcopenshell.api.geometry
|
|
import ifcopenshell.api.material
|
|
import ifcopenshell.api.pset
|
|
import ifcopenshell.util.element
|
|
import ifcopenshell.util.representation
|
|
import ifcopenshell.util.schema
|
|
import ifcopenshell.util.unit
|
|
import bonsai.tool as tool
|
|
import bonsai.core.geometry
|
|
import bonsai.core.geometry as core
|
|
import bonsai.core.root
|
|
from bonsai.bim.module.model.window import create_bm_window, create_bm_box
|
|
|
|
from mathutils import Vector, Matrix
|
|
from typing import Union, Any, Optional
|
|
|
|
import json
|
|
import collections
|
|
import collections.abc
|
|
|
|
V_ = tool.Blender.V_
|
|
|
|
|
|
def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
|
props = tool.Model.get_door_props(obj)
|
|
element = tool.Ifc.get_entity(obj)
|
|
ifc_file = tool.Ifc.get()
|
|
sliding_door = "SLIDING" in props.door_type
|
|
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
|
|
|
representation_data = {
|
|
"operation_type": props.door_type,
|
|
"overall_height": props.overall_height / si_conversion,
|
|
"overall_width": props.overall_width / si_conversion,
|
|
"lining_properties": {
|
|
"LiningDepth": props.lining_depth / si_conversion,
|
|
"LiningThickness": props.lining_thickness / si_conversion,
|
|
"LiningOffset": props.lining_offset / si_conversion,
|
|
"LiningToPanelOffsetX": props.lining_to_panel_offset_x / si_conversion,
|
|
"LiningToPanelOffsetY": props.lining_to_panel_offset_y / si_conversion,
|
|
"TransomThickness": props.transom_thickness / si_conversion,
|
|
"TransomOffset": props.transom_offset / si_conversion,
|
|
"CasingThickness": props.casing_thickness / si_conversion,
|
|
"CasingDepth": props.casing_depth / si_conversion,
|
|
"ThresholdThickness": props.threshold_thickness / si_conversion,
|
|
"ThresholdDepth": props.threshold_depth / si_conversion,
|
|
"ThresholdOffset": props.threshold_offset / si_conversion,
|
|
},
|
|
"panel_properties": {
|
|
"PanelDepth": props.panel_depth / si_conversion,
|
|
"PanelWidth": props.panel_width_ratio,
|
|
"FrameDepth": props.frame_depth / si_conversion,
|
|
"FrameThickness": props.frame_thickness / si_conversion,
|
|
},
|
|
}
|
|
|
|
active_context = tool.Geometry.get_active_representation_context(obj)
|
|
|
|
# ELEVATION_VIEW representation
|
|
profile = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW")
|
|
if profile:
|
|
representation_data["context"] = profile
|
|
elevation_representation = ifcopenshell.api.geometry.add_door_representation(ifc_file, **representation_data)
|
|
tool.Model.replace_object_ifc_representation(profile, obj, elevation_representation)
|
|
|
|
# MODEL_VIEW representation
|
|
# (Model/Body defined only BEFORE Plan/Body to prevent #2744)
|
|
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
|
representation_data["context"] = body
|
|
representation_data["part_of_product"] = ifcopenshell.util.representation.get_part_of_product(element, body)
|
|
model_representation = ifcopenshell.api.geometry.add_door_representation(ifc_file, **representation_data)
|
|
representation_data["part_of_product"] = None
|
|
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
|
if fallback_material := (int(props.lining_material) or int(props.framing_material) or int(props.glazing_material)):
|
|
materials = {
|
|
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
|
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
|
}
|
|
if props.transom_thickness:
|
|
materials["Glazing"] = tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material)
|
|
ifcopenshell.api.material.set_shape_aspect_constituents(
|
|
ifc_file,
|
|
element=element,
|
|
context=body,
|
|
materials=materials,
|
|
)
|
|
elif material := ifcopenshell.util.element.get_material(element):
|
|
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
|
if not material.is_a("IfcMaterial") and not ifc_file.get_total_inverses(material):
|
|
ifcopenshell.api.material.remove_material_set(ifc_file, material=material)
|
|
|
|
# Body/PLAN_VIEW representation
|
|
plan_body = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Body", "PLAN_VIEW")
|
|
if plan_body:
|
|
representation_data["context"] = plan_body
|
|
plan_representation = ifcopenshell.api.geometry.add_door_representation(ifc_file, **representation_data)
|
|
tool.Model.replace_object_ifc_representation(plan_body, obj, plan_representation)
|
|
|
|
# Annotation/PLAN_VIEW representation
|
|
plan_annotation = ifcopenshell.util.representation.get_context(ifc_file, "Plan", "Annotation", "PLAN_VIEW")
|
|
if plan_annotation:
|
|
if not sliding_door:
|
|
# only sliding doors have Annotation/PLAN_VIEW
|
|
# for other types we just check for old representation and remove it if it's there
|
|
old_representation = ifcopenshell.util.representation.get_representation(
|
|
element, "Plan", "Annotation", "PLAN_VIEW"
|
|
)
|
|
if old_representation:
|
|
core.remove_representation(
|
|
tool.Ifc,
|
|
tool.Geometry,
|
|
obj=obj,
|
|
representation=old_representation,
|
|
)
|
|
else:
|
|
representation_data["context"] = plan_annotation
|
|
plan_representation = ifcopenshell.api.geometry.add_door_representation(ifc_file, **representation_data)
|
|
tool.Model.replace_object_ifc_representation(plan_annotation, obj, plan_representation)
|
|
|
|
bonsai.core.geometry.switch_representation(
|
|
tool.Ifc,
|
|
tool.Geometry,
|
|
obj=obj,
|
|
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
|
)
|
|
|
|
# type attributes
|
|
if tool.Ifc.get_schema() != "IFC2X3":
|
|
element.OperationType = props.door_type
|
|
|
|
# occurrences attributes
|
|
occurrences = tool.Ifc.get_all_element_occurrences(element)
|
|
for occurrence in occurrences:
|
|
occurrence.OverallWidth = props.overall_width / si_conversion
|
|
occurrence.OverallHeight = props.overall_height / si_conversion
|
|
|
|
tool.Model.update_simple_openings(element)
|
|
|
|
|
|
def bm_mirror(
|
|
bm: bmesh.types.BMesh,
|
|
verts: list[bmesh.types.BMVert],
|
|
mirror_axes: Vector = V_(1, 0, 0).freeze(),
|
|
mirror_point: Vector = V_(0, 0, 0).freeze(),
|
|
create_copy: bool = False,
|
|
) -> list[bmesh.types.BMVert]:
|
|
matrix = Matrix.Translation(mirror_point)
|
|
for i, v in enumerate(mirror_axes):
|
|
if not v:
|
|
continue
|
|
mirror_axis = V_(0, 0, 0)
|
|
mirror_axis[i] = 1.0
|
|
matrix = matrix @ Matrix.Scale(-1, 4, mirror_axis)
|
|
matrix = matrix @ Matrix.Translation(-mirror_point)
|
|
|
|
# `bmesh.ops.mirror` has no option to mirror existing geometry without creating new
|
|
# and matrix kind of work diffrently than transform so I chose `bmesh.ops.transform`
|
|
if create_copy:
|
|
faces = set()
|
|
for v in verts:
|
|
faces.update(v.link_faces)
|
|
duplicated = bmesh.ops.duplicate(bm, geom=list(faces))
|
|
verts = tool.Model.bm_sort_out_geom(duplicated["geom"])["verts"]
|
|
|
|
bmesh.ops.transform(bm, verts=verts, matrix=matrix, space=Matrix.Identity(4))
|
|
return verts
|
|
|
|
|
|
def create_bm_extruded_profile(
|
|
bm: bmesh.types.BMesh,
|
|
points: list[Vector],
|
|
edges: Optional[list[tuple[int, int]]] = None,
|
|
faces: Optional[list[list[int]]] = None,
|
|
position: Vector = V_(0, 0, 0).freeze(),
|
|
magnitude: float = 1.0,
|
|
extrusion_vector: Vector = V_(0, 0, 1).freeze(),
|
|
) -> list[bmesh.types.BMVert]:
|
|
bm.verts.index_update()
|
|
bm.edges.index_update()
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
if not edges:
|
|
last_point = len(points) - 1
|
|
edges = [(i, i + 1) for i in range(last_point)]
|
|
edges.append((last_point, 0))
|
|
|
|
new_verts = [bm.verts.new(v) for v in points]
|
|
new_edges = [bm.edges.new([new_verts[vi] for vi in edge]) for edge in edges]
|
|
|
|
if not faces:
|
|
new_faces = bmesh.ops.contextual_create(bm, geom=new_edges)["faces"]
|
|
else:
|
|
new_faces = [bm.faces.new([new_verts[vi] for vi in face]) for face in faces]
|
|
|
|
extruded = bmesh.ops.extrude_face_region(bm, geom=new_faces)
|
|
extrusion_vector = extrusion_vector * magnitude
|
|
extruded_verts = tool.Model.bm_sort_out_geom(extruded["geom"])["verts"]
|
|
bmesh.ops.translate(bm, vec=extrusion_vector, verts=extruded_verts)
|
|
|
|
bmesh.ops.translate(bm, vec=position, verts=new_verts + extruded_verts)
|
|
return new_verts + extruded_verts
|
|
|
|
|
|
def create_bm_door_lining(
|
|
bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V_(0, 0, 0).freeze()
|
|
) -> list[bmesh.types.BMVert]:
|
|
"""`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, bmesh.types.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: bpy.types.Context) -> None:
|
|
obj = context.active_object
|
|
assert obj
|
|
props = tool.Model.get_door_props(obj)
|
|
if not props.is_editing:
|
|
return
|
|
|
|
overall_width = props.overall_width
|
|
overall_height = props.overall_height
|
|
door_type = props.door_type
|
|
double_swing_door = "DOUBLE_SWING" in door_type
|
|
double_door = "DOUBLE_DOOR" in door_type
|
|
sliding_door = "SLIDING" in door_type
|
|
|
|
# lining params
|
|
lining_depth = props.lining_depth
|
|
lining_thickness_default = props.lining_thickness
|
|
lining_offset = props.lining_offset
|
|
lining_to_panel_offset_x = props.lining_to_panel_offset_x if not sliding_door else lining_thickness_default
|
|
panel_depth = props.panel_depth
|
|
lining_to_panel_offset_y = props.lining_to_panel_offset_y if not sliding_door else -panel_depth
|
|
|
|
transom_thickness = props.transom_thickness / 2
|
|
transfom_offset = props.transom_offset
|
|
if transom_thickness == 0:
|
|
transfom_offset = 0
|
|
window_lining_height = overall_height - transfom_offset - transom_thickness
|
|
|
|
side_lining_thickness = lining_thickness_default
|
|
panel_lining_overlap_x = max(lining_thickness_default - lining_to_panel_offset_x, 0) if not sliding_door else 0
|
|
|
|
top_lining_thickness = transom_thickness or lining_thickness_default
|
|
panel_top_lining_overlap_x = max(top_lining_thickness - lining_to_panel_offset_x, 0) if not sliding_door else 0
|
|
door_opening_width = overall_width - lining_to_panel_offset_x * 2
|
|
if double_swing_door:
|
|
side_lining_thickness = side_lining_thickness - panel_lining_overlap_x
|
|
top_lining_thickness = top_lining_thickness - panel_top_lining_overlap_x
|
|
|
|
threshold_thickness = props.threshold_thickness
|
|
threshold_depth = props.threshold_depth
|
|
threshold_offset = props.threshold_offset
|
|
threshold_width = overall_width - side_lining_thickness * 2
|
|
|
|
casing_thickness = props.casing_thickness
|
|
casing_depth = props.casing_depth
|
|
|
|
# panel params
|
|
panel_width = door_opening_width * props.panel_width_ratio
|
|
frame_depth = props.frame_depth
|
|
frame_thickness = props.frame_thickness
|
|
frame_height = window_lining_height - lining_to_panel_offset_x * 2
|
|
glass_thickness = 0.01
|
|
|
|
# handle dimensions (hardcoded)
|
|
handle_size = V_(120, 40, 20) * 0.001
|
|
handle_offset = V_(60, 0, 1000) * 0.001 # to the handle center
|
|
handle_center_offset = V_(handle_size.y / 2, 0, handle_size.z) / 2
|
|
|
|
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 = [side_lining_thickness, 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_(side_lining_thickness, 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)
|
|
|
|
inner_casing_thickness = [
|
|
casing_thickness - panel_lining_overlap_x,
|
|
casing_thickness - panel_top_lining_overlap_x,
|
|
]
|
|
outer_casing_thickness = inner_casing_thickness.copy() if double_swing_door else casing_thickness
|
|
|
|
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, outer_casing_thickness, casing_position)
|
|
casing_verts.extend(outer_casing_verts)
|
|
|
|
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(inner_casing_verts)
|
|
|
|
def create_bm_door_panel(
|
|
panel_size: Vector, panel_position: Vector, door_swing_type: str
|
|
) -> list[bmesh.types.BMVert]:
|
|
door_verts = []
|
|
# add door panel
|
|
door_verts.extend(create_bm_box(bm, panel_size, panel_position))
|
|
# add door handle
|
|
handle_points = [
|
|
V_(0, 0, 0),
|
|
V_(0, -handle_size.y, 0),
|
|
V_(handle_size.x, -handle_size.y, 0),
|
|
V_(handle_size.x, -handle_size.y / 2, 0),
|
|
V_(handle_size.y / 2, -handle_size.y / 2, 0),
|
|
V_(handle_size.y / 2, 0, 0),
|
|
]
|
|
handle_position = panel_position + handle_offset - handle_center_offset
|
|
door_handle_verts = create_bm_extruded_profile(
|
|
bm, handle_points, magnitude=handle_size.z, position=handle_position
|
|
)
|
|
door_verts.extend(door_handle_verts)
|
|
|
|
if door_swing_type == "LEFT":
|
|
bm_mirror(
|
|
bm,
|
|
door_handle_verts,
|
|
mirror_axes=V_(1, 0, 0),
|
|
mirror_point=panel_position + V_(panel_size.x / 2, 0, 0),
|
|
)
|
|
|
|
door_handle_mirrored_verts = bm_mirror(
|
|
bm,
|
|
door_handle_verts,
|
|
mirror_axes=V_(0, 1, 0),
|
|
mirror_point=handle_position + V_(0, panel_size.y / 2, 0),
|
|
create_copy=True,
|
|
)
|
|
door_verts.extend(door_handle_mirrored_verts)
|
|
return door_verts
|
|
|
|
door_verts = []
|
|
panel_size = V_(panel_width, panel_depth, panel_height)
|
|
panel_position = V_(lining_to_panel_offset_x, lining_to_panel_offset_y, threshold_thickness)
|
|
|
|
if double_door:
|
|
# keeping a little space between doors for readibility
|
|
double_door_offset = 0.001
|
|
panel_size.x = panel_size.x / 2 - double_door_offset
|
|
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
|
|
|
|
mirror_point = panel_position + V_(door_opening_width / 2, 0, 0)
|
|
door_verts.extend(bm_mirror(bm, door_verts, V_(1, 0, 0), mirror_point, create_copy=True))
|
|
else:
|
|
door_swing_type = "LEFT" if door_type.endswith("LEFT") else "RIGHT"
|
|
door_verts.extend(create_bm_door_panel(panel_size, panel_position, door_swing_type))
|
|
|
|
# add on top window
|
|
if not transom_thickness:
|
|
window_lining_verts = []
|
|
frame_verts = []
|
|
glass_verts = []
|
|
else:
|
|
window_lining_thickness = [
|
|
side_lining_thickness,
|
|
lining_thickness_default,
|
|
side_lining_thickness,
|
|
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 + door_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.active_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(bpy.types.Operator, tool.Ifc.Operator):
|
|
bl_idname = "mesh.add_door"
|
|
bl_label = "Add Door"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return tool.Ifc.get() and context.mode == "OBJECT"
|
|
|
|
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.active_object is not None:
|
|
spawn_location = context.active_object.location.copy()
|
|
context.active_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
|
|
|
|
element = bonsai.core.root.assign_class(
|
|
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcDoor", should_add_representation=False
|
|
)
|
|
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
|
if tool.Ifc.get_schema() != "IFC2X3":
|
|
element.PredefinedType = "DOOR"
|
|
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
bpy.context.view_layer.objects.active = None
|
|
bpy.context.view_layer.objects.active = obj
|
|
tool.Blender.select_object(obj)
|
|
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_description = "Add a Parametric Door to the Selected IFC Door Elements"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def add_door_on_object(self, obj: bpy.types.Object) -> None:
|
|
element = tool.Ifc.get_entity(obj)
|
|
assert element
|
|
props = tool.Model.get_door_props(obj)
|
|
|
|
tool.Blender.get_addon_preferences().default_parameters.door.copy_to(props)
|
|
|
|
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
|
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
|
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
|
|
|
door_data["lining_properties"] = lining_props
|
|
door_data["panel_properties"] = panel_props
|
|
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
|
|
|
if not pset:
|
|
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=element, name="BBIM_Door")
|
|
|
|
ifcopenshell.api.pset.edit_pset(
|
|
tool.Ifc.get(),
|
|
pset=pset,
|
|
properties={"Data": tool.Ifc.get().createIfcText(json.dumps(door_data, default=list))},
|
|
)
|
|
update_door_modifier_representation(obj)
|
|
|
|
def _execute(self, context):
|
|
for obj in tool.Blender.get_selected_objects():
|
|
if not tool.Blender.Modifier.is_eligible_for_door_modifier(obj):
|
|
continue
|
|
self.add_door_on_object(obj)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
|
bl_idname = "bim.cancel_editing_door"
|
|
bl_label = "Cancel Editing Door on Selected Objects"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def cancel_editing_door_on_object(self, obj: bpy.types.Object) -> None:
|
|
element = tool.Ifc.get_entity(obj)
|
|
assert element
|
|
if not tool.Blender.Modifier.is_door(element):
|
|
return
|
|
props = tool.Model.get_door_props(obj)
|
|
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
|
data.update(data.pop("lining_properties"))
|
|
data.update(data.pop("panel_properties"))
|
|
|
|
# restore previous settings since editing was canceled
|
|
props.set_props_kwargs_from_ifc_data(data)
|
|
|
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
|
bonsai.core.geometry.switch_representation(
|
|
tool.Ifc,
|
|
tool.Geometry,
|
|
obj=obj,
|
|
representation=body,
|
|
)
|
|
|
|
props.is_editing = False
|
|
|
|
def _execute(self, context):
|
|
for obj in tool.Blender.get_selected_objects():
|
|
self.cancel_editing_door_on_object(obj)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
|
bl_idname = "bim.finish_editing_door"
|
|
bl_label = "Finish Editing Door on Selected Objects"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def finish_editing_door_on_object(self, obj):
|
|
element = tool.Ifc.get_entity(obj)
|
|
assert element
|
|
if not tool.Blender.Modifier.is_door(element):
|
|
return
|
|
props = tool.Model.get_door_props(obj)
|
|
|
|
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
|
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
|
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
|
|
|
door_data["lining_properties"] = lining_props
|
|
door_data["panel_properties"] = panel_props
|
|
|
|
props.is_editing = False
|
|
|
|
update_door_modifier_representation(obj)
|
|
element_type = ifcopenshell.util.element.get_type(element)
|
|
if element_type:
|
|
tool.Model.mark_thumbnail_for_update(element_type)
|
|
|
|
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
|
door_data = tool.Ifc.get().createIfcText(json.dumps(door_data, default=list))
|
|
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": door_data})
|
|
|
|
def _execute(self, context):
|
|
for obj in tool.Blender.get_selected_objects():
|
|
self.finish_editing_door_on_object(obj)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
|
bl_idname = "bim.enable_editing_door"
|
|
bl_label = "Enable Editing Door on Selected Objects"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def edit_door_on_obj(self, obj):
|
|
element = tool.Ifc.get_entity(obj)
|
|
assert element
|
|
if not tool.Blender.Modifier.is_door(element):
|
|
return
|
|
props = tool.Model.get_door_props(obj)
|
|
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
|
data.update(data.pop("lining_properties"))
|
|
data.update(data.pop("panel_properties"))
|
|
data.update(tool.Model.get_constituents_props_data(element))
|
|
|
|
# required since we could load pset from .ifc and BIMDoorProperties won't be set
|
|
props.set_props_kwargs_from_ifc_data(data)
|
|
props.is_editing = True
|
|
|
|
def _execute(self, context):
|
|
for obj in tool.Blender.get_selected_objects():
|
|
self.edit_door_on_obj(obj)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
|
|
bl_idname = "bim.remove_door"
|
|
bl_label = "Remove Door on Selected Objects"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def remove_door_on_object(self, obj):
|
|
element = tool.Ifc.get_entity(obj)
|
|
assert element
|
|
if not tool.Blender.Modifier.is_door(element):
|
|
return
|
|
props = tool.Model.get_door_props(obj)
|
|
props.is_editing = False
|
|
|
|
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
|
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
|
|
|
|
def _execute(self, context):
|
|
for obj in tool.Blender.get_selected_objects():
|
|
self.remove_door_on_object(obj)
|
|
return {"FINISHED"}
|