mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Half complete "add window" tool prototype
This commit is contained in:
@@ -128,6 +128,7 @@ class EditGeoreferencing(bpy.types.Operator):
|
||||
|
||||
def export_attributes(self, attributes, prop):
|
||||
if not prop.is_null and prop.data_type == "string":
|
||||
# We store our floats as string to prevent single precision data loss
|
||||
attributes[prop.name] = float(prop.string_value)
|
||||
return True
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi
|
||||
from mathutils import Vector
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
@@ -56,40 +57,96 @@ class AddElementOpening(bpy.types.Operator):
|
||||
bl_idname = "bim.add_element_opening"
|
||||
bl_label = "Add Element Opening"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
voided_building_element: bpy.props.StringProperty()
|
||||
filling_building_element: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
selected_objs = context.selected_objects
|
||||
if len(selected_objs) == 0 or not context.active_object:
|
||||
voided_obj = self.get_voided_building_element(context)
|
||||
filling_obj = self.get_filling_building_element(context)
|
||||
|
||||
if not voided_obj:
|
||||
return {"FINISHED"}
|
||||
obj = context.active_object
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
local_location = obj.matrix_world.inverted() @ context.scene.cursor.location
|
||||
raycast = obj.closest_point_on_mesh(local_location, distance=0.01)
|
||||
|
||||
element = IfcStore.get_file().by_id(voided_obj.BIMObjectProperties.ifc_definition_id)
|
||||
local_location = voided_obj.matrix_world.inverted() @ context.scene.cursor.location
|
||||
raycast = voided_obj.closest_point_on_mesh(local_location, distance=0.01)
|
||||
if not raycast[0]:
|
||||
return {"FINISHED"}
|
||||
|
||||
# The opening shall be based on the smallest bounding dimension of the element
|
||||
dimension = min(obj.dimensions)
|
||||
bpy.ops.mesh.primitive_cube_add(size=dimension * 2)
|
||||
opening = context.selected_objects[0]
|
||||
if filling_obj:
|
||||
opening = self.generate_opening_from_filling(filling_obj)
|
||||
else:
|
||||
# The opening shall be based on the smallest bounding dimension of the element
|
||||
dimension = min(voided_obj.dimensions)
|
||||
bpy.ops.mesh.primitive_cube_add(size=dimension * 2)
|
||||
opening = context.selected_objects[0]
|
||||
|
||||
# Place the opening in the middle of the element
|
||||
global_location = obj.matrix_world @ raycast[1]
|
||||
normal = raycast[2]
|
||||
normal.negate()
|
||||
global_normal = obj.matrix_world.to_quaternion() @ normal
|
||||
opening.location = global_location + (global_normal * (dimension / 2))
|
||||
# Place the opening in the middle of the element
|
||||
global_location = voided_obj.matrix_world @ raycast[1]
|
||||
normal = raycast[2]
|
||||
normal.negate()
|
||||
global_normal = voided_obj.matrix_world.to_quaternion() @ normal
|
||||
opening.location = global_location + (global_normal * (dimension / 2))
|
||||
opening.rotation_euler = voided_obj.rotation_euler
|
||||
opening.name = "Opening"
|
||||
|
||||
opening.rotation_euler = obj.rotation_euler
|
||||
opening.name = "Opening"
|
||||
bpy.ops.bim.add_opening(opening=opening.name, obj=obj.name)
|
||||
bpy.ops.bim.add_opening(opening=opening.name, obj=voided_obj.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_voided_building_element(self, context):
|
||||
obj = None
|
||||
if self.voided_building_element:
|
||||
obj = bpy.data.objects.get(self.voided_building_element)
|
||||
else:
|
||||
total_selected = len(context.selected_objects)
|
||||
if total_selected == 1 or total_selected == 2:
|
||||
obj = context.active_object
|
||||
if obj and obj.BIMObjectProperties.ifc_definition_id:
|
||||
return obj
|
||||
|
||||
def get_filling_building_element(self, context):
|
||||
obj = None
|
||||
if self.filling_building_element:
|
||||
obj = bpy.data.objects.get(self.filling_building_element)
|
||||
else:
|
||||
total_selected = len(context.selected_objects)
|
||||
if total_selected == 2:
|
||||
if context.selected_objects[0] == context.active_object:
|
||||
obj = context.selected_objects[1]
|
||||
else:
|
||||
obj = context.selected_objects[0]
|
||||
if obj and obj.BIMObjectProperties.ifc_definition_id:
|
||||
return obj
|
||||
|
||||
def generate_opening_from_filling(self, filling_obj):
|
||||
x, y, z = filling_obj.dimensions
|
||||
verts = [
|
||||
Vector((0, 0, 0)),
|
||||
Vector((0, 0, z)),
|
||||
Vector((0, y, 0)),
|
||||
Vector((0, y, z)),
|
||||
Vector((x, 0, 0)),
|
||||
Vector((x, 0, z)),
|
||||
Vector((x, y, 0)),
|
||||
Vector((x, y, z)),
|
||||
]
|
||||
edges = []
|
||||
faces = [
|
||||
[0, 1, 3, 2],
|
||||
[2, 3, 7, 6],
|
||||
[6, 7, 5, 4],
|
||||
[4, 5, 1, 0],
|
||||
[2, 6, 4, 0],
|
||||
[7, 3, 1, 5],
|
||||
]
|
||||
mesh = bpy.data.meshes.new(name="Opening")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = bpy.data.objects.new("Opening", mesh)
|
||||
obj.matrix_world = filling_obj.matrix_world
|
||||
return obj
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
|
||||
@@ -51,37 +51,26 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
tprops = context.scene.BIMTypeProperties
|
||||
ifc_class = self.ifc_class or tprops.ifc_class
|
||||
relating_type_id = self.relating_type or tprops.relating_type
|
||||
|
||||
if not ifc_class or not relating_type_id:
|
||||
return {"FINISHED"}
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
|
||||
relating_type = self.file.by_id(int(relating_type_id))
|
||||
material = ifcopenshell.util.element.get_material(relating_type)
|
||||
if material.is_a("IfcMaterialProfileSet"):
|
||||
obj = profile.DumbProfileGenerator(relating_type).generate()
|
||||
if obj:
|
||||
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
if profile.DumbProfileGenerator(relating_type).generate():
|
||||
return {"FINISHED"}
|
||||
elif material.is_a("IfcMaterialLayerSet"):
|
||||
layer_set_direction = None
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
||||
if layer_set_direction is None:
|
||||
if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]:
|
||||
layer_set_direction = "AXIS3"
|
||||
else:
|
||||
layer_set_direction = "AXIS2"
|
||||
|
||||
if layer_set_direction == "AXIS3":
|
||||
obj = slab.DumbSlabGenerator(relating_type).generate()
|
||||
elif layer_set_direction == "AXIS2":
|
||||
obj = wall.DumbWallGenerator(relating_type).generate()
|
||||
else:
|
||||
obj = None # Dumb block generator? Eh? :)
|
||||
|
||||
if obj:
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
return {"FINISHED"}
|
||||
|
||||
building_obj = None
|
||||
if len(context.selected_objects) == 1 and context.active_object:
|
||||
building_obj = context.active_object
|
||||
|
||||
# A cube
|
||||
verts = [
|
||||
Vector((-1, -1, -1)),
|
||||
@@ -95,12 +84,12 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
]
|
||||
edges = []
|
||||
faces = [
|
||||
[0, 2, 3, 1],
|
||||
[0, 1, 3, 2],
|
||||
[2, 3, 7, 6],
|
||||
[4, 5, 7, 6],
|
||||
[0, 1, 5, 4],
|
||||
[1, 3, 7, 5],
|
||||
[0, 2, 6, 4],
|
||||
[6, 7, 5, 4],
|
||||
[4, 5, 1, 0],
|
||||
[2, 6, 4, 0],
|
||||
[7, 3, 1, 5],
|
||||
]
|
||||
mesh = bpy.data.meshes.new(name="Instance")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
@@ -111,10 +100,39 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
bpy.ops.bim.assign_type(relating_type=int(tprops.relating_type), related_object=obj.name)
|
||||
if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
|
||||
|
||||
if building_obj:
|
||||
if instance_class == "IfcWindow":
|
||||
# TODO For now we are hardcoding windows as a prototype
|
||||
bpy.ops.bim.add_element_opening(
|
||||
voided_building_element=building_obj.name, filling_building_element=obj.name
|
||||
)
|
||||
else:
|
||||
if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
|
||||
return {"FINISHED"}
|
||||
|
||||
def generate_layered_element(self, ifc_class, relating_type):
|
||||
layer_set_direction = None
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
||||
if layer_set_direction is None:
|
||||
if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]:
|
||||
layer_set_direction = "AXIS3"
|
||||
else:
|
||||
layer_set_direction = "AXIS2"
|
||||
|
||||
if layer_set_direction == "AXIS3":
|
||||
if slab.DumbSlabGenerator(relating_type).generate():
|
||||
return True
|
||||
elif layer_set_direction == "AXIS2":
|
||||
if wall.DumbWallGenerator(relating_type).generate():
|
||||
return True
|
||||
else:
|
||||
pass # Dumb block generator? Eh? :)
|
||||
|
||||
|
||||
class AlignProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.align_product"
|
||||
|
||||
@@ -17,7 +17,7 @@ class BimTool(WorkSpaceTool):
|
||||
bl_keymap = (
|
||||
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
|
||||
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}),
|
||||
("bim.add_type_instance", {"type": "A", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}),
|
||||
("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}),
|
||||
@@ -113,6 +113,9 @@ class Hotkey(bpy.types.Operator):
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def hotkey_S_A(self):
|
||||
bpy.ops.bim.add_type_instance()
|
||||
|
||||
def hotkey_S_C(self):
|
||||
if self.props.ifc_class == "IfcWallType":
|
||||
bpy.ops.bim.align_wall(align_type="CENTERLINE")
|
||||
@@ -138,7 +141,7 @@ class Hotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.align_product(align_type="NEGATIVE")
|
||||
|
||||
def hotkey_S_O(self):
|
||||
bpy.ops.bim.add_element_opening()
|
||||
bpy.ops.bim.add_element_opening(voided_building_element="", filling_building_element="")
|
||||
|
||||
def hotkey_A_D(self):
|
||||
bpy.ops.bim.toggle_decomposition_parenting()
|
||||
|
||||
@@ -28,6 +28,8 @@ def extract_docs(
|
||||
print(e)
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}")
|
||||
except:
|
||||
print(f"Error : IFCPatch {str(submodule)} could not load")
|
||||
|
||||
def _extract_docs(cls, method_name, boilerplate_args):
|
||||
inputs = collections.OrderedDict()
|
||||
|
||||
Reference in New Issue
Block a user