mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
New "cut decorator" that draws cut lines in drawings.
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user