mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-09 13:52:23 +00:00
New clipping planes feature.
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from . import ui, prop, operator
|
from . import ui, prop, operator, workspace, gizmo
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
operator.AppendEntireLibrary,
|
operator.AppendEntireLibrary,
|
||||||
@@ -25,6 +25,7 @@ classes = (
|
|||||||
operator.AppendLibraryElementByQuery,
|
operator.AppendLibraryElementByQuery,
|
||||||
operator.AssignLibraryDeclaration,
|
operator.AssignLibraryDeclaration,
|
||||||
operator.ChangeLibraryElement,
|
operator.ChangeLibraryElement,
|
||||||
|
operator.CreateClippingPlane,
|
||||||
operator.CreateProject,
|
operator.CreateProject,
|
||||||
operator.DisableCulling,
|
operator.DisableCulling,
|
||||||
operator.DisableEditingHeader,
|
operator.DisableEditingHeader,
|
||||||
@@ -40,6 +41,7 @@ classes = (
|
|||||||
operator.LoadProjectElements,
|
operator.LoadProjectElements,
|
||||||
operator.NewProject,
|
operator.NewProject,
|
||||||
operator.QueryLinkedElement,
|
operator.QueryLinkedElement,
|
||||||
|
operator.RefreshClippingPlanes,
|
||||||
operator.RefreshLibrary,
|
operator.RefreshLibrary,
|
||||||
operator.ReloadLink,
|
operator.ReloadLink,
|
||||||
operator.RevertProject,
|
operator.RevertProject,
|
||||||
@@ -53,6 +55,7 @@ classes = (
|
|||||||
operator.UnlinkIfc,
|
operator.UnlinkIfc,
|
||||||
operator.UnloadLink,
|
operator.UnloadLink,
|
||||||
operator.UnloadProject,
|
operator.UnloadProject,
|
||||||
|
workspace.QueryHotkey,
|
||||||
prop.LibraryElement,
|
prop.LibraryElement,
|
||||||
prop.FilterCategory,
|
prop.FilterCategory,
|
||||||
prop.Link,
|
prop.Link,
|
||||||
@@ -66,6 +69,7 @@ classes = (
|
|||||||
ui.BIM_UL_library,
|
ui.BIM_UL_library,
|
||||||
ui.BIM_UL_filter_categories,
|
ui.BIM_UL_filter_categories,
|
||||||
ui.BIM_UL_links,
|
ui.BIM_UL_links,
|
||||||
|
gizmo.ClippingPlane,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -73,6 +77,8 @@ addon_keymaps = []
|
|||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
if not bpy.app.background:
|
||||||
|
bpy.utils.register_tool(workspace.QueryTool, after={"builtin.select"}, separator=True, group=False)
|
||||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||||
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
||||||
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
||||||
@@ -92,6 +98,8 @@ def register():
|
|||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
if not bpy.app.background:
|
||||||
|
bpy.utils.unregister_tool(workspace.QueryTool)
|
||||||
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
|
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
|
||||||
bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu)
|
bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu)
|
||||||
del bpy.types.Scene.BIMProjectProperties
|
del bpy.types.Scene.BIMProjectProperties
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import gpu
|
||||||
|
import bmesh
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
from bpy.types import SpaceView3D
|
||||||
|
from mathutils import Vector
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectDecorator:
|
||||||
|
installed = None
|
||||||
|
|
||||||
|
@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 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 __call__(self, context):
|
||||||
|
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||||
|
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||||
|
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||||
|
special_elements_color = self.addon_prefs.decorator_color_special
|
||||||
|
|
||||||
|
def transparent_color(color, alpha=0.1):
|
||||||
|
color = [i for i in color]
|
||||||
|
color[3] = alpha
|
||||||
|
return color
|
||||||
|
|
||||||
|
gpu.state.point_size_set(6)
|
||||||
|
gpu.state.blend_set("ALPHA")
|
||||||
|
|
||||||
|
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||||
|
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||||
|
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||||
|
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||||
|
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||||
|
|
||||||
|
# general shader
|
||||||
|
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||||
|
|
||||||
|
selected_vertices = []
|
||||||
|
selected_edges = []
|
||||||
|
selected_tris = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = context.scene.BIMProjectProperties.queried_obj
|
||||||
|
selected_vertices = obj["selected_vertices"]
|
||||||
|
selected_edges = obj["selected_edges"]
|
||||||
|
selected_tris = obj["selected_tris"]
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
if selected_edges:
|
||||||
|
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||||
|
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||||
|
|
||||||
|
|
||||||
|
class ClippingPlaneDecorator:
|
||||||
|
installed = None
|
||||||
|
|
||||||
|
@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 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 __call__(self, context):
|
||||||
|
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
|
||||||
|
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||||
|
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||||
|
special_elements_color = self.addon_prefs.decorator_color_special
|
||||||
|
|
||||||
|
def transparent_color(color, alpha=0.1):
|
||||||
|
color = [i for i in color]
|
||||||
|
color[3] = alpha
|
||||||
|
return color
|
||||||
|
|
||||||
|
gpu.state.point_size_set(6)
|
||||||
|
gpu.state.blend_set("ALPHA")
|
||||||
|
|
||||||
|
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||||
|
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||||
|
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||||
|
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||||
|
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||||
|
|
||||||
|
# general shader
|
||||||
|
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||||
|
|
||||||
|
selected_vertices = []
|
||||||
|
selected_edges = []
|
||||||
|
selected_tris = []
|
||||||
|
unselected_vertices = []
|
||||||
|
unselected_edges = []
|
||||||
|
unselected_tris = []
|
||||||
|
|
||||||
|
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||||
|
obj = clipping_plane.obj
|
||||||
|
if not obj or not obj.data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if obj.mode == "EDIT":
|
||||||
|
continue # A profile decorator or something else is used here.
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
obj.data.calc_loop_triangles()
|
||||||
|
|
||||||
|
if obj.select_get():
|
||||||
|
offset = len(selected_vertices)
|
||||||
|
selected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||||
|
selected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||||
|
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||||
|
else:
|
||||||
|
offset = len(unselected_vertices)
|
||||||
|
unselected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||||
|
unselected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||||
|
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||||
|
|
||||||
|
verts = [
|
||||||
|
tuple(obj.matrix_world @ Vector((0, 0, 0))),
|
||||||
|
tuple(obj.matrix_world @ Vector((0, 0, -0.5))),
|
||||||
|
tuple(obj.matrix_world @ Vector((-0.05, 0, -0.45))),
|
||||||
|
tuple(obj.matrix_world @ Vector((0.05, 0, -0.45))),
|
||||||
|
tuple(obj.matrix_world @ Vector((0, -0.05, -0.45))),
|
||||||
|
tuple(obj.matrix_world @ Vector((0, 0.05, -0.45))),
|
||||||
|
]
|
||||||
|
edges = [(0, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
|
||||||
|
color = selected_elements_color if obj in context.selected_objects else special_elements_color
|
||||||
|
self.draw_batch("LINES", verts, color, edges)
|
||||||
|
|
||||||
|
if obj.mode != "EDIT":
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
if unselected_edges:
|
||||||
|
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
||||||
|
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
|
||||||
|
if selected_edges:
|
||||||
|
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||||
|
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import GizmoGroup
|
||||||
|
from mathutils import Matrix
|
||||||
|
|
||||||
|
|
||||||
|
class ClippingPlane(GizmoGroup):
|
||||||
|
bl_idname = "OBJECT_GGT_bim_clipping_plane"
|
||||||
|
bl_label = "Clipping Plane"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_options = {"3D", "PERSISTENT"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
obj = context.object
|
||||||
|
return (
|
||||||
|
context.selected_objects
|
||||||
|
and obj.name.startswith("ClippingPlane")
|
||||||
|
and obj in [sp.obj for sp in context.scene.BIMProjectProperties.clipping_planes]
|
||||||
|
)
|
||||||
|
|
||||||
|
def setup(self, context):
|
||||||
|
self.obj = None
|
||||||
|
self.offset = 0
|
||||||
|
self.mw = Matrix()
|
||||||
|
self.last_mw = Matrix()
|
||||||
|
|
||||||
|
self.gizmo = self.gizmos.new("GIZMO_GT_arrow_3d")
|
||||||
|
|
||||||
|
def move_get_x():
|
||||||
|
return self.offset
|
||||||
|
|
||||||
|
def move_set_x(value):
|
||||||
|
self.obj.matrix_world.col[3] = self.mw.col[3] + (self.mw.col[2] * self.offset)
|
||||||
|
self.last_mw = self.obj.matrix_world.copy()
|
||||||
|
self.offset = value
|
||||||
|
|
||||||
|
self.gizmo.target_set_handler("offset", get=move_get_x, set=move_set_x)
|
||||||
|
|
||||||
|
def refresh(self, context):
|
||||||
|
if self.obj != context.object or self.last_mw != context.object.matrix_world:
|
||||||
|
self.obj = context.object
|
||||||
|
self.offset = 0
|
||||||
|
self.mw = context.object.matrix_world.copy()
|
||||||
|
obj = context.object
|
||||||
|
mw = context.object.matrix_world.normalized()
|
||||||
|
mw.col[3] -= mw.col[2] * self.offset
|
||||||
|
self.gizmo.matrix_basis = mw
|
||||||
@@ -41,6 +41,7 @@ from pathlib import Path
|
|||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
from blenderbim.bim.module.project.data import LinksData
|
from blenderbim.bim.module.project.data import LinksData
|
||||||
|
from blenderbim.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator
|
||||||
|
|
||||||
|
|
||||||
class NewProject(bpy.types.Operator):
|
class NewProject(bpy.types.Operator):
|
||||||
@@ -1700,3 +1701,178 @@ class DisableCulling(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
LinksData.enable_culling = False
|
LinksData.enable_culling = False
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshClippingPlanes(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.refresh_clipping_planes"
|
||||||
|
bl_label = "Refresh Clipping Planes"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.total_planes = 0
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
should_refresh = False
|
||||||
|
|
||||||
|
self.clean_deleted_planes(context)
|
||||||
|
|
||||||
|
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||||
|
if clipping_plane.obj and self.is_moved(clipping_plane.obj):
|
||||||
|
should_refresh = True
|
||||||
|
break
|
||||||
|
|
||||||
|
total_planes = len(context.scene.BIMProjectProperties.clipping_planes)
|
||||||
|
if should_refresh or total_planes != self.total_planes:
|
||||||
|
self.refresh_clipping_planes(context)
|
||||||
|
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||||
|
if clipping_plane.obj:
|
||||||
|
tool.Geometry.record_object_position(clipping_plane.obj)
|
||||||
|
self.total_planes = total_planes
|
||||||
|
return {"PASS_THROUGH"}
|
||||||
|
|
||||||
|
def clean_deleted_planes(self, context):
|
||||||
|
while True:
|
||||||
|
for i, clipping_plane in enumerate(context.scene.BIMProjectProperties.clipping_planes):
|
||||||
|
if clipping_plane.obj:
|
||||||
|
try:
|
||||||
|
clipping_plane.obj.name
|
||||||
|
except:
|
||||||
|
context.scene.BIMProjectProperties.clipping_planes.remove(i)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
context.scene.BIMProjectProperties.clipping_planes.remove(i)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def is_moved(self, obj):
|
||||||
|
if not obj.BIMObjectProperties.location_checksum:
|
||||||
|
return True # Let's be conservative
|
||||||
|
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
|
||||||
|
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
|
||||||
|
loc_real = np.array(obj.matrix_world.translation).flatten()
|
||||||
|
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
|
||||||
|
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def refresh_clipping_planes(self, context):
|
||||||
|
import bmesh
|
||||||
|
from itertools import cycle
|
||||||
|
|
||||||
|
area = next(a for a in bpy.context.screen.areas if a.type == "VIEW_3D")
|
||||||
|
region = next(r for r in area.regions if r.type == "WINDOW")
|
||||||
|
data = region.data
|
||||||
|
|
||||||
|
if not len(context.scene.BIMProjectProperties.clipping_planes):
|
||||||
|
data.use_clip_planes = False
|
||||||
|
else:
|
||||||
|
with bpy.context.temp_override(area=area, region=region):
|
||||||
|
bpy.ops.view3d.clip_border()
|
||||||
|
|
||||||
|
clip_planes = []
|
||||||
|
for clipping_plane in bpy.context.scene.BIMProjectProperties.clipping_planes:
|
||||||
|
obj = clipping_plane.obj
|
||||||
|
if not obj:
|
||||||
|
continue
|
||||||
|
print("doing", obj)
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
|
||||||
|
world_matrix = obj.matrix_world
|
||||||
|
|
||||||
|
bm.faces.ensure_lookup_table()
|
||||||
|
face = bm.faces[0]
|
||||||
|
center = world_matrix @ face.calc_center_median()
|
||||||
|
print("center", center)
|
||||||
|
normal = world_matrix.to_3x3() @ face.normal * -1
|
||||||
|
center += normal * -0.01
|
||||||
|
print("normal", normal)
|
||||||
|
print("new center", center)
|
||||||
|
|
||||||
|
normal.normalize()
|
||||||
|
distance = -center.dot(normal)
|
||||||
|
clip_plane = (normal.x, normal.y, normal.z, distance)
|
||||||
|
clip_planes.append(clip_plane)
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
clip_planes = cycle(clip_planes)
|
||||||
|
data.clip_planes = [tuple(next(clip_planes)) for i in range(0, 6)]
|
||||||
|
data.update()
|
||||||
|
region.tag_redraw()
|
||||||
|
[a.tag_redraw() for a in bpy.context.screen.areas]
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateClippingPlane(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.create_clipping_plane"
|
||||||
|
bl_label = "Create Clipping Plane"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.area.type == "VIEW_3D"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
|
||||||
|
|
||||||
|
# Clean up deleted planes
|
||||||
|
|
||||||
|
if len(bpy.context.scene.BIMProjectProperties.clipping_planes) > 5:
|
||||||
|
self.report({"INFO"}, "Maximum of six clipping planes allowed.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == "VIEW_3D":
|
||||||
|
area.tag_redraw()
|
||||||
|
|
||||||
|
region = context.region
|
||||||
|
rv3d = context.region_data
|
||||||
|
coord = (self.mouse_x, self.mouse_y)
|
||||||
|
origin = region_2d_to_origin_3d(region, rv3d, coord)
|
||||||
|
direction = region_2d_to_vector_3d(region, rv3d, coord)
|
||||||
|
hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction)
|
||||||
|
if not hit:
|
||||||
|
self.report({"INFO"}, "No object found.")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
vertices = [(-0.5, -0.5, 0), (0.5, -0.5, 0), (0.5, 0.5, 0), (-0.5, 0.5, 0)]
|
||||||
|
|
||||||
|
faces = [(0, 1, 2, 3)]
|
||||||
|
|
||||||
|
mesh = bpy.data.meshes.new(name="ClippingPlane")
|
||||||
|
mesh.from_pydata(vertices, [], faces)
|
||||||
|
mesh.update()
|
||||||
|
|
||||||
|
plane_obj = bpy.data.objects.new("ClippingPlane", mesh)
|
||||||
|
bpy.context.collection.objects.link(plane_obj)
|
||||||
|
z_axis = Vector((0, 0, 1))
|
||||||
|
rotation_matrix = z_axis.rotation_difference(normal).to_matrix().to_4x4()
|
||||||
|
plane_obj.matrix_world = rotation_matrix
|
||||||
|
plane_obj.matrix_world.translation = location
|
||||||
|
|
||||||
|
bpy.context.scene.cursor.location = location
|
||||||
|
|
||||||
|
new = bpy.context.scene.BIMProjectProperties.clipping_planes.add()
|
||||||
|
new.obj = plane_obj
|
||||||
|
|
||||||
|
tool.Blender.set_active_object(plane_obj)
|
||||||
|
|
||||||
|
ClippingPlaneDecorator.install(bpy.context)
|
||||||
|
bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def ray_cast(self, context, origin, direction):
|
||||||
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
|
result = context.scene.ray_cast(depsgraph, origin, direction)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.mouse_x = event.mouse_region_x
|
||||||
|
self.mouse_y = event.mouse_region_y
|
||||||
|
return self.execute(context)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import bpy
|
|||||||
import ifcopenshell.util.placement
|
import ifcopenshell.util.placement
|
||||||
from blenderbim.bim.module.project.data import ProjectData
|
from blenderbim.bim.module.project.data import ProjectData
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.prop import StrProperty
|
from blenderbim.bim.prop import StrProperty, ObjProperty
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
PointerProperty,
|
PointerProperty,
|
||||||
@@ -171,6 +171,7 @@ class BIMProjectProperties(PropertyGroup):
|
|||||||
template_file: EnumProperty(items=get_template_file, name="Template File")
|
template_file: EnumProperty(items=get_template_file, name="Template File")
|
||||||
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
|
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
|
||||||
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||||
|
clipping_planes: bpy.props.CollectionProperty(type=ObjProperty)
|
||||||
|
|
||||||
def get_library_element_index(self, lib_element):
|
def get_library_element_index(self, lib_element):
|
||||||
return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element))
|
return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element))
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# This file is part of BlenderBIM Add-on.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
from bpy.types import WorkSpaceTool
|
||||||
|
from blenderbim.bim.module.project.data import LinksData
|
||||||
|
|
||||||
|
|
||||||
|
class QueryTool(bpy.types.WorkSpaceTool):
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_context_mode = "OBJECT"
|
||||||
|
bl_idname = "bim.query_tool"
|
||||||
|
bl_label = "Query Tool"
|
||||||
|
bl_description = "Fetch data about a linked IFC element"
|
||||||
|
bl_icon = "ops.generic.select_circle"
|
||||||
|
bl_widget = None
|
||||||
|
bl_keymap = (
|
||||||
|
("bim.query_linked_element", {"type": "RIGHTMOUSE", "value": "PRESS"}, None),
|
||||||
|
("bim.query_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||||
|
("bim.query_hotkey", {"type": "C", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_C")]}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_settings(context, layout, ws_tool):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="Query Object", icon="MOUSE_RMB")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="Add Clipping Plane", icon="EVENT_C")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_ALT")
|
||||||
|
row.label(text="Disable Culling" if LinksData.enable_culling else "Enable Culling", icon="EVENT_C")
|
||||||
|
|
||||||
|
|
||||||
|
class QueryHotkey(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.query_hotkey"
|
||||||
|
bl_label = "Query Hotkey"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
hotkey: bpy.props.StringProperty()
|
||||||
|
description: bpy.props.StringProperty()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def description(cls, context, operator):
|
||||||
|
return operator.description or ""
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
getattr(self, f"hotkey_{self.hotkey}")()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def hotkey_S_C(self):
|
||||||
|
bpy.ops.bim.create_clipping_plane("INVOKE_DEFAULT")
|
||||||
|
|
||||||
|
def hotkey_A_C(self):
|
||||||
|
if LinksData.enable_culling:
|
||||||
|
bpy.ops.bim.disable_culling()
|
||||||
|
else:
|
||||||
|
bpy.ops.bim.enable_culling("INVOKE_DEFAULT")
|
||||||
Reference in New Issue
Block a user