mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +00:00
fix #7537 - Layer thickness correct when slab is rotated and few other features...
- Add dual-rotation support for AXIS3 slabs (IFC angle + object rotation) - Fix profile editing to display horizontal projection for tilted slabs - Fix AXIS2 layer slicing to use local extrusion direction for walls - Fix ChangeExtrusionDepth to refresh geometry after depth changes - Remove rotation lock on slabs to allow free rotation - Fix undefined variable bug in add_slab_representation.py
This commit is contained in:
@@ -35,7 +35,7 @@ import bonsai.core.geometry
|
|||||||
import bonsai.core.root
|
import bonsai.core.root
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from math import cos, pi
|
from math import cos, sin, pi, acos, degrees
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
|
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
|
||||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||||
@@ -296,50 +296,65 @@ class DumbSlabPlaner:
|
|||||||
if representation:
|
if representation:
|
||||||
extrusion = tool.Model.get_extrusion(representation)
|
extrusion = tool.Model.get_extrusion(representation)
|
||||||
if extrusion:
|
if extrusion:
|
||||||
# TODO Right now we don't have a reliable way to calculate the existing x_angle only based solely on the extrusion direction.
|
|
||||||
# For instances, a 30 degrees angled extrusion with positive direction has the same extrusion direction as a
|
|
||||||
# -150 degrees angled extrusion with negative direction. The difference lies in the object's rotation.
|
|
||||||
# This means that things can get messy if the user changes the object x angle somehow. We have to figure out an alternative approach.
|
|
||||||
existing_x_angle = obj.rotation_euler.x
|
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
|
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
|
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 2 * pi, tolerance=0.001) else existing_x_angle
|
|
||||||
direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios)
|
direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios)
|
||||||
offset_direction = direction_ratios.copy()
|
|
||||||
perpendicular_depth = thickness * abs(1 / cos(existing_x_angle))
|
# Calculate the actual extrusion angle from vertical
|
||||||
perpendicular_offset = layer_offset * abs(1 / cos(existing_x_angle)) / self.unit_scale
|
extrusion_angle = 0
|
||||||
|
if direction_ratios.length > 0:
|
||||||
# Check angle and z direction to determine whether the extrusion direction is positive or negative
|
cos_angle = direction_ratios.normalized().dot(Vector((0, 0, 1)))
|
||||||
if (abs(existing_x_angle) < (pi / 2) and direction_ratios.z > 0) or (
|
extrusion_angle = acos(min(max(cos_angle, -1), 1))
|
||||||
abs(existing_x_angle) > (pi / 2) and direction_ratios.z < 0
|
|
||||||
):
|
# FIX: Only apply 1/cos factor when there's actual extrusion slope
|
||||||
# The extrusion direction is positive. If the layer_parameter is set to negative,
|
if extrusion_angle > 1e-6:
|
||||||
# then the we change the extrusion direction.
|
perpendicular_depth = thickness * abs(1 / cos(extrusion_angle))
|
||||||
if layer_params["direction_sense"] == "NEGATIVE":
|
perpendicular_offset = layer_offset * abs(1 / cos(extrusion_angle)) / self.unit_scale
|
||||||
direction_ratios *= -1
|
|
||||||
elif (abs(existing_x_angle) > (pi / 2) and direction_ratios.z > 0) or (
|
|
||||||
abs(existing_x_angle) < (pi / 2) and direction_ratios.z < 0
|
|
||||||
):
|
|
||||||
# The extrusion direction is negative. If the layer_parameter is set to positive,
|
|
||||||
# then the we change the extrusion direction. And the offset direction should remain positive
|
|
||||||
# for either direction sense, so we change it.
|
|
||||||
offset_direction *= -1
|
|
||||||
if layer_params["direction_sense"] == "POSITIVE":
|
|
||||||
direction_ratios *= -1
|
|
||||||
|
|
||||||
extrusion.ExtrudedDirection.DirectionRatios = tuple(direction_ratios)
|
|
||||||
extrusion.Depth = perpendicular_depth
|
|
||||||
|
|
||||||
ifc_position = extrusion.Position
|
|
||||||
position = offset_direction * perpendicular_offset
|
|
||||||
material = ifcopenshell.util.element.get_material(element)
|
|
||||||
if material:
|
|
||||||
if material.is_a("IfcMaterialLayerSetUsage"):
|
|
||||||
material.OffsetFromReferenceLine = position.z
|
|
||||||
if ifc_position:
|
|
||||||
ifc_position.Location.Coordinates = position
|
|
||||||
else:
|
else:
|
||||||
tool.Model.add_extrusion_position(extrusion, position)
|
perpendicular_depth = thickness
|
||||||
|
perpendicular_offset = layer_offset / self.unit_scale
|
||||||
|
|
||||||
|
# Check if direction sense needs to be applied
|
||||||
|
# This should only happen if explicitly requested, not automatically
|
||||||
|
if layer_params.get("apply_direction_sense", False):
|
||||||
|
# Store current direction before potential change
|
||||||
|
old_direction = direction_ratios.copy()
|
||||||
|
|
||||||
|
# Apply direction sense logic
|
||||||
|
existing_x_angle = extrusion_angle
|
||||||
|
if (abs(existing_x_angle) < (pi / 2) and direction_ratios.z > 0) or (
|
||||||
|
abs(existing_x_angle) > (pi / 2) and direction_ratios.z < 0
|
||||||
|
):
|
||||||
|
if layer_params["direction_sense"] == "NEGATIVE":
|
||||||
|
direction_ratios *= -1
|
||||||
|
elif (abs(existing_x_angle) > (pi / 2) and direction_ratios.z > 0) or (
|
||||||
|
abs(existing_x_angle) < (pi / 2) and direction_ratios.z < 0
|
||||||
|
):
|
||||||
|
offset_direction = direction_ratios.copy() * -1
|
||||||
|
if layer_params["direction_sense"] == "POSITIVE":
|
||||||
|
direction_ratios *= -1
|
||||||
|
|
||||||
|
# If direction changed, update extrusion with rotation compensation
|
||||||
|
if (direction_ratios.normalized() - old_direction.normalized()).length > 1e-6:
|
||||||
|
update_extrusion_direction(element, tuple(direction_ratios), obj)
|
||||||
|
# After updating direction, get the updated extrusion
|
||||||
|
extrusion = tool.Model.get_extrusion(representation)
|
||||||
|
|
||||||
|
# Update depth
|
||||||
|
extrusion.Depth = perpendicular_depth
|
||||||
|
|
||||||
|
# Update position
|
||||||
|
ifc_position = extrusion.Position
|
||||||
|
if direction_ratios.length > 0:
|
||||||
|
offset_vector = direction_ratios.normalized() * perpendicular_offset
|
||||||
|
position = offset_vector
|
||||||
|
|
||||||
|
material = ifcopenshell.util.element.get_material(element)
|
||||||
|
if material and material.is_a("IfcMaterialLayerSetUsage"):
|
||||||
|
material.OffsetFromReferenceLine = position.z
|
||||||
|
|
||||||
|
if ifc_position:
|
||||||
|
ifc_position.Location.Coordinates = position
|
||||||
|
else:
|
||||||
|
tool.Model.add_extrusion_position(extrusion, position)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
props = tool.Model.get_model_props()
|
props = tool.Model.get_model_props()
|
||||||
@@ -383,6 +398,113 @@ class DumbSlabPlaner:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_extrusion_direction(element: ifcopenshell.entity_instance,
|
||||||
|
new_direction_ratios: tuple,
|
||||||
|
obj: bpy.types.Object = None) -> None:
|
||||||
|
"""
|
||||||
|
Update extrusion direction while preserving overall object orientation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: The IFC element
|
||||||
|
new_direction_ratios: New extrusion direction ratios (x,y,z)
|
||||||
|
obj: Optional Blender object (will be fetched if not provided)
|
||||||
|
"""
|
||||||
|
if not obj:
|
||||||
|
obj = tool.Ifc.get_object(element)
|
||||||
|
if not obj:
|
||||||
|
return
|
||||||
|
|
||||||
|
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
if not representation:
|
||||||
|
return
|
||||||
|
|
||||||
|
extrusion = tool.Model.get_extrusion(representation)
|
||||||
|
if not extrusion:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get current extrusion direction
|
||||||
|
old_direction = Vector(extrusion.ExtrudedDirection.DirectionRatios)
|
||||||
|
if old_direction.length == 0:
|
||||||
|
old_direction = Vector((0, 0, 1)) # Default
|
||||||
|
|
||||||
|
new_direction = Vector(new_direction_ratios)
|
||||||
|
if new_direction.length == 0:
|
||||||
|
new_direction = Vector((0, 0, 1)) # Default
|
||||||
|
|
||||||
|
# Normalize both directions
|
||||||
|
old_direction_normalized = old_direction.normalized()
|
||||||
|
new_direction_normalized = new_direction.normalized()
|
||||||
|
|
||||||
|
# Store current object matrix
|
||||||
|
old_matrix = obj.matrix_world.copy()
|
||||||
|
|
||||||
|
# Calculate the rotation needed to keep same orientation
|
||||||
|
# When extrusion direction changes from A to B relative to local coordinates,
|
||||||
|
# we need to rotate the object by the inverse of that change
|
||||||
|
|
||||||
|
# Calculate rotation from old to new direction
|
||||||
|
rotation_axis = old_direction_normalized.cross(new_direction_normalized)
|
||||||
|
if rotation_axis.length > 1e-6:
|
||||||
|
rotation_axis.normalized()
|
||||||
|
dot_product = old_direction_normalized.dot(new_direction_normalized)
|
||||||
|
angle = acos(min(max(dot_product, -1), 1))
|
||||||
|
|
||||||
|
# Apply INVERSE rotation to object to compensate
|
||||||
|
rotation_matrix = Matrix.Rotation(-angle, 4, rotation_axis)
|
||||||
|
|
||||||
|
# Update object rotation
|
||||||
|
obj.matrix_world = old_matrix @ rotation_matrix
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
|
# Update extrusion direction (keeping magnitude)
|
||||||
|
if old_direction.length > 0:
|
||||||
|
# Preserve the magnitude of the original direction vector
|
||||||
|
magnitude = old_direction.length
|
||||||
|
new_direction = new_direction_normalized * magnitude
|
||||||
|
|
||||||
|
extrusion.ExtrudedDirection.DirectionRatios = tuple(new_direction)
|
||||||
|
|
||||||
|
# Update depth based on new extrusion angle
|
||||||
|
extrusion_angle = 0
|
||||||
|
if new_direction.length > 0:
|
||||||
|
cos_angle = new_direction_normalized.dot(Vector((0, 0, 1)))
|
||||||
|
extrusion_angle = acos(min(max(cos_angle, -1), 1))
|
||||||
|
|
||||||
|
# Get current depth (perpendicular depth)
|
||||||
|
current_perpendicular_depth = extrusion.Depth
|
||||||
|
|
||||||
|
# If we have material layer info, calculate actual thickness
|
||||||
|
material = ifcopenshell.util.element.get_material(element)
|
||||||
|
actual_thickness = current_perpendicular_depth
|
||||||
|
if material and material.is_a("IfcMaterialLayerSetUsage"):
|
||||||
|
layer_set = material.ForLayerSet
|
||||||
|
actual_thickness = sum([l.LayerThickness for l in layer_set.MaterialLayers])
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
actual_thickness *= unit_scale
|
||||||
|
|
||||||
|
# Convert to perpendicular depth if needed
|
||||||
|
if extrusion_angle > 1e-6:
|
||||||
|
new_perpendicular_depth = actual_thickness * abs(1 / cos(extrusion_angle))
|
||||||
|
else:
|
||||||
|
new_perpendicular_depth = actual_thickness
|
||||||
|
|
||||||
|
extrusion.Depth = new_perpendicular_depth
|
||||||
|
|
||||||
|
# Update position offset if needed
|
||||||
|
if extrusion.Position:
|
||||||
|
# Recalculate offset based on new direction
|
||||||
|
material = ifcopenshell.util.element.get_material(element)
|
||||||
|
if material and material.is_a("IfcMaterialLayerSetUsage"):
|
||||||
|
offset = material.OffsetFromReferenceLine
|
||||||
|
if extrusion_angle > 1e-6:
|
||||||
|
perpendicular_offset = offset * abs(1 / cos(extrusion_angle))
|
||||||
|
else:
|
||||||
|
perpendicular_offset = offset
|
||||||
|
|
||||||
|
offset_vector = new_direction_normalized * perpendicular_offset
|
||||||
|
extrusion.Position.Location.Coordinates = tuple(offset_vector)
|
||||||
|
|
||||||
|
|
||||||
class EnableEditingSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
class EnableEditingSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.enable_editing_sketch_extrusion_profile"
|
bl_idname = "bim.enable_editing_sketch_extrusion_profile"
|
||||||
bl_label = "Enable Editing Sketch Extrusion Profile"
|
bl_label = "Enable Editing Sketch Extrusion Profile"
|
||||||
@@ -656,6 +778,8 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
extrusion = tool.Model.get_extrusion(body)
|
extrusion = tool.Model.get_extrusion(body)
|
||||||
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
||||||
layer_params = tool.Model.get_material_layer_parameters(element)
|
layer_params = tool.Model.get_material_layer_parameters(element)
|
||||||
|
|
||||||
|
usage_type = tool.Model.get_usage_type(element)
|
||||||
|
|
||||||
if extrusion.Position:
|
if extrusion.Position:
|
||||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||||
@@ -669,22 +793,49 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
tranlation_matrix = Matrix.Translation(rot_offset)
|
tranlation_matrix = Matrix.Translation(rot_offset)
|
||||||
position = position @ tranlation_matrix
|
position = position @ tranlation_matrix
|
||||||
|
|
||||||
# Restore Object rotation to zero
|
# For AXIS3 with dual rotation: Reset rotation to zero so profile is horizontal
|
||||||
local_rot_mat = obj.rotation_euler.to_matrix()
|
if usage_type == "LAYER3":
|
||||||
rot_mat = Matrix.Rotation(-existing_x_angle, 4, "X")
|
# Store original rotation for later restoration
|
||||||
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
original_rotation_x = obj.rotation_euler.x
|
||||||
new_rot_euler = new_rot_mat.to_euler()
|
obj["pre_edit_rotation_x"] = original_rotation_x
|
||||||
obj.rotation_euler = new_rot_euler
|
|
||||||
|
# Reset rotation to zero - profile will be horizontal
|
||||||
|
current_z_rot = obj.rotation_euler.z
|
||||||
|
obj.rotation_euler.x = 0.0
|
||||||
|
obj.rotation_euler.z = current_z_rot
|
||||||
|
else:
|
||||||
|
# Original behavior: Restore Object rotation to zero
|
||||||
|
local_rot_mat = obj.rotation_euler.to_matrix()
|
||||||
|
rot_mat = Matrix.Rotation(-existing_x_angle, 4, "X")
|
||||||
|
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
||||||
|
new_rot_euler = new_rot_mat.to_euler()
|
||||||
|
obj.rotation_euler = new_rot_euler
|
||||||
|
|
||||||
else:
|
else:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position, x_angle=existing_x_angle)
|
# Import profile with correct x_angle
|
||||||
|
if usage_type == "LAYER3":
|
||||||
|
# For LAYER3: Use x_angle=0 and scale by cos(rotation) to get horizontal projection
|
||||||
|
obj_x_rotation = original_rotation_x # Use stored original rotation
|
||||||
|
scale_factor = abs(cos(obj_x_rotation)) if abs(obj_x_rotation) > 1e-6 else 1.0
|
||||||
|
|
||||||
|
# Import with x_angle=0
|
||||||
|
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position, x_angle=0)
|
||||||
|
|
||||||
|
# Scale the Y coordinates by cos(rotation) to get horizontal projection
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
vert.co.y *= scale_factor
|
||||||
|
else:
|
||||||
|
# For other types: Use existing_x_angle
|
||||||
|
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position, x_angle=existing_x_angle)
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context))
|
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context))
|
||||||
if not bpy.app.background:
|
if not bpy.app.background:
|
||||||
tool.Blender.set_viewport_tool("bim.cad_tool")
|
tool.Blender.set_viewport_tool("bim.cad_tool")
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -706,6 +857,8 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
extrusion = tool.Model.get_extrusion(body)
|
extrusion = tool.Model.get_extrusion(body)
|
||||||
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
||||||
layer_params = tool.Model.get_material_layer_parameters(element)
|
layer_params = tool.Model.get_material_layer_parameters(element)
|
||||||
|
usage_type = tool.Model.get_usage_type(element)
|
||||||
|
|
||||||
if extrusion.Position:
|
if extrusion.Position:
|
||||||
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||||
position.translation *= self.unit_scale
|
position.translation *= self.unit_scale
|
||||||
@@ -718,20 +871,40 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
tranlation_matrix = Matrix.Translation(rot_offset)
|
tranlation_matrix = Matrix.Translation(rot_offset)
|
||||||
position = position @ tranlation_matrix
|
position = position @ tranlation_matrix
|
||||||
|
|
||||||
# Restore Object rotation to x_angle
|
# Restore rotation
|
||||||
local_rot_mat = obj.rotation_euler.to_matrix()
|
if usage_type == "LAYER3":
|
||||||
rot_mat = Matrix.Rotation(existing_x_angle, 4, "X")
|
# Restore original rotation from before editing
|
||||||
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
if "pre_edit_rotation_x" in obj:
|
||||||
new_rot_euler = new_rot_mat.to_euler()
|
current_z_rot = obj.rotation_euler.z
|
||||||
obj.rotation_euler = new_rot_euler
|
obj.rotation_euler.x = obj["pre_edit_rotation_x"]
|
||||||
|
obj.rotation_euler.z = current_z_rot
|
||||||
|
del obj["pre_edit_rotation_x"]
|
||||||
|
else:
|
||||||
|
# Original behavior
|
||||||
|
local_rot_mat = obj.rotation_euler.to_matrix()
|
||||||
|
rot_mat = Matrix.Rotation(existing_x_angle, 4, "X")
|
||||||
|
new_rot_mat = local_rot_mat.to_4x4() @ rot_mat
|
||||||
|
new_rot_euler = new_rot_mat.to_euler()
|
||||||
|
obj.rotation_euler = new_rot_euler
|
||||||
|
|
||||||
else:
|
else:
|
||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
profile = tool.Model.export_profile(obj, position=position, x_angle=existing_x_angle)
|
# Export profile with correct x_angle
|
||||||
|
if usage_type == "LAYER3":
|
||||||
|
# Scale Y coordinates back up before exporting
|
||||||
|
obj_x_rotation = obj.rotation_euler.x
|
||||||
|
scale_factor = abs(cos(obj_x_rotation)) if abs(obj_x_rotation) > 1e-6 else 1.0
|
||||||
|
|
||||||
|
# Un-scale the profile before exporting
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
vert.co.y /= scale_factor # Inverse of import scaling
|
||||||
|
|
||||||
|
profile = tool.Model.export_profile(obj, position=position, x_angle=0)
|
||||||
|
else:
|
||||||
|
profile = tool.Model.export_profile(obj, position=position, x_angle=existing_x_angle)
|
||||||
|
|
||||||
if not profile:
|
if not profile:
|
||||||
|
|
||||||
def msg(self, context):
|
def msg(self, context):
|
||||||
self.layout.label(text="INVALID PROFILE")
|
self.layout.label(text="INVALID PROFILE")
|
||||||
|
|
||||||
@@ -781,6 +954,29 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
footprint_context = ifcopenshell.util.representation.get_context(
|
||||||
|
tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW"
|
||||||
|
)
|
||||||
|
if not footprint_context:
|
||||||
|
return
|
||||||
|
|
||||||
|
curves = [profile.OuterCurve]
|
||||||
|
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||||
|
curves.extend(profile.InnerCurves)
|
||||||
|
new_footprint = ifcopenshell.api.geometry.add_footprint_representation(
|
||||||
|
tool.Ifc.get(), context=footprint_context, curves=curves
|
||||||
|
)
|
||||||
|
old_footprint = ifcopenshell.util.representation.get_representation(element, "Plan", "FootPrint", "SKETCH_VIEW")
|
||||||
|
if old_footprint:
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(old_footprint):
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
|
||||||
|
bonsai.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint)
|
||||||
|
else:
|
||||||
|
ifcopenshell.api.geometry.assign_representation(
|
||||||
|
tool.Ifc.get(), product=element, representation=new_footprint
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ResetVertex(bpy.types.Operator):
|
class ResetVertex(bpy.types.Operator):
|
||||||
bl_idname = "bim.reset_vertex"
|
bl_idname = "bim.reset_vertex"
|
||||||
bl_label = "Reset Vertex"
|
bl_label = "Reset Vertex"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import bonsai.core.geometry
|
|||||||
import bonsai.core.model as core
|
import bonsai.core.model as core
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from math import pi, sin, cos, degrees, atan2
|
from math import pi, sin, cos, degrees, atan2, acos
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
||||||
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
|
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
|
||||||
@@ -397,27 +397,46 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
for obj in selected_objs:
|
for obj in selected_objs:
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
assert element
|
assert element
|
||||||
|
|
||||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
if not representation:
|
if not representation:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
extrusion = tool.Model.get_extrusion(representation)
|
extrusion = tool.Model.get_extrusion(representation)
|
||||||
if not extrusion:
|
if not extrusion:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Get extrusion direction
|
||||||
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
||||||
|
|
||||||
|
# Calculate angle from vertical
|
||||||
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
|
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
|
||||||
extrusion.Depth = self.depth / si_conversion * (1 / cos(x_angle))
|
|
||||||
|
# For sloped walls, compensate so VERTICAL height = target depth
|
||||||
|
cos_angle = cos(x_angle)
|
||||||
|
compensation_factor = abs(1 / cos_angle) if abs(cos_angle) > 1e-6 else 1.0
|
||||||
|
new_depth_ifc = (self.depth / si_conversion) * compensation_factor
|
||||||
|
|
||||||
|
extrusion.Depth = new_depth_ifc
|
||||||
|
|
||||||
|
# IMPORTANT: Refresh the geometry to reflect the IFC changes
|
||||||
|
bonsai.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=representation,
|
||||||
|
)
|
||||||
|
|
||||||
if tool.Model.get_usage_type(element) == "LAYER2":
|
if tool.Model.get_usage_type(element) == "LAYER2":
|
||||||
for rel in element.ConnectedFrom:
|
for rel in element.ConnectedFrom:
|
||||||
if rel.is_a() == "IfcRelConnectsElements":
|
if rel.is_a() == "IfcRelConnectsElements":
|
||||||
ifcopenshell.api.geometry.disconnect_element(
|
related_element = rel.RelatedElement
|
||||||
ifc_file,
|
if related_element.is_a() == "IfcWall":
|
||||||
relating_element=rel.RelatingElement,
|
layer2_objs.append(tool.Ifc.get_object(related_element))
|
||||||
related_element=element,
|
|
||||||
)
|
|
||||||
layer2_objs.append(obj)
|
|
||||||
|
|
||||||
if layer2_objs:
|
if layer2_objs:
|
||||||
tool.Model.recalculate_walls(layer2_objs)
|
tool.Model.recalculate_walls(layer2_objs)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -437,80 +456,126 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
layer2_objs: list[bpy.types.Object] = []
|
layer2_objs: list[bpy.types.Object] = []
|
||||||
x_angle = 0 if tool.Cad.is_x(self.x_angle, 0, tolerance=0.001) else self.x_angle
|
|
||||||
x_angle = 0 if tool.Cad.is_x(self.x_angle, pi, tolerance=0.001) else self.x_angle
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
|
||||||
selected_objs = tool.Model.get_selected_mesh_ifc_objects()
|
|
||||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
x_angle = self.x_angle
|
||||||
|
|
||||||
for obj in selected_objs:
|
for obj in context.selected_objects:
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
assert element
|
if not element:
|
||||||
|
continue
|
||||||
|
|
||||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
if not representation:
|
if not representation:
|
||||||
continue
|
continue
|
||||||
extrusion = tool.Model.get_extrusion(representation)
|
extrusion = tool.Model.get_extrusion(representation)
|
||||||
if not extrusion:
|
if not extrusion:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Get current object rotation matrix
|
||||||
|
obj_rotation = obj.matrix_world.to_3x3()
|
||||||
|
|
||||||
|
# Get current extrusion direction in LOCAL coordinates
|
||||||
|
current_local_direction = Vector(extrusion.ExtrudedDirection.DirectionRatios)
|
||||||
|
if current_local_direction.length == 0:
|
||||||
|
current_local_direction = Vector((0, 0, 1))
|
||||||
|
current_local_direction_normalized = current_local_direction.normalized()
|
||||||
|
|
||||||
|
# Calculate what the current extrusion direction is in WORLD coordinates
|
||||||
|
current_world_direction = obj_rotation @ current_local_direction_normalized
|
||||||
|
|
||||||
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
|
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
|
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
|
||||||
|
|
||||||
|
# Calculate the NEW local extrusion direction based on x_angle
|
||||||
|
new_local_direction = Vector((0.0, sin(x_angle), cos(x_angle)))
|
||||||
|
|
||||||
|
# Check if extrusion direction is actually changing
|
||||||
|
current_local_norm = current_local_direction_normalized
|
||||||
|
new_local_norm = new_local_direction.normalized()
|
||||||
|
|
||||||
|
# Compare the LOCAL directions
|
||||||
|
local_direction_changed = (new_local_norm - current_local_norm).length > 1e-6
|
||||||
|
|
||||||
if tool.Model.get_usage_type(element) == "LAYER2":
|
if tool.Model.get_usage_type(element) == "LAYER2":
|
||||||
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
|
||||||
depth = extrusion.Depth / abs(1 / cos(existing_x_angle))
|
depth = extrusion.Depth / abs(1 / cos(existing_x_angle))
|
||||||
perpendicular_depth = depth * abs(1 / cos(x_angle))
|
perpendicular_depth = depth * abs(1 / cos(x_angle))
|
||||||
extrusion.ExtrudedDirection.DirectionRatios = (0.0, sin(x_angle), cos(x_angle))
|
|
||||||
layer2_objs.append(obj)
|
# Update extrusion direction
|
||||||
|
if local_direction_changed:
|
||||||
|
extrusion.ExtrudedDirection.DirectionRatios = tuple(new_local_direction)
|
||||||
|
|
||||||
|
# Always update depth
|
||||||
extrusion.Depth = perpendicular_depth
|
extrusion.Depth = perpendicular_depth
|
||||||
|
layer2_objs.append(obj)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if tool.Model.get_usage_type(element) == "LAYER3":
|
if tool.Model.get_usage_type(element) == "LAYER3":
|
||||||
existing_x_angle = obj.rotation_euler.x
|
# For slabs, handle polyline scaling
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
|
existing_obj_x_angle = obj.rotation_euler.x
|
||||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
|
existing_obj_x_angle = 0 if tool.Cad.is_x(existing_obj_x_angle, 0, tolerance=0.001) else existing_obj_x_angle
|
||||||
|
existing_obj_x_angle = 0 if tool.Cad.is_x(existing_obj_x_angle, pi, tolerance=0.001) else existing_obj_x_angle
|
||||||
|
|
||||||
|
# Scale the polyline coordinates
|
||||||
coord_list = builder.get_polyline_coords(extrusion.SweptArea.OuterCurve)
|
coord_list = builder.get_polyline_coords(extrusion.SweptArea.OuterCurve)
|
||||||
coord_list = [
|
coord_list = [
|
||||||
(p[0], p[1] * abs(cos(existing_x_angle))) for p in coord_list
|
(p[0], p[1] * abs(cos(existing_x_angle))) for p in coord_list
|
||||||
] # Reset the transformation and returns to the original points with 0 degrees
|
] # Reset the transformation
|
||||||
coord_list = [
|
coord_list = [
|
||||||
(p[0], p[1] * abs(1 / cos(x_angle))) for p in coord_list
|
(p[0], p[1] * abs(1 / cos(x_angle))) for p in coord_list
|
||||||
] # Apply the transformation for the new x_angle
|
] # Apply the transformation for the new x_angle
|
||||||
builder.set_polyline_coords(extrusion.SweptArea.OuterCurve, coord_list)
|
builder.set_polyline_coords(extrusion.SweptArea.OuterCurve, coord_list)
|
||||||
|
|
||||||
# The extrusion direction calculated previously default to the positive direction
|
# Calculate new extrusion direction with direction sense
|
||||||
# Here we set the extrusion direction to negative if that's the case
|
base_local_direction = Vector((0.0, sin(x_angle), cos(x_angle)))
|
||||||
direction_ratios = Vector((0.0, sin(x_angle), cos(x_angle)))
|
|
||||||
# direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios)
|
|
||||||
layer_params = tool.Model.get_material_layer_parameters(element)
|
layer_params = tool.Model.get_material_layer_parameters(element)
|
||||||
perpendicular_depth = layer_params["thickness"] * abs(1 / cos(x_angle)) / unit_scale
|
perpendicular_depth = layer_params["thickness"] * abs(1 / cos(x_angle)) / unit_scale
|
||||||
perpendicular_offset = layer_params["offset"] * abs(1 / cos(x_angle)) / unit_scale
|
perpendicular_offset = layer_params["offset"] * abs(1 / cos(x_angle)) / unit_scale
|
||||||
offset_direction = direction_ratios.copy()
|
offset_direction = base_local_direction.copy()
|
||||||
|
|
||||||
# Check angle and z direction to determine whether the extrusion direction is positive or negative
|
# Apply direction sense
|
||||||
if (abs(x_angle) < (pi / 2) and direction_ratios.z > 0) or (
|
final_local_direction = base_local_direction.copy()
|
||||||
abs(x_angle) > (pi / 2) and direction_ratios.z < 0
|
if (abs(x_angle) < (pi / 2) and base_local_direction.z > 0) or (
|
||||||
|
abs(x_angle) > (pi / 2) and base_local_direction.z < 0
|
||||||
):
|
):
|
||||||
# The extrusion direction is positive. If the layer_parameter is set to negative,
|
|
||||||
# then the we change the extrusion direction.
|
|
||||||
if layer_params["direction_sense"] == "NEGATIVE":
|
if layer_params["direction_sense"] == "NEGATIVE":
|
||||||
direction_ratios *= -1
|
final_local_direction *= -1
|
||||||
elif ((x_angle) > (pi / 2) and direction_ratios.z > 0) or (
|
elif (x_angle > (pi / 2) and base_local_direction.z > 0) or (
|
||||||
(x_angle) < (pi / 2) and direction_ratios.z < 0
|
x_angle < (pi / 2) and base_local_direction.z < 0
|
||||||
):
|
):
|
||||||
# The extrusion direction is negative. If the layer_parameter is set to positive,
|
|
||||||
# then the we change the extrusion direction.
|
|
||||||
# then the we change the extrusion direction. And the offset direction should remain positive
|
|
||||||
# for either direction sense, so we change it.
|
|
||||||
offset_direction *= -1
|
offset_direction *= -1
|
||||||
if layer_params["direction_sense"] == "POSITIVE":
|
if layer_params["direction_sense"] == "POSITIVE":
|
||||||
direction_ratios *= -1
|
final_local_direction *= -1
|
||||||
|
|
||||||
extrusion.ExtrudedDirection.DirectionRatios = tuple(direction_ratios)
|
# Check if extrusion direction actually changed
|
||||||
|
final_local_norm = final_local_direction.normalized()
|
||||||
|
local_direction_changed = (final_local_norm - current_local_norm).length > 1e-6
|
||||||
|
|
||||||
|
# Update extrusion properties
|
||||||
|
extrusion.ExtrudedDirection.DirectionRatios = tuple(final_local_direction)
|
||||||
extrusion.Depth = perpendicular_depth
|
extrusion.Depth = perpendicular_depth
|
||||||
|
|
||||||
if extrusion.Position or perpendicular_offset != 0:
|
if extrusion.Position or perpendicular_offset != 0:
|
||||||
position = offset_direction * perpendicular_offset
|
position = offset_direction * perpendicular_offset
|
||||||
tool.Model.add_extrusion_position(extrusion, position)
|
tool.Model.add_extrusion_position(extrusion, position)
|
||||||
|
|
||||||
|
# Adjust object rotation if extrusion direction changed
|
||||||
|
if local_direction_changed:
|
||||||
|
# Calculate what the NEW world direction would be with current object rotation
|
||||||
|
expected_new_world_direction = obj_rotation @ final_local_norm
|
||||||
|
|
||||||
|
# The rotation needed is from expected_new_world_direction to current_world_direction
|
||||||
|
rotation_axis = expected_new_world_direction.cross(current_world_direction)
|
||||||
|
if rotation_axis.length > 1e-6:
|
||||||
|
rotation_axis.normalize()
|
||||||
|
dot_product = expected_new_world_direction.dot(current_world_direction)
|
||||||
|
angle = acos(min(max(dot_product, -1), 1))
|
||||||
|
|
||||||
|
# Create and apply rotation matrix
|
||||||
|
rotation_matrix = Matrix.Rotation(angle, 4, rotation_axis)
|
||||||
|
obj.matrix_world = rotation_matrix @ obj.matrix_world
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
bonsai.core.geometry.switch_representation(
|
bonsai.core.geometry.switch_representation(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
@@ -519,12 +584,6 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
representation=representation,
|
representation=representation,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Object rotation
|
|
||||||
current_z_rot = obj.rotation_euler.z
|
|
||||||
rot_mat = mathutils.Matrix.Rotation(x_angle, 4, "X")
|
|
||||||
obj.rotation_euler = rot_mat.to_euler()
|
|
||||||
obj.rotation_euler.z = current_z_rot
|
|
||||||
|
|
||||||
if layer2_objs:
|
if layer2_objs:
|
||||||
tool.Model.recalculate_walls(layer2_objs)
|
tool.Model.recalculate_walls(layer2_objs)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -1022,6 +1081,7 @@ class DumbWallGenerator:
|
|||||||
obj=obj,
|
obj=obj,
|
||||||
representation=representation,
|
representation=representation,
|
||||||
)
|
)
|
||||||
|
|
||||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||||
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties={"Engine": "Bonsai.DumbLayer2"})
|
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties={"Engine": "Bonsai.DumbLayer2"})
|
||||||
material = ifcopenshell.util.element.get_material(element)
|
material = ifcopenshell.util.element.get_material(element)
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ class Collector(bonsai.core.tool.Collector):
|
|||||||
# Note that tool.Geometry.is_locked is only checked within the if
|
# Note that tool.Geometry.is_locked is only checked within the if
|
||||||
# statements for efficiency as it is a slow check.
|
# statements for efficiency as it is a slow check.
|
||||||
tool.Geometry.lock_scale(obj)
|
tool.Geometry.lock_scale(obj)
|
||||||
if element.is_a("IfcSlab"):
|
|
||||||
tool.Geometry.lock_rotation(obj, x=True)
|
|
||||||
|
|
||||||
if element.is_a("IfcGridAxis"):
|
if element.is_a("IfcGridAxis"):
|
||||||
if tool.Geometry.is_locked(element):
|
if tool.Geometry.is_locked(element):
|
||||||
|
|||||||
@@ -1030,28 +1030,57 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
sense_factor = 1
|
sense_factor = 1
|
||||||
else:
|
else:
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
if len(layer_set.MaterialLayers) == 1:
|
if len(layer_set.MaterialLayers) == 1:
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
bm.from_mesh(mesh)
|
bm.from_mesh(mesh)
|
||||||
|
|
||||||
prev_co = None
|
prev_co = None
|
||||||
|
advance_direction = None # Will store direction to advance planes
|
||||||
|
|
||||||
if not usage:
|
if not usage:
|
||||||
sense_factor = 1 # Assume the extrusion vector points in the direction sense
|
sense_factor = 1
|
||||||
no = cls.get_extrusion_vector(element).normalized()
|
no = cls.get_extrusion_vector(element).normalized()
|
||||||
co = Vector((0.0, 0.0, offset))
|
co = Vector((0.0, 0.0, offset))
|
||||||
|
advance_direction = no
|
||||||
elif usage.LayerSetDirection == "AXIS2":
|
elif usage.LayerSetDirection == "AXIS2":
|
||||||
co = Vector((0.0, offset, 0.0))
|
co = Vector((0.0, offset, 0.0))
|
||||||
no = cls.get_extrusion_vector(element).normalized()
|
|
||||||
no = no.cross(Vector([1.0, 0.0, 0.0]))
|
# Get LOCAL extrusion direction
|
||||||
|
local_extrusion = Vector([0.0, 0.0, 1.0])
|
||||||
|
if body := ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW"):
|
||||||
|
for item in ifcopenshell.util.representation.resolve_representation(body).Items:
|
||||||
|
while item.is_a("IfcBooleanResult"):
|
||||||
|
item = item.FirstOperand
|
||||||
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
|
local_extrusion = Vector(item.ExtrudedDirection.DirectionRatios).normalized()
|
||||||
|
break
|
||||||
|
|
||||||
|
# Thickness direction: perpendicular to extrusion and length
|
||||||
|
thickness_dir = local_extrusion.cross(Vector([1.0, 0.0, 0.0])).normalized()
|
||||||
|
|
||||||
|
# Ensure it points in POSITIVE Y (through wall thickness, not backwards)
|
||||||
|
if thickness_dir.y < 0:
|
||||||
|
thickness_dir = -thickness_dir
|
||||||
|
|
||||||
|
no = thickness_dir
|
||||||
|
advance_direction = thickness_dir
|
||||||
elif usage.LayerSetDirection == "AXIS3":
|
elif usage.LayerSetDirection == "AXIS3":
|
||||||
co = Vector((0.0, 0.0, offset))
|
co = Vector((0.0, 0.0, offset))
|
||||||
no = cls.get_extrusion_vector(element).normalized()
|
no = cls.get_extrusion_vector(element).normalized()
|
||||||
no = Vector([0.0, 0.0, 1.0])
|
no = Vector([0.0, 0.0, 1.0])
|
||||||
|
advance_direction = no
|
||||||
elif usage.LayerSetDirection == "AXIS1":
|
elif usage.LayerSetDirection == "AXIS1":
|
||||||
co = Vector((0.0, 0.0, offset))
|
co = Vector((0.0, 0.0, offset))
|
||||||
no = cls.get_extrusion_vector(element).normalized()
|
no = cls.get_extrusion_vector(element).normalized()
|
||||||
no = Vector([1.0, 0.0, 0.0])
|
no = Vector([1.0, 0.0, 0.0])
|
||||||
|
advance_direction = no
|
||||||
|
|
||||||
no *= sense_factor
|
no *= sense_factor
|
||||||
|
advance_direction *= sense_factor
|
||||||
|
|
||||||
# Cache this
|
# Cache this
|
||||||
body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||||
styles = {}
|
styles = {}
|
||||||
@@ -1059,20 +1088,25 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
for i, material in enumerate(mesh.materials):
|
for i, material in enumerate(mesh.materials):
|
||||||
if style := tool.Ifc.get_entity(material):
|
if style := tool.Ifc.get_entity(material):
|
||||||
styles[style] = i
|
styles[style] = i
|
||||||
|
|
||||||
last_i = len(layer_set.MaterialLayers) - 1
|
last_i = len(layer_set.MaterialLayers) - 1
|
||||||
for i, layer in enumerate(layer_set.MaterialLayers):
|
for i, layer in enumerate(layer_set.MaterialLayers):
|
||||||
if i != last_i:
|
if i != last_i:
|
||||||
prev_co = co.copy()
|
prev_co = co.copy()
|
||||||
co += no * layer.LayerThickness * cls.unit_scale
|
# Use advance_direction (not no) to move planes!
|
||||||
|
co += advance_direction * layer.LayerThickness * cls.unit_scale
|
||||||
|
|
||||||
bisect_geom = bmesh.ops.bisect_plane(
|
bisect_geom = bmesh.ops.bisect_plane(
|
||||||
bm, geom=bm.verts[:] + bm.edges[:] + bm.faces[:], dist=0.0001, plane_co=co, plane_no=no
|
bm, geom=bm.verts[:] + bm.edges[:] + bm.faces[:], dist=0.0001, plane_co=co, plane_no=no
|
||||||
)
|
)
|
||||||
bmesh.ops.duplicate(bm, geom=bisect_geom["geom_cut"])
|
bmesh.ops.duplicate(bm, geom=bisect_geom["geom_cut"])
|
||||||
|
|
||||||
if not (style := ifcopenshell.util.representation.get_material_style(layer.Material, body)):
|
if not (style := ifcopenshell.util.representation.get_material_style(layer.Material, body)):
|
||||||
continue
|
continue
|
||||||
if (material_index := styles.get(style, None)) is None:
|
if (material_index := styles.get(style, None)) is None:
|
||||||
material_index = len(mesh.materials)
|
material_index = len(mesh.materials)
|
||||||
mesh.materials.append(tool.Ifc.get_object(style))
|
mesh.materials.append(tool.Ifc.get_object(style))
|
||||||
|
|
||||||
if i == last_i:
|
if i == last_i:
|
||||||
for face in bisect_geom["geom"]:
|
for face in bisect_geom["geom"]:
|
||||||
if isinstance(face, bmesh.types.BMFace):
|
if isinstance(face, bmesh.types.BMFace):
|
||||||
@@ -1097,13 +1131,35 @@ class Loader(bonsai.core.tool.Loader):
|
|||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_extrusion_vector(cls, wall):
|
def get_extrusion_vector(cls, element):
|
||||||
if body := ifcopenshell.util.representation.get_representation(wall, "Model", "Body", "MODEL_VIEW"):
|
"""Get the extrusion direction in WORLD coordinates (accounting for object rotation)"""
|
||||||
|
if body := ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW"):
|
||||||
for item in ifcopenshell.util.representation.resolve_representation(body).Items:
|
for item in ifcopenshell.util.representation.resolve_representation(body).Items:
|
||||||
while item.is_a("IfcBooleanResult"):
|
while item.is_a("IfcBooleanResult"):
|
||||||
item = item.FirstOperand
|
item = item.FirstOperand
|
||||||
if item.is_a("IfcExtrudedAreaSolid"):
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
return Vector(item.ExtrudedDirection.DirectionRatios)
|
local_direction = Vector(item.ExtrudedDirection.DirectionRatios)
|
||||||
|
|
||||||
|
# Transform to world coordinates using object rotation
|
||||||
|
obj = tool.Ifc.get_object(element)
|
||||||
|
if obj:
|
||||||
|
# Apply object rotation to get actual world direction
|
||||||
|
world_direction = obj.matrix_world.to_3x3() @ local_direction
|
||||||
|
return world_direction
|
||||||
|
|
||||||
|
return local_direction
|
||||||
|
return Vector([0.0, 0.0, 1.0])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_local_extrusion_vector(cls, element):
|
||||||
|
"""Get the extrusion direction in LOCAL coordinates (from IFC, no object rotation)"""
|
||||||
|
if body := ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW"):
|
||||||
|
for item in ifcopenshell.util.representation.resolve_representation(body).Items:
|
||||||
|
while item.is_a("IfcBooleanResult"):
|
||||||
|
item = item.FirstOperand
|
||||||
|
if item.is_a("IfcExtrudedAreaSolid"):
|
||||||
|
local_direction = Vector(item.ExtrudedDirection.DirectionRatios)
|
||||||
|
return local_direction
|
||||||
return Vector([0.0, 0.0, 1.0])
|
return Vector([0.0, 0.0, 1.0])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -100,35 +100,45 @@ class Usecase:
|
|||||||
size = self.convert_si_to_unit(1)
|
size = self.convert_si_to_unit(1)
|
||||||
points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0))
|
points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0))
|
||||||
if self.polyline:
|
if self.polyline:
|
||||||
points = [
|
# Only scale polyline if we have actual slope
|
||||||
(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.x_angle))))
|
if self.x_angle and abs(self.x_angle) > 1e-6:
|
||||||
for p in self.polyline
|
points = [
|
||||||
]
|
(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.x_angle))))
|
||||||
|
for p in self.polyline
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
points = [
|
||||||
|
(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1]))
|
||||||
|
for p in self.polyline
|
||||||
|
]
|
||||||
|
|
||||||
if self.file.schema == "IFC2X3":
|
if self.file.schema == "IFC2X3":
|
||||||
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
|
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
|
||||||
else:
|
else:
|
||||||
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points))
|
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points))
|
||||||
|
|
||||||
if self.x_angle:
|
if self.x_angle:
|
||||||
direction_ratios = (0.0, sin(self.x_angle), cos(self.x_angle))
|
direction_ratios = (0.0, sin(self.x_angle), cos(self.x_angle))
|
||||||
else:
|
else:
|
||||||
direction_ratios = (0.0, 0.0, 1.0)
|
direction_ratios = (0.0, 0.0, 1.0)
|
||||||
|
|
||||||
offset_direction = direction_ratios # offset direction doesn't change if direction_sense is negative
|
|
||||||
extrusion_direction = self.file.createIfcDirection(direction_ratios)
|
extrusion_direction = self.file.createIfcDirection(direction_ratios)
|
||||||
if self.direction_sense == "NEGATIVE":
|
|
||||||
direction_ratios = tuple(-n for n in direction_ratios)
|
# Calculate depth based on extrusion angle
|
||||||
extrusion_direction = self.file.createIfcDirection(direction_ratios)
|
extrusion_angle = abs(self.x_angle) if self.x_angle else 0
|
||||||
|
if extrusion_angle > 1e-6:
|
||||||
perpendicular_offset = self.convert_si_to_unit(self.offset) * abs(1 / cos(self.x_angle))
|
perpendicular_depth = self.convert_si_to_unit(self.depth) * abs(1 / cos(extrusion_angle))
|
||||||
perpendicular_depth = self.convert_si_to_unit(self.depth) * abs(1 / cos(self.x_angle))
|
perpendicular_offset = self.convert_si_to_unit(self.offset) * abs(1 / cos(extrusion_angle))
|
||||||
|
else:
|
||||||
|
perpendicular_depth = self.convert_si_to_unit(self.depth)
|
||||||
|
perpendicular_offset = self.convert_si_to_unit(self.offset)
|
||||||
|
|
||||||
position = None
|
position = None
|
||||||
# default position for IFC2X3 where .Position is not optional
|
|
||||||
if self.file.schema == "IFC2X3" or self.offset != 0:
|
if self.file.schema == "IFC2X3" or self.offset != 0:
|
||||||
position_vector = (
|
position_vector = (
|
||||||
offset_direction[0] * perpendicular_offset,
|
direction_ratios[0] * perpendicular_offset,
|
||||||
offset_direction[1] * perpendicular_offset,
|
direction_ratios[1] * perpendicular_offset,
|
||||||
offset_direction[2] * perpendicular_offset,
|
direction_ratios[2] * perpendicular_offset,
|
||||||
)
|
)
|
||||||
position = self.file.createIfcAxis2Placement3D(
|
position = self.file.createIfcAxis2Placement3D(
|
||||||
self.file.createIfcCartesianPoint(position_vector),
|
self.file.createIfcCartesianPoint(position_vector),
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ class Usecase:
|
|||||||
def create_item(self) -> ifcopenshell.entity_instance:
|
def create_item(self) -> ifcopenshell.entity_instance:
|
||||||
length = self.convert_si_to_unit(self.settings["length"])
|
length = self.convert_si_to_unit(self.settings["length"])
|
||||||
thickness = self.convert_si_to_unit(self.settings["thickness"])
|
thickness = self.convert_si_to_unit(self.settings["thickness"])
|
||||||
thickness *= 1 / cos(self.settings["x_angle"])
|
|
||||||
if self.settings["direction_sense"] == "NEGATIVE":
|
if self.settings["direction_sense"] == "NEGATIVE":
|
||||||
thickness *= -1
|
thickness *= -1
|
||||||
points = (
|
points = (
|
||||||
@@ -113,7 +112,7 @@ class Usecase:
|
|||||||
self.file.createIfcDirection((1.0, 0.0, 0.0)),
|
self.file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||||
),
|
),
|
||||||
extrusion_direction,
|
extrusion_direction,
|
||||||
self.convert_si_to_unit(self.settings["height"]) * abs(1 / cos(self.settings["x_angle"])),
|
self.convert_si_to_unit(self.settings["height"]),
|
||||||
)
|
)
|
||||||
if self.settings["booleans"]:
|
if self.settings["booleans"]:
|
||||||
extrusion = self.apply_booleans(extrusion)
|
extrusion = self.apply_booleans(extrusion)
|
||||||
|
|||||||
Reference in New Issue
Block a user