This commit is contained in:
Andrej730
2025-01-16 16:58:40 +05:00
parent 2859e9632e
commit 6fb0f5e63a
2 changed files with 36 additions and 22 deletions
+27 -15
View File
@@ -45,7 +45,7 @@ from bpy.types import SpaceView3D
from bpy.props import FloatProperty from bpy.props import FloatProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add from bpy_extras.object_utils import AddObjectHelper, object_data_add
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
from typing import Union, Optional, Any, cast from typing import Union, Optional, Any, cast, Sequence
class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
@@ -63,19 +63,21 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
class FilledOpeningGenerator: class FilledOpeningGenerator:
def generate( def generate(
self, self,
filling_obj: Union[bpy.types.Object, None], filling_obj: bpy.types.Object,
voided_obj: Union[bpy.types.Object, None], voided_obj: bpy.types.Object,
target: Optional[Vector] = None, target: Optional[Vector] = None,
) -> None: ) -> Union[None, str]:
"""
:param target: Target opening position. If ommited, cursor position is used.
:return: None if there was no errors, otherwise returns a string with error message.
"""
props = bpy.context.scene.BIMModelProperties props = bpy.context.scene.BIMModelProperties
opening_thickness_si = 0.0 opening_thickness_si = 0.0
filling = tool.Ifc.get_entity(filling_obj) filling = tool.Ifc.get_entity(filling_obj)
element = tool.Ifc.get_entity(voided_obj) element = tool.Ifc.get_entity(voided_obj)
if not voided_obj or not filling_obj: assert filling and element
return
if filling.FillsVoids: if filling.FillsVoids:
ifcopenshell.api.run( ifcopenshell.api.run(
"void.remove_opening", tool.Ifc.get(), opening=filling.FillsVoids[0].RelatingOpeningElement "void.remove_opening", tool.Ifc.get(), opening=filling.FillsVoids[0].RelatingOpeningElement
@@ -94,7 +96,7 @@ class FilledOpeningGenerator:
target = filling_obj.matrix_world.translation.copy() target = filling_obj.matrix_world.translation.copy()
raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.5) raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.5)
if not raycast[0]: if not raycast[0]:
return return "TARGET is too far away from the voided object's mesh."
# In this prototype, we assume openings are only added to axis-based elements # In this prototype, we assume openings are only added to axis-based elements
layers = tool.Model.get_material_layer_parameters(element) layers = tool.Model.get_material_layer_parameters(element)
@@ -121,6 +123,8 @@ class FilledOpeningGenerator:
new_matrix.translation.xyz = voided_obj.matrix_world @ local_position_on_voided_obj new_matrix.translation.xyz = voided_obj.matrix_world @ local_position_on_voided_obj
rotation_matrix = Matrix.Rotation(radians(-90), 4, "X") rotation_matrix = Matrix.Rotation(radians(-90), 4, "X")
new_matrix @= rotation_matrix new_matrix @= rotation_matrix
else:
assert False, f"Unexpected layer set direction: {layers['layer_set_direction']}"
filling_obj.matrix_world = new_matrix filling_obj.matrix_world = new_matrix
bpy.context.view_layer.update() bpy.context.view_layer.update()
@@ -149,6 +153,7 @@ class FilledOpeningGenerator:
representation = ifcopenshell.util.representation.get_representation( representation = ifcopenshell.util.representation.get_representation(
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
) )
assert representation
representation = ifcopenshell.util.representation.resolve_representation(representation) representation = ifcopenshell.util.representation.resolve_representation(representation)
else: else:
representation = self.generate_opening_from_filling( representation = self.generate_opening_from_filling(
@@ -175,8 +180,10 @@ class FilledOpeningGenerator:
for voided_obj in voided_objs: for voided_obj in voided_objs:
if voided_obj.data: if voided_obj.data:
voided_element = tool.Ifc.get_entity(voided_obj) voided_element = tool.Ifc.get_entity(voided_obj)
assert voided_element
context = tool.Geometry.get_active_representation_context(voided_obj) context = tool.Geometry.get_active_representation_context(voided_obj)
representation = tool.Geometry.get_representation_by_context(voided_element, context) representation = tool.Geometry.get_representation_by_context(voided_element, context)
assert representation
bonsai.core.geometry.switch_representation( bonsai.core.geometry.switch_representation(
tool.Ifc, tool.Ifc,
@@ -273,11 +280,12 @@ class FilledOpeningGenerator:
) )
filling_obj = tool.Ifc.get_object(filling_type) filling_obj = tool.Ifc.get_object(filling_type)
context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
assert context
if profile: if profile:
profile = ifcopenshell.util.representation.resolve_representation(profile) profile = ifcopenshell.util.representation.resolve_representation(profile)
def get_curve_2d_from_3d(profile): def get_curve_2d_from_3d(profile: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
if len(profile.Items) == 1: if len(profile.Items) == 1:
curve_3d = profile.Items[0] curve_3d = profile.Items[0]
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
@@ -944,7 +952,7 @@ class UpdateOpeningsFocus(Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
def hide_openings(context, objects): def hide_openings(context: bpy.types.Context, objects: Sequence[bpy.types.Object]) -> None:
objects_to_remove = set() objects_to_remove = set()
for opening_prop in context.scene.BIMModelProperties.openings: for opening_prop in context.scene.BIMModelProperties.openings:
opening_obj = opening_prop.obj opening_obj = opening_prop.obj
@@ -1008,10 +1016,12 @@ class EditOpenings(Operator, tool.Ifc.Operator):
bpy.ops.bim.update_openings_focus() bpy.ops.bim.update_openings_focus()
return {"FINISHED"} return {"FINISHED"}
def get_buildings_and_openings(self, context): def get_buildings_and_openings(
self, context: bpy.types.Context
) -> tuple[set[bpy.types.Object], set[ifcopenshell.entity_instance]]:
props = context.scene.BIMModelProperties props = context.scene.BIMModelProperties
building_objs = set() building_objs: set[bpy.types.Object] = set()
opening_elements = set() opening_elements: set[ifcopenshell.entity_instance] = set()
objects_to_remove = set() objects_to_remove = set()
if self.apply_all: if self.apply_all:
for opening_prop in props.openings: for opening_prop in props.openings:
@@ -1045,8 +1055,10 @@ class EditOpenings(Operator, tool.Ifc.Operator):
tool.Blender.remove_data_blocks(objects_to_remove, remove_unused_data=True) tool.Blender.remove_data_blocks(objects_to_remove, remove_unused_data=True)
return building_objs, opening_elements return building_objs, opening_elements
def edit_openings(self, building_objs, opening_elements): def edit_openings(
objects_to_remove = set() self, building_objs: set[bpy.types.Object], opening_elements: set[ifcopenshell.entity_instance]
) -> None:
objects_to_remove: set[bpy.types.Object] = set()
for opening_element in opening_elements: for opening_element in opening_elements:
opening_obj = tool.Ifc.get_object(opening_element) opening_obj = tool.Ifc.get_object(opening_element)
+9 -7
View File
@@ -662,26 +662,28 @@ class Model(bonsai.core.tool.Model):
return "PROFILE" return "PROFILE"
@classmethod @classmethod
def get_wall_axis(cls, obj: bpy.types.Object, layers: Optional[dict[str, Any]] = None) -> dict[str, Any]: def get_wall_axis(
cls, obj: bpy.types.Object, layers: Optional[dict[str, Any]] = None
) -> dict[str, tuple[Vector, Vector]]:
x_values = [v[0] for v in obj.bound_box] x_values = [v[0] for v in obj.bound_box]
min_x = min(x_values) min_x = min(x_values)
max_x = max(x_values) max_x = max(x_values)
axes = {} axes = {}
if layers: if layers:
axes = { axes = {
"base": [ "base": (
(obj.matrix_world @ Vector((min_x, layers["offset"], 0.0))).to_2d(), (obj.matrix_world @ Vector((min_x, layers["offset"], 0.0))).to_2d(),
(obj.matrix_world @ Vector((max_x, layers["offset"], 0.0))).to_2d(), (obj.matrix_world @ Vector((max_x, layers["offset"], 0.0))).to_2d(),
], ),
"side": [ "side": (
(obj.matrix_world @ Vector((min_x, layers["offset"] + layers["thickness"], 0.0))).to_2d(), (obj.matrix_world @ Vector((min_x, layers["offset"] + layers["thickness"], 0.0))).to_2d(),
(obj.matrix_world @ Vector((max_x, layers["offset"] + layers["thickness"], 0.0))).to_2d(), (obj.matrix_world @ Vector((max_x, layers["offset"] + layers["thickness"], 0.0))).to_2d(),
], ),
} }
axes["reference"] = [ axes["reference"] = (
(obj.matrix_world @ Vector((min_x, 0.0, 0.0))).to_2d(), (obj.matrix_world @ Vector((min_x, 0.0, 0.0))).to_2d(),
(obj.matrix_world @ Vector((max_x, 0.0, 0.0))).to_2d(), (obj.matrix_world @ Vector((max_x, 0.0, 0.0))).to_2d(),
] )
return axes return axes
@classmethod @classmethod