New "cut decorator" that draws cut lines in drawings.

This commit is contained in:
Dion Moult
2023-04-12 21:25:56 +10:00
parent cbe71f221c
commit 9170e5ca65
5 changed files with 171 additions and 23 deletions
@@ -30,6 +30,7 @@ def refresh():
SchedulesData.is_loaded = False SchedulesData.is_loaded = False
DrawingsData.is_loaded = False DrawingsData.is_loaded = False
DecoratorData.data = {} DecoratorData.data = {}
DecoratorData.cut_cache = {}
class ProductAssignmentsData: class ProductAssignmentsData:
@@ -152,6 +153,7 @@ FONT_SIZES = {
class DecoratorData: class DecoratorData:
# stores 1 type of data per object # stores 1 type of data per object
data = {} data = {}
cut_cache = {}
# used by Ifc Annotations with ObjectType = "BATTING" # used by Ifc Annotations with ObjectType = "BATTING"
@classmethod @classmethod
@@ -30,6 +30,7 @@ from bpy.types import SpaceView3D
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from bpy_extras.view3d_utils import location_3d_to_region_2d from bpy_extras.view3d_utils import location_3d_to_region_2d
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
from gpu_extras.batch import batch_for_shader
from blenderbim.bim.module.drawing.data import DecoratorData from blenderbim.bim.module.drawing.data import DecoratorData
from blenderbim.bim.module.drawing.shaders import BASE_LIB_GLSL, BASE_DEF_GLSL from blenderbim.bim.module.drawing.shaders import BASE_LIB_GLSL, BASE_DEF_GLSL
@@ -242,7 +243,7 @@ class BaseDecorator:
vertices = [obj.matrix_world @ v.co for v in bm.verts] vertices = [obj.matrix_world @ v.co for v in bm.verts]
return vertices, indices return vertices, indices
def decorate(self, context, object): def decorate(self, context, obj):
"""perform actual drawing stuff""" """perform actual drawing stuff"""
raise NotImplementedError() raise NotImplementedError()
@@ -1887,6 +1888,147 @@ class TextDecorator(BaseDecorator):
line_i += 1 line_i += 1
class CutDecorator:
installed = None
cache = {}
@classmethod
def install(cls, context):
if cls.installed:
cls.uninstall()
handler = cls()
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
@classmethod
def uninstall(cls):
try:
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
except ValueError:
pass
cls.installed = None
def __call__(self, context):
for obj in self.get_objects(None):
self.decorate(context, obj)
def get_objects(self, collection):
return [o for o in bpy.context.visible_objects if o.type == "MESH"]
def draw_batch(self, shader_type, content_pos, color, indices=None):
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
def decorate(self, context, obj):
element = tool.Ifc.get_entity(obj)
if not element:
return
# Currently selected objects shall not be cached as they may be being moved / edited.
# If the camera is selected, we also disable the cache as the user may be moving the camera.
if obj.select_get() or context.scene.camera.select_get():
all_vertices, all_edges = None, None
else:
all_vertices, all_edges = DecoratorData.cut_cache.get(element.id(), (None, None))
if all_vertices is False:
return
if not self.is_intersecting_camera(obj, context.scene.camera):
DecoratorData.cut_cache[element.id()] = (False, False)
return
if all_vertices is None:
all_vertices, all_edges = self.bisect_mesh(obj, context.scene.camera)
DecoratorData.cut_cache[element.id()] = (all_vertices, all_edges)
gpu.state.point_size_set(2)
gpu.state.blend_set("ALPHA")
### Actually drawing
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.line_shader.bind()
# POLYLINE_UNIFORM_COLOR specific uniforms
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
self.line_shader.uniform_float("lineWidth", 3.0)
# general shader
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
self.shader.bind()
green = (0.545, 0.863, 0, 1)
white = (0, 0, 0, 1)
color = green if obj.select_get() else white
self.draw_batch("LINES", all_vertices, color, all_edges)
self.draw_batch("POINTS", all_vertices, color)
def is_intersecting_camera(self, obj, camera):
# Based on separating axis theorem
plane_co = camera.matrix_world.translation
plane_no = camera.matrix_world.col[2].xyz
# Broadphase check using the bounding box
bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box]
bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords]
pos_exists_bb = any(d > 0 for d in bounding_box_signed_distances)
neg_exists_bb = any(d < 0 for d in bounding_box_signed_distances)
if not (pos_exists_bb and neg_exists_bb):
return False
bm = bmesh.new()
bm.from_mesh(obj.data)
# Transform the vertices to world space
mesh_mat = obj.matrix_world
bm.transform(mesh_mat)
# Calculate the signed distances of all vertices from the plane
signed_distances = [plane_no.dot(v.co - plane_co) for v in bm.verts]
bm.free()
# Check for intersection
pos_exists = any(d > 0 for d in signed_distances)
neg_exists = any(d < 0 for d in signed_distances)
return pos_exists and neg_exists
def bisect_mesh(self, obj, camera):
camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world
plane_co = camera_matrix.translation
plane_no = camera_matrix.col[2].xyz
global_offset = camera.matrix_world.col[2].xyz * -camera.data.clip_start
bm = bmesh.new()
bm.from_mesh(obj.data)
# Run the bisect operation
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no)
vert_map = {}
verts = []
edges = []
i = 0
for geom in results["geom_cut"]:
if isinstance(geom, bmesh.types.BMVert):
verts.append(tuple((obj.matrix_world @ geom.co) + global_offset))
vert_map[geom.index] = i
i += 1
else:
# It seems as though edges always appear after verts
edges.append([vert_map[v.index] for v in geom.verts])
bm.free()
return verts, edges
class DecorationsHandler: class DecorationsHandler:
decorators_classes = [ decorators_classes = [
DimensionDecorator, DimensionDecorator,
@@ -23,8 +23,7 @@ from bpy.app.handlers import persistent
@persistent @persistent
def toggleDecorationsOnLoad(*args): def toggleDecorationsOnLoad(*args):
toggle = bpy.context.scene.DocProperties.should_draw_decorations if bpy.context.scene.DocProperties.should_draw_decorations:
if toggle:
decoration.DecorationsHandler.install(bpy.context) decoration.DecorationsHandler.install(bpy.context)
else: else:
decoration.DecorationsHandler.uninstall() decoration.DecorationsHandler.uninstall()
@@ -39,6 +39,7 @@ import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.sheeter as sheeter import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.decoration import CutDecorator
from blenderbim.bim.module.drawing.data import DecoratorData from blenderbim.bim.module.drawing.data import DecoratorData
import blenderbim.bim.export_ifc import blenderbim.bim.export_ifc
from lxml import etree from lxml import etree
@@ -989,11 +990,13 @@ class ActivateView(bpy.types.Operator):
drawing: bpy.props.IntProperty() drawing: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing)) drawing = tool.Ifc.get().by_id(self.drawing)
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
bpy.context.scene.DocProperties.active_drawing_id = self.drawing bpy.context.scene.DocProperties.active_drawing_id = self.drawing
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"): if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.activate_drawing_style() bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing)) core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
CutDecorator.install(context)
return {"FINISHED"} return {"FINISHED"}
@@ -117,6 +117,7 @@ class Drawing(blenderbim.core.tool.Drawing):
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
camera.data.type = "ORTHO" camera.data.type = "ORTHO"
camera.data.ortho_scale = 50 # The default of 6m is too small camera.data.ortho_scale = 50 # The default of 6m is too small
camera.data.clip_start = 0.002 # 2mm is close to zero but allows any GPU-drawn lines to be visible.
camera.data.clip_end = 10 # A slightly more reasonable default camera.data.clip_end = 10 # A slightly more reasonable default
if bpy.context.scene.unit_settings.system == "IMPERIAL": if bpy.context.scene.unit_settings.system == "IMPERIAL":
camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96' camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96'
@@ -495,6 +496,7 @@ class Drawing(blenderbim.core.tool.Drawing):
camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry)) camera = bpy.data.cameras.new(tool.Loader.get_mesh_name(geometry))
camera.type = "ORTHO" camera.type = "ORTHO"
camera.ortho_scale = width if width > height else height camera.ortho_scale = width if width > height else height
camera.clip_start = 0.002
camera.clip_end = depth camera.clip_end = depth
if width > height: if width > height: