Half complete "add window" tool prototype

This commit is contained in:
Dion Moult
2021-08-16 10:41:14 +10:00
parent b4256c504f
commit 897d7a6b2d
5 changed files with 134 additions and 53 deletions
@@ -128,6 +128,7 @@ class EditGeoreferencing(bpy.types.Operator):
def export_attributes(self, attributes, prop): def export_attributes(self, attributes, prop):
if not prop.is_null and prop.data_type == "string": 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) attributes[prop.name] = float(prop.string_value)
return True return True
@@ -5,6 +5,7 @@ import ifcopenshell
import ifcopenshell.util.representation import ifcopenshell.util.representation
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from math import pi from math import pi
from mathutils import Vector
from bpy.types import Operator from bpy.types import Operator
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
@@ -56,40 +57,96 @@ class AddElementOpening(bpy.types.Operator):
bl_idname = "bim.add_element_opening" bl_idname = "bim.add_element_opening"
bl_label = "Add Element Opening" bl_label = "Add Element Opening"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
voided_building_element: bpy.props.StringProperty()
filling_building_element: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
selected_objs = context.selected_objects voided_obj = self.get_voided_building_element(context)
if len(selected_objs) == 0 or not context.active_object: filling_obj = self.get_filling_building_element(context)
if not voided_obj:
return {"FINISHED"} return {"FINISHED"}
obj = context.active_object
if not obj.BIMObjectProperties.ifc_definition_id: element = IfcStore.get_file().by_id(voided_obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"} local_location = voided_obj.matrix_world.inverted() @ context.scene.cursor.location
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id) raycast = voided_obj.closest_point_on_mesh(local_location, distance=0.01)
local_location = obj.matrix_world.inverted() @ context.scene.cursor.location
raycast = obj.closest_point_on_mesh(local_location, distance=0.01)
if not raycast[0]: if not raycast[0]:
return {"FINISHED"} return {"FINISHED"}
# The opening shall be based on the smallest bounding dimension of the element if filling_obj:
dimension = min(obj.dimensions) opening = self.generate_opening_from_filling(filling_obj)
bpy.ops.mesh.primitive_cube_add(size=dimension * 2) else:
opening = context.selected_objects[0] # 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 # Place the opening in the middle of the element
global_location = obj.matrix_world @ raycast[1] global_location = voided_obj.matrix_world @ raycast[1]
normal = raycast[2] normal = raycast[2]
normal.negate() normal.negate()
global_normal = obj.matrix_world.to_quaternion() @ normal global_normal = voided_obj.matrix_world.to_quaternion() @ normal
opening.location = global_location + (global_normal * (dimension / 2)) opening.location = global_location + (global_normal * (dimension / 2))
opening.rotation_euler = voided_obj.rotation_euler
opening.name = "Opening"
opening.rotation_euler = obj.rotation_euler bpy.ops.bim.add_opening(opening=opening.name, obj=voided_obj.name)
opening.name = "Opening"
bpy.ops.bim.add_opening(opening=opening.name, obj=obj.name)
return {"FINISHED"} 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): def add_object(self, context):
@@ -51,37 +51,26 @@ class AddTypeInstance(bpy.types.Operator):
tprops = context.scene.BIMTypeProperties tprops = context.scene.BIMTypeProperties
ifc_class = self.ifc_class or tprops.ifc_class ifc_class = self.ifc_class or tprops.ifc_class
relating_type_id = self.relating_type or tprops.relating_type relating_type_id = self.relating_type or tprops.relating_type
if not ifc_class or not relating_type_id: if not ifc_class or not relating_type_id:
return {"FINISHED"} return {"FINISHED"}
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0] instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
relating_type = self.file.by_id(int(relating_type_id)) relating_type = self.file.by_id(int(relating_type_id))
material = ifcopenshell.util.element.get_material(relating_type) material = ifcopenshell.util.element.get_material(relating_type)
if material.is_a("IfcMaterialProfileSet"):
obj = profile.DumbProfileGenerator(relating_type).generate() if material and material.is_a("IfcMaterialProfileSet"):
if obj: if profile.DumbProfileGenerator(relating_type).generate():
return {"FINISHED"} return {"FINISHED"}
elif material.is_a("IfcMaterialLayerSet"): elif material and material.is_a("IfcMaterialLayerSet"):
layer_set_direction = None if self.generate_layered_element(ifc_class, relating_type):
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:
return {"FINISHED"} return {"FINISHED"}
building_obj = None
if len(context.selected_objects) == 1 and context.active_object:
building_obj = context.active_object
# A cube # A cube
verts = [ verts = [
Vector((-1, -1, -1)), Vector((-1, -1, -1)),
@@ -95,12 +84,12 @@ class AddTypeInstance(bpy.types.Operator):
] ]
edges = [] edges = []
faces = [ faces = [
[0, 2, 3, 1], [0, 1, 3, 2],
[2, 3, 7, 6], [2, 3, 7, 6],
[4, 5, 7, 6], [6, 7, 5, 4],
[0, 1, 5, 4], [4, 5, 1, 0],
[1, 3, 7, 5], [2, 6, 4, 0],
[0, 2, 6, 4], [7, 3, 1, 5],
] ]
mesh = bpy.data.meshes.new(name="Instance") mesh = bpy.data.meshes.new(name="Instance")
mesh.from_pydata(verts, edges, faces) mesh.from_pydata(verts, edges, faces)
@@ -111,10 +100,39 @@ class AddTypeInstance(bpy.types.Operator):
collection_obj = bpy.data.objects.get(collection.name) collection_obj = bpy.data.objects.get(collection.name)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class) 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) 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"} 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): class AlignProduct(bpy.types.Operator):
bl_idname = "bim.align_product" bl_idname = "bim.align_product"
@@ -17,7 +17,7 @@ class BimTool(WorkSpaceTool):
bl_keymap = ( bl_keymap = (
# ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}), # ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}),
# ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"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.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": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}),
("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}), ("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}")() getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"} return {"FINISHED"}
def hotkey_S_A(self):
bpy.ops.bim.add_type_instance()
def hotkey_S_C(self): def hotkey_S_C(self):
if self.props.ifc_class == "IfcWallType": if self.props.ifc_class == "IfcWallType":
bpy.ops.bim.align_wall(align_type="CENTERLINE") 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") bpy.ops.bim.align_product(align_type="NEGATIVE")
def hotkey_S_O(self): 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): def hotkey_A_D(self):
bpy.ops.bim.toggle_decomposition_parenting() bpy.ops.bim.toggle_decomposition_parenting()
@@ -28,6 +28,8 @@ def extract_docs(
print(e) print(e)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(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): def _extract_docs(cls, method_name, boilerplate_args):
inputs = collections.OrderedDict() inputs = collections.OrderedDict()