mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 14:41:25 +00:00
Roof Modifier - separating vertices on defining gable roof angle
Added option (which is activated by default) - when you set gable roof angle for some edge it will duplicate the top vertex instead of just moving which allows you to do shape like this - https://imgur.com/a/OEFZ4pw Previously you would get something like this in that case - https://community.osarch.org/uploads/editor/ap/hlt70rbw5r1l.png
This commit is contained in:
@@ -31,3 +31,4 @@ class BIMCadProperties(PropertyGroup):
|
|||||||
gable_roof_edge_angle: bpy.props.FloatProperty(
|
gable_roof_edge_angle: bpy.props.FloatProperty(
|
||||||
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
|
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
|
||||||
)
|
)
|
||||||
|
gable_roof_separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ class CadHotkey(bpy.types.Operator):
|
|||||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||||
):
|
):
|
||||||
self.layout.row().prop(props, "gable_roof_edge_angle")
|
self.layout.row().prop(props, "gable_roof_edge_angle")
|
||||||
|
self.layout.row().prop(props, "gable_roof_separate_verts")
|
||||||
|
|
||||||
elif self.hotkey == "S_V":
|
elif self.hotkey == "S_V":
|
||||||
if not self.is_profile():
|
if not self.is_profile():
|
||||||
@@ -285,7 +286,9 @@ class CadHotkey(bpy.types.Operator):
|
|||||||
and RoofData.data["parameters"]
|
and RoofData.data["parameters"]
|
||||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||||
):
|
):
|
||||||
bpy.ops.bim.set_gable_roof_edge_angle(angle=self.props.gable_roof_edge_angle)
|
bpy.ops.bim.set_gable_roof_edge_angle(
|
||||||
|
angle=self.props.gable_roof_edge_angle, separate_verts=self.props.gable_roof_separate_verts
|
||||||
|
)
|
||||||
|
|
||||||
def hotkey_S_T(self):
|
def hotkey_S_T(self):
|
||||||
bpy.ops.bim.cad_mitre()
|
bpy.ops.bim.cad_mitre()
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Operator
|
|
||||||
import bmesh
|
import bmesh
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -29,12 +28,11 @@ from blenderbim.bim.module.model.data import RoofData, refresh
|
|||||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from math import tan, radians, degrees, atan
|
from math import tan
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bpypolyskel import bpypolyskel
|
from bpypolyskel import bpypolyskel
|
||||||
import shapely
|
import shapely
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from itertools import chain
|
|
||||||
from math import pi
|
from math import pi
|
||||||
|
|
||||||
# reference:
|
# reference:
|
||||||
@@ -116,10 +114,13 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
|||||||
|
|
||||||
original_geometry_data = dict()
|
original_geometry_data = dict()
|
||||||
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
||||||
|
separate_verts_layer = bm.edges.layers.int.get("BBIM_gable_roof_separate_verts")
|
||||||
if angle_layer:
|
if angle_layer:
|
||||||
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), e[angle_layer]) for e in bm.edges]
|
original_geometry_data["edges"] = [
|
||||||
|
(set(bm_get_indices(e.verts)), e[angle_layer], e[separate_verts_layer]) for e in bm.edges
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), None) for e in bm.edges]
|
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), None, None) for e in bm.edges]
|
||||||
|
|
||||||
original_geometry_data["verts"] = {v.index: v.co.copy() for v in bm.verts}
|
original_geometry_data["verts"] = {v.index: v.co.copy() for v in bm.verts}
|
||||||
footprint_z = bm.verts[:][0].co.z
|
footprint_z = bm.verts[:][0].co.z
|
||||||
@@ -212,6 +213,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
|||||||
footprint_edges = []
|
footprint_edges = []
|
||||||
footprint_verts = set()
|
footprint_verts = set()
|
||||||
verts_to_change = {}
|
verts_to_change = {}
|
||||||
|
verts_to_rip = []
|
||||||
|
|
||||||
# find footprint edges
|
# find footprint edges
|
||||||
for edge in bm.edges:
|
for edge in bm.edges:
|
||||||
@@ -227,7 +229,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
|||||||
# iterate over edges from original geometry
|
# iterate over edges from original geometry
|
||||||
# if their angle was redefined by user - apply the changes to the related vertices
|
# if their angle was redefined by user - apply the changes to the related vertices
|
||||||
# to match the requested angle
|
# to match the requested angle
|
||||||
for old_edge_verts, defined_angle in original_geometry_data["edges"]:
|
for old_edge_verts, defined_angle, separate_verts in original_geometry_data["edges"]:
|
||||||
if not defined_angle:
|
if not defined_angle:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -240,14 +242,35 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
|||||||
|
|
||||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||||
for v in verts_to_move:
|
for v in verts_to_move:
|
||||||
vert_co = verts_to_change.get(v, v.co)
|
if not separate_verts:
|
||||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
vert_co = verts_to_change.get(v, v.co)
|
||||||
verts_to_change[v] = new_vert_co
|
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||||
|
verts_to_change[v] = new_vert_co
|
||||||
|
else:
|
||||||
|
vert_co = v.co
|
||||||
|
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||||
|
verts_to_rip.append([v, new_vert_co, identical_edge])
|
||||||
|
|
||||||
|
def separate_vert(bm, vert, edge, new_co):
|
||||||
|
face = next(f for f in vert.link_faces if edge in f.edges)
|
||||||
|
new_v = bmesh.utils.face_vert_separate(face, vert)
|
||||||
|
new_v.co = new_co
|
||||||
|
new_edge = bm.edges.new((vert, new_v))
|
||||||
|
|
||||||
|
for cur_edge in face.edges:
|
||||||
|
if cur_edge == edge:
|
||||||
|
continue
|
||||||
|
bmesh.ops.contextual_create(bm, geom=[new_edge, cur_edge])
|
||||||
|
|
||||||
# apply all changes once at the end
|
# apply all changes once at the end
|
||||||
for v in verts_to_change:
|
for v in verts_to_change:
|
||||||
v.co = verts_to_change[v]
|
v.co = verts_to_change[v]
|
||||||
|
|
||||||
|
for v, new_co, edge in verts_to_rip:
|
||||||
|
separate_vert(bm, v, edge, new_co)
|
||||||
|
|
||||||
|
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
|
||||||
|
|
||||||
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||||
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
|
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
|
||||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
||||||
@@ -289,9 +312,6 @@ def update_roof_modifier_ifc_data(context):
|
|||||||
# occurences attributes
|
# occurences attributes
|
||||||
# occurences = tool.Ifc.get_all_element_occurences(element)
|
# occurences = tool.Ifc.get_all_element_occurences(element)
|
||||||
|
|
||||||
# TODO: add Qto_RoofBaseQuantities, need to calculate GrossArea, NetArea, ProjectedArea
|
|
||||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/Qto_RoofBaseQuantities.htm
|
|
||||||
|
|
||||||
tool.Ifc.edit(obj)
|
tool.Ifc.edit(obj)
|
||||||
|
|
||||||
|
|
||||||
@@ -311,12 +331,14 @@ def update_roof_modifier_bmesh(context):
|
|||||||
RoofData.load()
|
RoofData.load()
|
||||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||||
angle_layer_data = path_data.get("gable_roof_angles", None)
|
angle_layer_data = path_data.get("gable_roof_angles", None)
|
||||||
|
separate_verts_data = path_data.get("gable_roof_separate_verts", None)
|
||||||
|
|
||||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
# need to make sure we support edit mode
|
# need to make sure we support edit mode
|
||||||
# since users will probably be in edit mode when they'll be changing roof path
|
# 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)
|
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||||
angle_layer = bm.edges.layers.float.new("BBIM_gable_roof_angles")
|
angle_layer = bm.edges.layers.float.new("BBIM_gable_roof_angles")
|
||||||
|
separate_verts_layer = bm.edges.layers.int.new("BBIM_gable_roof_separate_verts")
|
||||||
|
|
||||||
# generating roof path
|
# generating roof path
|
||||||
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
|
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
|
||||||
@@ -325,6 +347,7 @@ def update_roof_modifier_bmesh(context):
|
|||||||
e = path_data["edges"][i]
|
e = path_data["edges"][i]
|
||||||
edge = bm.edges.new((new_verts[e[0]], new_verts[e[1]]))
|
edge = bm.edges.new((new_verts[e[0]], new_verts[e[1]]))
|
||||||
edge[angle_layer] = angle_layer_data[i] if angle_layer_data else 0
|
edge[angle_layer] = angle_layer_data[i] if angle_layer_data else 0
|
||||||
|
edge[separate_verts_layer] = separate_verts_data[i] if separate_verts_data else 0
|
||||||
new_edges.append(edge)
|
new_edges.append(edge)
|
||||||
|
|
||||||
if props.is_editing_path:
|
if props.is_editing_path:
|
||||||
@@ -349,12 +372,15 @@ def get_path_data(obj):
|
|||||||
bm_mesh_clean_up(bm)
|
bm_mesh_clean_up(bm)
|
||||||
|
|
||||||
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
||||||
|
separate_verts_layer = bm.edges.layers.int.get("BBIM_gable_roof_separate_verts")
|
||||||
|
|
||||||
path_data = dict()
|
path_data = dict()
|
||||||
path_data["edges"] = [bm_get_indices(e.verts) for e in bm.edges]
|
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]
|
path_data["verts"] = [v.co / si_conversion for v in bm.verts]
|
||||||
if angle_layer:
|
if angle_layer:
|
||||||
path_data["gable_roof_angles"] = [e[angle_layer] for e in bm.edges]
|
path_data["gable_roof_angles"] = [e[angle_layer] for e in bm.edges]
|
||||||
|
if separate_verts_layer:
|
||||||
|
path_data["gable_roof_separate_verts"] = [e[separate_verts_layer] for e in bm.edges]
|
||||||
|
|
||||||
if not path_data["edges"] or not path_data["verts"]:
|
if not path_data["edges"] or not path_data["verts"]:
|
||||||
return None
|
return None
|
||||||
@@ -634,6 +660,7 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
|
|||||||
bl_label = "Set Gable Roof Edge Angle"
|
bl_label = "Set Gable Roof Edge Angle"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
angle: bpy.props.FloatProperty(name="Angle", default=90)
|
angle: bpy.props.FloatProperty(name="Angle", default=90)
|
||||||
|
separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -656,12 +683,17 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
|
|||||||
if "BBIM_gable_roof_angles" not in me.attributes:
|
if "BBIM_gable_roof_angles" not in me.attributes:
|
||||||
me.attributes.new("BBIM_gable_roof_angles", type="FLOAT", domain="EDGE")
|
me.attributes.new("BBIM_gable_roof_angles", type="FLOAT", domain="EDGE")
|
||||||
|
|
||||||
|
if "BBIM_gable_roof_separate_verts" not in me.attributes:
|
||||||
|
me.attributes.new("BBIM_gable_roof_separate_verts", type="INT", domain="EDGE")
|
||||||
|
|
||||||
angles_layer = bm.edges.layers.float["BBIM_gable_roof_angles"]
|
angles_layer = bm.edges.layers.float["BBIM_gable_roof_angles"]
|
||||||
|
separate_verts_layer = bm.edges.layers.int["BBIM_gable_roof_separate_verts"]
|
||||||
|
|
||||||
for e in bm.edges:
|
for e in bm.edges:
|
||||||
if not e.select:
|
if not e.select:
|
||||||
continue
|
continue
|
||||||
e[angles_layer] = self.angle
|
e[angles_layer] = self.angle
|
||||||
|
e[separate_verts_layer] = self.separate_verts
|
||||||
|
|
||||||
tool.Blender.apply_bmesh(me, bm)
|
tool.Blender.apply_bmesh(me, bm)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
Reference in New Issue
Block a user