mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 12:23:44 +00:00
First implementation. Two types of raycast and basic snaping for faces,
edges and edge center.
This commit is contained in:
committed by
Bruno Perdigão
parent
2938c1fd0b
commit
94ca2423e9
@@ -69,6 +69,7 @@ classes = (
|
|||||||
wall.ChangeExtrusionDepth,
|
wall.ChangeExtrusionDepth,
|
||||||
wall.ChangeExtrusionXAngle,
|
wall.ChangeExtrusionXAngle,
|
||||||
wall.ChangeLayerLength,
|
wall.ChangeLayerLength,
|
||||||
|
wall.DrawPolylineWall,
|
||||||
wall.FlipWall,
|
wall.FlipWall,
|
||||||
wall.MergeWall,
|
wall.MergeWall,
|
||||||
wall.RecalculateWall,
|
wall.RecalculateWall,
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ import blenderbim.core.model as core
|
|||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from math import pi, sin, cos, degrees
|
from math import pi, sin, cos, degrees
|
||||||
|
from time import time
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
|
from mathutils.bvhtree import BVHTree
|
||||||
|
from bpy_extras import view3d_utils
|
||||||
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -279,6 +282,222 @@ def recalculate_dumb_wall_origin(wall, new_origin=None):
|
|||||||
child.matrix_parent_inverse = wall.matrix_world.inverted()
|
child.matrix_parent_inverse = wall.matrix_world.inverted()
|
||||||
|
|
||||||
|
|
||||||
|
class DrawPolylineWall(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.draw_polyline_wall"
|
||||||
|
bl_label = "Draw Polyline Wall"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
ray_cast_type = bpy.props.StringProperty()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.space_data.type == 'VIEW_3D'
|
||||||
|
|
||||||
|
|
||||||
|
def main(self, context, event):
|
||||||
|
scene = context.scene
|
||||||
|
region = context.region
|
||||||
|
rv3d = context.region_data
|
||||||
|
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||||
|
|
||||||
|
offset = 0.4
|
||||||
|
mouse_offset = (
|
||||||
|
(-offset, offset), (0, offset), (offset, offset),
|
||||||
|
(-offset, 0), (0, 0), (offset, 0),
|
||||||
|
(-offset, -offset), (0, -offset), (offset, -offset)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# Plane to intersect. Default Container
|
||||||
|
plane_origin = Vector((0, 0, 0))
|
||||||
|
plane_normal = Vector((0, 0, 1))
|
||||||
|
|
||||||
|
|
||||||
|
def get_viewport_ray_data():
|
||||||
|
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_pos)
|
||||||
|
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_pos)
|
||||||
|
ray_target = ray_origin + view_vector
|
||||||
|
ray_direction = ray_target - ray_origin
|
||||||
|
|
||||||
|
return ray_origin, ray_target, ray_direction
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_ray_data(obj_matrix):
|
||||||
|
matrix_inv = obj_matrix.inverted()
|
||||||
|
ray_origin_obj = matrix_inv @ ray_origin
|
||||||
|
ray_target_obj = matrix_inv @ ray_target
|
||||||
|
ray_direction_obj = ray_target_obj - ray_origin_obj
|
||||||
|
|
||||||
|
return ray_origin_obj, ray_target_obj, ray_direction_obj
|
||||||
|
|
||||||
|
|
||||||
|
def get_visible_objects():
|
||||||
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
|
for dup in depsgraph.object_instances:
|
||||||
|
if dup.is_instance: # Real dupli instance
|
||||||
|
obj = dup.instance_object
|
||||||
|
yield (obj, dup.matrix_world.copy())
|
||||||
|
else: # Usual object
|
||||||
|
obj = dup.object
|
||||||
|
yield (obj, obj.matrix_world.copy())
|
||||||
|
|
||||||
|
|
||||||
|
def obj_ray_cast(obj):
|
||||||
|
ray_origin_obj, _, ray_direction_obj = get_object_ray_data(obj.matrix_world.copy())
|
||||||
|
success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
|
||||||
|
if success:
|
||||||
|
return location, normal, face_index
|
||||||
|
else:
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def bvh_ray_cast(obj):
|
||||||
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
|
bvh = BVHTree.FromObject(obj, depsgraph)
|
||||||
|
#BVH uses objects local matrix
|
||||||
|
ray_origin_obj, _, ray_direction_obj = get_object_ray_data(obj.matrix_local.copy())
|
||||||
|
location, normal, face_index, distance = bvh.ray_cast(ray_origin_obj, ray_direction_obj)
|
||||||
|
if location:
|
||||||
|
return location, normal, face_index
|
||||||
|
else:
|
||||||
|
return None, None, None
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def ray_cast_to_plane(mouse_pos, context, plane_origin, plane_normal):
|
||||||
|
intersection = Vector ((0,0,0,))
|
||||||
|
try:
|
||||||
|
loc = view3d_utils.region_2d_to_location_3d(
|
||||||
|
region, rv3d, mouse_pos, ray_direction
|
||||||
|
)
|
||||||
|
intersection = mathutils.geometry.intersect_line_plane(ray_target, loc, plane_origin, plane_normal)
|
||||||
|
except:
|
||||||
|
intersection = Vector((0,0,0))
|
||||||
|
|
||||||
|
if intersection == None:
|
||||||
|
intersection = Vector((0,0,0))
|
||||||
|
|
||||||
|
return intersection
|
||||||
|
|
||||||
|
|
||||||
|
def cast_rays_and_get_best_object():
|
||||||
|
best_length_squared = 1.0
|
||||||
|
best_obj = None
|
||||||
|
best_hit = None
|
||||||
|
best_face_index = None
|
||||||
|
|
||||||
|
for obj, matrix in get_visible_objects():
|
||||||
|
if obj.type == 'MESH':
|
||||||
|
hit, normal, face_index = obj_ray_cast(obj) if self.ray_cast_type == 'OBJ' else bvh_ray_cast(obj)
|
||||||
|
if hit is not None:
|
||||||
|
hit_world = matrix @ hit
|
||||||
|
length_squared = (hit_world - ray_origin).length_squared
|
||||||
|
if best_obj is None or length_squared < best_length_squared:
|
||||||
|
best_length_squared = length_squared
|
||||||
|
best_obj = obj
|
||||||
|
best_hit = hit_world
|
||||||
|
best_face_index = face_index
|
||||||
|
|
||||||
|
if best_obj is not None:
|
||||||
|
return best_obj, best_hit, best_face_index
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_snap_points(obj, face_index):
|
||||||
|
snap_points = {}
|
||||||
|
matrix = obj.matrix_world.copy()
|
||||||
|
face = obj.data.polygons[face_index]
|
||||||
|
vertices = []
|
||||||
|
for i in face.vertices:
|
||||||
|
vertices.append(matrix @ obj.data.vertices[i].co)
|
||||||
|
|
||||||
|
edges_center = []
|
||||||
|
for v1, v2 in zip(vertices, vertices[1:] + [vertices[0]]):
|
||||||
|
edges_center.append((v1-v2) / 2)
|
||||||
|
|
||||||
|
snap_points.update({tuple(vertex): "Vertices" for vertex in vertices})
|
||||||
|
snap_points.update({tuple(edge_center): "Edge Center" for edge_center in edges_center})
|
||||||
|
|
||||||
|
return snap_points
|
||||||
|
|
||||||
|
|
||||||
|
def select_snap_point(snap_points, hit, threshold):
|
||||||
|
shortest_distance = None
|
||||||
|
snap_point = None
|
||||||
|
for point, snap_type in snap_points.items():
|
||||||
|
point = Vector(point)
|
||||||
|
distance = (point - hit).length
|
||||||
|
if distance > threshold:
|
||||||
|
continue
|
||||||
|
if shortest_distance and distance < shortest_distance:
|
||||||
|
shortest_distance = distance
|
||||||
|
snap_point = (point, snap_type)
|
||||||
|
|
||||||
|
elif not shortest_distance:
|
||||||
|
shortest_distance = distance
|
||||||
|
snap_point = (point, snap_type)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return snap_point
|
||||||
|
|
||||||
|
|
||||||
|
snap_threshold = 0.3
|
||||||
|
ray_origin, ray_target, ray_direction = get_viewport_ray_data()
|
||||||
|
obj, hit, face_index = cast_rays_and_get_best_object()
|
||||||
|
intersection = ray_cast_to_plane(mouse_pos, context, plane_origin, plane_normal)
|
||||||
|
|
||||||
|
if obj is not None:
|
||||||
|
original_obj = obj.original
|
||||||
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
original_obj.select_set(True)
|
||||||
|
context.view_layer.objects.active = original_obj
|
||||||
|
|
||||||
|
snap_points = get_snap_points(obj, face_index)
|
||||||
|
snap_point = select_snap_point(snap_points, hit, snap_threshold)
|
||||||
|
if snap_point:
|
||||||
|
scene.cursor.location = snap_point[0]
|
||||||
|
else:
|
||||||
|
scene.cursor.location = hit
|
||||||
|
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
# scene.cursor.location = intersection
|
||||||
|
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
elif event.type == 'MOUSEMOVE':
|
||||||
|
start_time = time()
|
||||||
|
self.main(context, event)
|
||||||
|
operator_time = time() - start_time
|
||||||
|
print(f"main function was finished in {operator_time:.2f} seconds")
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
elif event.type == 'O':
|
||||||
|
self.ray_cast_type = 'OBJ'
|
||||||
|
print("OBJ selected.")
|
||||||
|
elif event.type == 'B':
|
||||||
|
self.ray_cast_type = 'BVH'
|
||||||
|
print("BVH selected.")
|
||||||
|
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||||
|
print("CANCELLLL")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if context.space_data.type == 'VIEW_3D':
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, "Active space must be a View3d")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
|
||||||
class DumbWallAligner:
|
class DumbWallAligner:
|
||||||
# An alignment shifts the origin of all walls to the closest point on the
|
# An alignment shifts the origin of all walls to the closest point on the
|
||||||
# local X axis of the reference wall. In addition, the Z rotation is copied.
|
# local X axis of the reference wall. In addition, the Z rotation is copied.
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class BimTool(WorkSpaceTool):
|
|||||||
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
||||||
("bim.hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
("bim.hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
||||||
("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
||||||
|
("bim.hotkey", {"type": "P", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_P")]}),
|
||||||
("bim.hotkey", {"type": "L", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_L")]}),
|
("bim.hotkey", {"type": "L", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_L")]}),
|
||||||
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||||
("bim.hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
("bim.hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
||||||
@@ -841,6 +842,9 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
self.props.y = self.y
|
self.props.y = self.y
|
||||||
self.props.z = self.z
|
self.props.z = self.z
|
||||||
|
|
||||||
|
def hotkey_S_P(self):
|
||||||
|
bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT")
|
||||||
|
|
||||||
def hotkey_S_L(self):
|
def hotkey_S_L(self):
|
||||||
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
|
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
|
||||||
if len(bpy.context.selected_objects) == 2:
|
if len(bpy.context.selected_objects) == 2:
|
||||||
|
|||||||
Reference in New Issue
Block a user