mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
Ifc Roof modifier
Added Ifc Roof modifier (also added it to shift-a menu and type manager). It works similar way to railing modifier - you define roof profile and then roof is generated. Currently it has only 1 type of roof - HIP_ROOF (it utilizes bim.generate_hipped_roof operator).
This commit is contained in:
@@ -37,6 +37,7 @@ from . import (
|
||||
sverchok_modifier,
|
||||
door,
|
||||
railing,
|
||||
roof,
|
||||
)
|
||||
|
||||
classes = (
|
||||
@@ -101,6 +102,7 @@ classes = (
|
||||
prop.BIMWindowProperties,
|
||||
prop.BIMDoorProperties,
|
||||
prop.BIMRailingProperties,
|
||||
prop.BIMRoofProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_array,
|
||||
ui.BIM_PT_stair,
|
||||
@@ -108,6 +110,7 @@ classes = (
|
||||
ui.BIM_PT_window,
|
||||
ui.BIM_PT_door,
|
||||
ui.BIM_PT_railing,
|
||||
ui.BIM_PT_roof,
|
||||
ui.LaunchTypeManager,
|
||||
ui.HelpConstrTypes,
|
||||
ui.BIM_MT_model,
|
||||
@@ -150,6 +153,15 @@ classes = (
|
||||
railing.FinishEditingRailingPath,
|
||||
railing.EnableEditingRailingPath,
|
||||
railing.RemoveRailing,
|
||||
roof.BIM_OT_add_roof,
|
||||
roof.AddRoof,
|
||||
roof.CancelEditingRoof,
|
||||
roof.FinishEditingRoof,
|
||||
roof.EnableEditingRoof,
|
||||
roof.CancelEditingRoofPath,
|
||||
roof.FinishEditingRoofPath,
|
||||
roof.EnableEditingRoofPath,
|
||||
roof.RemoveRoof,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
@@ -165,11 +177,13 @@ def register():
|
||||
bpy.types.Object.BIMWindowProperties = bpy.props.PointerProperty(type=prop.BIMWindowProperties)
|
||||
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
|
||||
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
||||
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
|
||||
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_mesh_add.append(railing.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(roof.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
|
||||
@@ -190,12 +204,14 @@ def unregister():
|
||||
del bpy.types.Object.BIMWindowProperties
|
||||
del bpy.types.Object.BIMDoorProperties
|
||||
del bpy.types.Object.BIMRailingProperties
|
||||
del bpy.types.Object.BIMRoofProperties
|
||||
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_mesh_add.remove(railing.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(roof.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.remove(ui.add_menu)
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
@@ -36,6 +36,7 @@ def refresh():
|
||||
WindowData.is_loaded = False
|
||||
DoorData.is_loaded = False
|
||||
RailingData.is_loaded = False
|
||||
RoofData.is_loaded = False
|
||||
|
||||
|
||||
class AuthoringData:
|
||||
@@ -483,3 +484,23 @@ class RailingData:
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
|
||||
class RoofData:
|
||||
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_Roof", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
@@ -237,6 +237,7 @@ class BIMModelProperties(PropertyGroup):
|
||||
("DOOR", "Door", ""),
|
||||
("STAIR", "Stair", ""),
|
||||
("RAILING", "Railing", ""),
|
||||
("ROOF", "Roof", ""),
|
||||
),
|
||||
name="Type Template",
|
||||
default="MESH",
|
||||
@@ -564,3 +565,33 @@ class BIMRailingProperties(PropertyGroup):
|
||||
"thickness": self.thickness,
|
||||
"spacing": self.spacing,
|
||||
}
|
||||
|
||||
|
||||
class BIMRoofProperties(PropertyGroup):
|
||||
roof_types = (("HIP_ROOF", "HIP_ROOF", ""),)
|
||||
roof_generation_methods = (
|
||||
("HEIGHT", "HEIGHT", ""),
|
||||
("ANGLE", "ANGLE", ""),
|
||||
)
|
||||
|
||||
roof_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
roof_type: bpy.props.EnumProperty(name="Roof Type", items=roof_types, default="HIP_ROOF")
|
||||
generation_method: bpy.props.EnumProperty(
|
||||
name="Roof Generation Method", items=roof_generation_methods, default="HEIGHT"
|
||||
)
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0)
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=10, description="In degrees")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
kwargs = {
|
||||
"roof_type": self.roof_type,
|
||||
"generation_method": self.generation_method,
|
||||
}
|
||||
if self.generation_method == "HEIGHT":
|
||||
kwargs["height"] = self.height
|
||||
else:
|
||||
kwargs["angle"] = self.angle
|
||||
return kwargs
|
||||
|
||||
@@ -243,7 +243,7 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.railing_added_previously:
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously", "path_data")
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
@@ -260,6 +260,7 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
railing_data["path_data"] = path_data
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
refresh()
|
||||
update_railing_modifier_ifc_data(context)
|
||||
update_railing_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -16,14 +16,26 @@
|
||||
# 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
|
||||
import bmesh
|
||||
import shapely
|
||||
|
||||
import ifcopenshell
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
from blenderbim.bim.module.model.data import RoofData, refresh
|
||||
|
||||
import json
|
||||
from math import tan, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from bpypolyskel import bpypolyskel
|
||||
import shapely
|
||||
|
||||
# reference:
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoof.htm
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -37,75 +49,393 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
return
|
||||
self.report({"ERROR"}, "Need to select some object first.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
boundary_lines = []
|
||||
bpy.ops.mesh.dissolve_limited()
|
||||
generate_hipped_roof(obj, self.mode, self.height, self.angle)
|
||||
return {"FINISHED"}
|
||||
|
||||
for edge in obj.data.edges:
|
||||
boundary_lines.append(
|
||||
shapely.LineString([obj.data.vertices[edge.vertices[0]].co, obj.data.vertices[edge.vertices[1]].co])
|
||||
)
|
||||
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
def generate_hipped_roof(obj, mode="ANGLE", height=1.0, angle=10):
|
||||
boundary_lines = []
|
||||
|
||||
roof_polygon = None
|
||||
biggest_area = 0
|
||||
for polygon in closed_polygons.geoms:
|
||||
area = polygon.area
|
||||
if area > biggest_area:
|
||||
roof_polygon = polygon
|
||||
biggest_area = area
|
||||
|
||||
roof_polygon = shapely.force_3d(roof_polygon)
|
||||
|
||||
if not shapely.is_ccw(roof_polygon):
|
||||
roof_polygon = roof_polygon.reverse()
|
||||
|
||||
# Define vertices for the base footprint of the building at height 0.0
|
||||
# counterclockwise order
|
||||
verts = [Vector(v) for v in roof_polygon.exterior.coords[0:-1]]
|
||||
total_exterior_verts = len(verts)
|
||||
next_index = total_exterior_verts
|
||||
|
||||
inner_loops = None
|
||||
for interior in roof_polygon.interiors:
|
||||
if inner_loops is None:
|
||||
inner_loops = []
|
||||
loop = interior.coords[0:-1]
|
||||
total_verts = len(loop)
|
||||
verts.extend([Vector(v) for v in loop])
|
||||
inner_loops.append((next_index, total_verts))
|
||||
next_index += total_verts
|
||||
|
||||
unit_vectors = None # we have no unit vectors, let them computed by polygonize()
|
||||
start_exterior_index = 0
|
||||
|
||||
faces = []
|
||||
|
||||
if self.mode == "HEIGHT":
|
||||
height = self.height
|
||||
angle = 0.0
|
||||
else:
|
||||
angle = tan(radians(round(self.angle, 4)))
|
||||
height = 0.0
|
||||
|
||||
faces = bpypolyskel.polygonize(
|
||||
verts, start_exterior_index, total_exterior_verts, inner_loops, height, angle, faces, unit_vectors
|
||||
for edge in obj.data.edges:
|
||||
boundary_lines.append(
|
||||
shapely.LineString([obj.data.vertices[edge.vertices[0]].co, obj.data.vertices[edge.vertices[1]].co])
|
||||
)
|
||||
|
||||
edges = []
|
||||
mesh = bpy.data.meshes.new(name="Building_with_Roof")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
roof_polygon = None
|
||||
biggest_area = 0
|
||||
for polygon in closed_polygons.geoms:
|
||||
area = polygon.area
|
||||
if area > biggest_area:
|
||||
roof_polygon = polygon
|
||||
biggest_area = area
|
||||
|
||||
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
||||
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
||||
roof_polygon = shapely.force_3d(roof_polygon)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
if not shapely.is_ccw(roof_polygon):
|
||||
roof_polygon = roof_polygon.reverse()
|
||||
|
||||
obj.data = mesh
|
||||
# Define vertices for the base footprint of the building at height 0.0
|
||||
# counterclockwise order
|
||||
verts = [Vector(v) for v in roof_polygon.exterior.coords[0:-1]]
|
||||
total_exterior_verts = len(verts)
|
||||
next_index = total_exterior_verts
|
||||
|
||||
inner_loops = None
|
||||
for interior in roof_polygon.interiors:
|
||||
if inner_loops is None:
|
||||
inner_loops = []
|
||||
loop = interior.coords[0:-1]
|
||||
total_verts = len(loop)
|
||||
verts.extend([Vector(v) for v in loop])
|
||||
inner_loops.append((next_index, total_verts))
|
||||
next_index += total_verts
|
||||
|
||||
unit_vectors = None # we have no unit vectors, let them computed by polygonize()
|
||||
start_exterior_index = 0
|
||||
|
||||
faces = []
|
||||
|
||||
if mode == "HEIGHT":
|
||||
height = height
|
||||
angle = 0.0
|
||||
else:
|
||||
angle = tan(radians(round(angle, 4)))
|
||||
height = 0.0
|
||||
|
||||
faces = bpypolyskel.polygonize(
|
||||
verts, start_exterior_index, total_exterior_verts, inner_loops, height, angle, faces, unit_vectors
|
||||
)
|
||||
|
||||
edges = []
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
new_verts = [bm.verts.new(v) for v in verts]
|
||||
new_edges = [bm.edges.new([new_verts[vi] for vi in edge]) for edge in edges]
|
||||
new_faces = [bm.faces.new([new_verts[vi] for vi in face]) for face in faces]
|
||||
|
||||
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
||||
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
|
||||
def bm_get_indices(sequence):
|
||||
return [i.index for i in sequence]
|
||||
|
||||
|
||||
def update_roof_modifier_ifc_data(context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# type attributes
|
||||
element.PredefinedType = props.roof_type
|
||||
# occurences attributes
|
||||
# occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
|
||||
|
||||
def update_bbim_roof_pset(element, roof_data):
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Roof")
|
||||
roof_data = json.dumps(roof_data, default=list)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": roof_data})
|
||||
|
||||
|
||||
def update_roof_modifier_bmesh(context):
|
||||
obj = context.object
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
if not RoofData.is_loaded:
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# need to make sure we support edit mode
|
||||
# since users will probably be in edit mode when they'll be changing roof path
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
|
||||
# generating roof path
|
||||
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
|
||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in path_data["edges"]]
|
||||
|
||||
if props.is_editing_path:
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
# apply dissolve limit seems to get more correct results with `generate_hipped_roof`
|
||||
# argument values are the defaults for `bpy.ops.mesh.dissolve_limited`
|
||||
bmesh.ops.dissolve_limit(
|
||||
bm, angle_limit=0.0872665, use_dissolve_boundaries=False, delimit={"NORMAL"}, verts=bm.verts[:]
|
||||
)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
height = props.height * si_conversion
|
||||
angle = props.angle * si_conversion
|
||||
generation_method = props.generation_method
|
||||
generate_hipped_roof(obj, generation_method, height, angle)
|
||||
|
||||
|
||||
def get_path_data(obj):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
# otherwise mesh may not contain all changes
|
||||
# added in edit mode
|
||||
obj.update_from_editmode()
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
|
||||
# remove internal edges and faces
|
||||
# adding missing faces so we could rely on `e.is_boundary` later
|
||||
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
|
||||
|
||||
edges_to_dissolve = [e for e in bm.edges if not e.is_boundary]
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bmesh.ops.delete(bm, geom=bm.faces[:], context="FACES_ONLY")
|
||||
|
||||
path_data = dict()
|
||||
path_data["edges"] = [bm_get_indices(e.verts) for e in bm.edges]
|
||||
path_data["verts"] = [v.co / si_conversion for v in bm.verts]
|
||||
|
||||
if not path_data["edges"] or not path_data["verts"]:
|
||||
return None
|
||||
return path_data
|
||||
|
||||
|
||||
class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_roof"
|
||||
bl_label = "Roof"
|
||||
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 roof.")
|
||||
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("IfcRoof")
|
||||
obj = bpy.data.objects.new("IfcRoof", mesh)
|
||||
obj.location = spawn_location
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class="IfcRoof",
|
||||
should_add_representation=True,
|
||||
context=body_context,
|
||||
)
|
||||
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_roof()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# UI operators
|
||||
class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_roof"
|
||||
bl_label = "Add Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if element.is_a() not in ("IfcRoof", "IfcRoofType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcRoof/IfcRoofType type to add a roof.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.roof_added_previously:
|
||||
skip_props = ("is_editing", "roof_type", "roof_added_previously", "generation_method")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
if not path_data:
|
||||
path_data = {
|
||||
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
|
||||
"verts": [
|
||||
Vector([-5.0, -5.0, 0.0]) / si_conversion,
|
||||
Vector([-5.0, 5.0, 0.0]) / si_conversion,
|
||||
Vector([5.0, 5.0, 0.0]) / si_conversion,
|
||||
Vector([5.0, -5.0, 0.0]) / si_conversion,
|
||||
],
|
||||
}
|
||||
roof_data["path_data"] = path_data
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
update_roof_modifier_ifc_data(context)
|
||||
refresh()
|
||||
update_roof_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_roof"
|
||||
bl_label = "Enable Editing Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRoofProperties 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", "roof_type", "roof_added_previously", "generation_method")
|
||||
skip_props += tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_roof"
|
||||
bl_label = "Cancel editing Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
props = obj.BIMRoofProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = -1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_roof"
|
||||
bl_label = "Finish editing roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
if not RoofData.is_loaded:
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
roof_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
update_roof_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_roof_path"
|
||||
bl_label = "Enable Editing Roof Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
props.is_editing_path = True
|
||||
update_roof_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_roof_path"
|
||||
bl_label = "Cancel Editing Roof Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
update_roof_modifier_bmesh(context)
|
||||
props.is_editing_path = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_roof_path"
|
||||
bl_label = "Finish Editing Roof Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
roof_data["path_data"] = path_data
|
||||
props.is_editing_path = False
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
refresh() # need RoofData to be updated before update_roof_modifier_bmesh
|
||||
update_roof_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_roof"
|
||||
bl_label = "Remove Roof"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMRoofProperties.is_editing = -1
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.roof_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def add_object_button(self, context):
|
||||
self.layout.operator(BIM_OT_add_roof.bl_idname, icon="PLUGIN")
|
||||
|
||||
@@ -27,12 +27,14 @@ from blenderbim.bim.module.model.data import (
|
||||
WindowData,
|
||||
DoorData,
|
||||
RailingData,
|
||||
RoofData,
|
||||
)
|
||||
from blenderbim.bim.module.model.prop import get_ifc_class
|
||||
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.module.model.railing import update_railing_modifier_bmesh
|
||||
from blenderbim.bim.module.model.roof import update_roof_modifier_bmesh
|
||||
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
|
||||
@@ -597,6 +599,65 @@ class BIM_PT_railing(bpy.types.Panel):
|
||||
row.operator("bim.add_railing", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_PT_roof(bpy.types.Panel):
|
||||
bl_label = "IFC Roof"
|
||||
bl_idname = "BIM_PT_roof"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "modifier"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@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 RoofData.is_loaded:
|
||||
RoofData.load()
|
||||
|
||||
props = context.active_object.BIMRoofProperties
|
||||
|
||||
if RoofData.data["parameters"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Roof parameters", icon="OUTLINER_OB_LATTICE")
|
||||
|
||||
roof_data = RoofData.data["parameters"]["data_dict"]
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_roof", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.cancel_editing_roof", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
update_roof_modifier_bmesh(context)
|
||||
|
||||
elif props.is_editing_path:
|
||||
row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="Finish editing path")
|
||||
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
|
||||
|
||||
else:
|
||||
row.operator("bim.enable_editing_roof", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.enable_editing_roof_path", icon="ANIM", text="")
|
||||
row.operator("bim.remove_roof", icon="X", text="")
|
||||
|
||||
box = self.layout.box()
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
prop_value = roof_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))
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Roof Found")
|
||||
row.operator("bim.add_roof", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_MT_model(Menu):
|
||||
bl_idname = "BIM_MT_model"
|
||||
bl_label = "IFC Objects"
|
||||
|
||||
@@ -279,6 +279,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
context=body,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
elif template == "WINDOW":
|
||||
mesh = bpy.data.meshes.new("IfcWindow")
|
||||
obj = bpy.data.objects.new("TYPEX", mesh)
|
||||
@@ -353,6 +354,25 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.select_set(True)
|
||||
bpy.ops.bim.add_railing()
|
||||
|
||||
elif template == "ROOF":
|
||||
mesh = bpy.data.meshes.new("IfcRoof")
|
||||
obj = bpy.data.objects.new("TYPEX", mesh)
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
predefined_type=predefined_type,
|
||||
ifc_class="IfcRoofType",
|
||||
should_add_representation=True,
|
||||
context=body,
|
||||
)
|
||||
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_roof()
|
||||
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user