mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 06:32:09 +00:00
Fix #2077. You can now edit profiles via their axis line.
This commit is contained in:
@@ -48,7 +48,9 @@ class CadTool(WorkSpaceTool):
|
|||||||
|
|
||||||
def draw_settings(context, layout, tool):
|
def draw_settings(context, layout, tool):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile:
|
if not obj or not obj.data:
|
||||||
|
return
|
||||||
|
if hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="", icon="EVENT_Q")
|
row.label(text="", icon="EVENT_Q")
|
||||||
@@ -100,6 +102,32 @@ class CadTool(WorkSpaceTool):
|
|||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="", icon="EVENT_X")
|
row.label(text="", icon="EVENT_X")
|
||||||
row.operator("bim.reset_vertex", text="Reset Vertex")
|
row.operator("bim.reset_vertex", text="Reset Vertex")
|
||||||
|
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_Q")
|
||||||
|
row.operator("bim.edit_extrusion_axis", text="Save Axis")
|
||||||
|
row.operator("bim.disable_editing_extrusion_axis", text="", icon="CANCEL")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_E")
|
||||||
|
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_T")
|
||||||
|
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_F")
|
||||||
|
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="", icon="EVENT_O")
|
||||||
|
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
|
||||||
else:
|
else:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
@@ -187,7 +215,10 @@ class CadHotkey(bpy.types.Operator):
|
|||||||
|
|
||||||
def hotkey_S_Q(self):
|
def hotkey_S_Q(self):
|
||||||
if tool.Ifc.get_entity(bpy.context.active_object):
|
if tool.Ifc.get_entity(bpy.context.active_object):
|
||||||
bpy.ops.bim.edit_extrusion_profile()
|
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||||
|
bpy.ops.bim.edit_extrusion_profile()
|
||||||
|
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||||
|
bpy.ops.bim.edit_extrusion_axis()
|
||||||
else:
|
else:
|
||||||
bpy.ops.bim.edit_arbitrary_profile()
|
bpy.ops.bim.edit_arbitrary_profile()
|
||||||
|
|
||||||
@@ -210,4 +241,9 @@ class CadHotkey(bpy.types.Operator):
|
|||||||
|
|
||||||
def is_profile(self):
|
def is_profile(self):
|
||||||
obj = bpy.context.active_object
|
obj = bpy.context.active_object
|
||||||
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
|
return (
|
||||||
|
obj
|
||||||
|
and obj.data
|
||||||
|
and hasattr(obj.data, "BIMMeshProperties")
|
||||||
|
and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||||
|
)
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ classes = (
|
|||||||
opening.ShowOpenings,
|
opening.ShowOpenings,
|
||||||
profile.ChangeCardinalPoint,
|
profile.ChangeCardinalPoint,
|
||||||
profile.ChangeProfileDepth,
|
profile.ChangeProfileDepth,
|
||||||
|
profile.DisableEditingExtrusionAxis,
|
||||||
|
profile.EditExtrusionAxis,
|
||||||
|
profile.EnableEditingExtrusionAxis,
|
||||||
profile.ExtendProfile,
|
profile.ExtendProfile,
|
||||||
profile.RecalculateProfile,
|
profile.RecalculateProfile,
|
||||||
profile.Rotate90,
|
profile.Rotate90,
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2020, 2021 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 bgl
|
||||||
|
import bmesh
|
||||||
|
import blenderbim.tool as tool
|
||||||
|
from math import pi, degrees, sin, cos, radians
|
||||||
|
from bpy.types import SpaceView3D
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileDecorator:
|
||||||
|
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 __call__(self, context):
|
||||||
|
obj = context.active_object
|
||||||
|
if obj.mode != "EDIT":
|
||||||
|
return
|
||||||
|
|
||||||
|
bgl.glLineWidth(2)
|
||||||
|
bgl.glPointSize(6)
|
||||||
|
bgl.glEnable(bgl.GL_BLEND)
|
||||||
|
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||||
|
|
||||||
|
all_vertices = []
|
||||||
|
error_vertices = []
|
||||||
|
selected_vertices = []
|
||||||
|
unselected_vertices = []
|
||||||
|
special_vertices = []
|
||||||
|
special_vertex_indices = {}
|
||||||
|
selected_edges = []
|
||||||
|
unselected_edges = []
|
||||||
|
special_edges = [] # edges that have a circle or an arc associated with them
|
||||||
|
|
||||||
|
arc_groups = []
|
||||||
|
circle_groups = []
|
||||||
|
for i, group in enumerate(obj.vertex_groups):
|
||||||
|
if "IFCARCINDEX" in group.name:
|
||||||
|
arc_groups.append(i)
|
||||||
|
elif "IFCCIRCLE" in group.name:
|
||||||
|
circle_groups.append(i)
|
||||||
|
|
||||||
|
arcs = {}
|
||||||
|
circles = {}
|
||||||
|
|
||||||
|
bm = bmesh.from_edit_mesh(obj.data)
|
||||||
|
|
||||||
|
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
||||||
|
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
||||||
|
deform_layer = bm.verts.layers.deform.active
|
||||||
|
|
||||||
|
for vertex in bm.verts:
|
||||||
|
co = tuple(obj.matrix_world @ vertex.co)
|
||||||
|
all_vertices.append(co)
|
||||||
|
if vertex.hide:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_arc = False
|
||||||
|
for group_index in arc_groups:
|
||||||
|
if group_index in vertex[deform_layer]:
|
||||||
|
is_arc = True
|
||||||
|
break
|
||||||
|
if is_arc:
|
||||||
|
arcs.setdefault(group_index, []).append(vertex)
|
||||||
|
special_vertex_indices[vertex.index] = group_index
|
||||||
|
|
||||||
|
is_circle = False
|
||||||
|
for group_index in circle_groups:
|
||||||
|
if group_index in vertex[deform_layer]:
|
||||||
|
is_circle = True
|
||||||
|
break
|
||||||
|
if is_circle:
|
||||||
|
circles.setdefault(group_index, []).append(vertex)
|
||||||
|
special_vertex_indices[vertex.index] = group_index
|
||||||
|
|
||||||
|
if vertex.select:
|
||||||
|
selected_vertices.append(co)
|
||||||
|
else:
|
||||||
|
if len(vertex.link_edges) > 1 and is_circle:
|
||||||
|
error_vertices.append(co)
|
||||||
|
elif is_circle:
|
||||||
|
special_vertices.append(co)
|
||||||
|
elif len(vertex.link_edges) != 2:
|
||||||
|
error_vertices.append(co)
|
||||||
|
elif is_arc:
|
||||||
|
special_vertices.append(co)
|
||||||
|
else:
|
||||||
|
unselected_vertices.append(co)
|
||||||
|
|
||||||
|
for edge in bm.edges:
|
||||||
|
edge_indices = [v.index for v in edge.verts]
|
||||||
|
if edge.hide:
|
||||||
|
continue
|
||||||
|
if edge.select:
|
||||||
|
selected_edges.append(edge_indices)
|
||||||
|
else:
|
||||||
|
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
||||||
|
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
||||||
|
special_edges.append(edge_indices)
|
||||||
|
else:
|
||||||
|
unselected_edges.append(edge_indices)
|
||||||
|
indices = [[v.index for v in e.verts] for e in bm.edges]
|
||||||
|
|
||||||
|
white = (1, 1, 1, 1)
|
||||||
|
green = (0.545, 0.863, 0, 1)
|
||||||
|
red = (1, 0.2, 0.322, 1)
|
||||||
|
blue = (0.157, 0.565, 1, 1)
|
||||||
|
grey = (0.2, 0.2, 0.2, 1)
|
||||||
|
|
||||||
|
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=unselected_edges)
|
||||||
|
self.shader.bind()
|
||||||
|
self.shader.uniform_float("color", white)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=selected_edges)
|
||||||
|
self.shader.uniform_float("color", green)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=special_edges)
|
||||||
|
self.shader.uniform_float("color", grey)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": unselected_vertices})
|
||||||
|
self.shader.uniform_float("color", white)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": error_vertices})
|
||||||
|
self.shader.uniform_float("color", red)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": special_vertices})
|
||||||
|
self.shader.uniform_float("color", blue)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": selected_vertices})
|
||||||
|
self.shader.uniform_float("color", green)
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
# Draw arcs
|
||||||
|
|
||||||
|
arc_centroids = []
|
||||||
|
arc_segments = []
|
||||||
|
for arc in arcs.values():
|
||||||
|
if len(arc) != 3:
|
||||||
|
continue
|
||||||
|
sorted_arc = [None, None, None]
|
||||||
|
for v1 in arc:
|
||||||
|
connections = 0
|
||||||
|
for link_edge in v1.link_edges:
|
||||||
|
v2 = link_edge.other_vert(v1)
|
||||||
|
if v2 in arc:
|
||||||
|
connections += 1
|
||||||
|
if connections == 2: # Midpoint
|
||||||
|
sorted_arc[1] = v1
|
||||||
|
else:
|
||||||
|
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
||||||
|
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
||||||
|
centroid = tool.Cad.get_center_of_arc(points)
|
||||||
|
if centroid:
|
||||||
|
arc_centroids.append(tuple(centroid))
|
||||||
|
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
|
||||||
|
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
for verts, edges in arc_segments:
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||||
|
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
# Draw circles
|
||||||
|
|
||||||
|
circle_centroids = []
|
||||||
|
circle_segments = []
|
||||||
|
for circle in circles.values():
|
||||||
|
if len(circle) != 2:
|
||||||
|
continue
|
||||||
|
p1 = obj.matrix_world @ circle[0].co
|
||||||
|
p2 = obj.matrix_world @ circle[1].co
|
||||||
|
radius = (p2 - p1).length / 2
|
||||||
|
centroid = p1.lerp(p2, 0.5)
|
||||||
|
circle_centroids.append(tuple(centroid))
|
||||||
|
segments = self.create_circle_segments(360, 20, radius)
|
||||||
|
matrix = obj.matrix_world.copy()
|
||||||
|
matrix.col[3] = centroid.to_4d()
|
||||||
|
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
||||||
|
circle_segments.append(segments)
|
||||||
|
|
||||||
|
batch = batch_for_shader(self.shader, "POINTS", {"pos": circle_centroids})
|
||||||
|
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
for verts, edges in circle_segments:
|
||||||
|
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
||||||
|
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
||||||
|
batch.draw(self.shader)
|
||||||
|
|
||||||
|
def create_matrix(self, p, x, y, z):
|
||||||
|
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||||
|
|
||||||
|
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
|
||||||
|
# This function is taken from Sverchok, licensed under GPL v2-or-later.
|
||||||
|
# This is a combination of the make_verts and make_edges function.
|
||||||
|
def create_circle_segments(self, Angle, Vertices, Radius):
|
||||||
|
if Angle < 360:
|
||||||
|
theta = Angle / (Vertices - 1)
|
||||||
|
else:
|
||||||
|
theta = Angle / Vertices
|
||||||
|
listVertX = []
|
||||||
|
listVertY = []
|
||||||
|
for i in range(Vertices):
|
||||||
|
listVertX.append(Radius * cos(radians(theta * i)))
|
||||||
|
listVertY.append(Radius * sin(radians(theta * i)))
|
||||||
|
|
||||||
|
if Angle < 360 and self.mode_ == 0:
|
||||||
|
sigma = radians(Angle)
|
||||||
|
listVertX[-1] = Radius * cos(sigma)
|
||||||
|
listVertY[-1] = Radius * sin(sigma)
|
||||||
|
elif Angle < 360 and self.mode_ == 1:
|
||||||
|
listVertX.append(0.0)
|
||||||
|
listVertY.append(0.0)
|
||||||
|
|
||||||
|
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
|
||||||
|
|
||||||
|
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
|
||||||
|
|
||||||
|
if Angle < 360 and self.mode_ == 1:
|
||||||
|
listEdg.append((0, Vertices))
|
||||||
|
listEdg.append((Vertices - 1, Vertices))
|
||||||
|
else:
|
||||||
|
listEdg.append((Vertices - 1, 0))
|
||||||
|
|
||||||
|
return points, listEdg
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import copy
|
import copy
|
||||||
import math
|
|
||||||
import bmesh
|
import bmesh
|
||||||
import mathutils.geometry
|
import mathutils.geometry
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -29,10 +28,10 @@ import blenderbim.bim.handler
|
|||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import blenderbim.core.type
|
import blenderbim.core.type
|
||||||
import blenderbim.core.geometry
|
import blenderbim.core.geometry
|
||||||
from blenderbim.bim.ifc import IfcStore
|
|
||||||
from math import pi, degrees, inf
|
from math import pi, degrees, inf
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix, Quaternion
|
||||||
from blenderbim.bim.module.geometry.helper import Helper
|
from blenderbim.bim.module.geometry.helper import Helper
|
||||||
|
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||||
|
|
||||||
|
|
||||||
def element_listener(element, obj):
|
def element_listener(element, obj):
|
||||||
@@ -48,12 +47,12 @@ def mode_callback(obj, data):
|
|||||||
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
or not bpy.context.scene.BIMProjectProperties.is_authoring
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
product = tool.Ifc.get().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
|
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
|
||||||
return
|
return
|
||||||
if obj.mode == "EDIT":
|
if obj.mode == "EDIT":
|
||||||
IfcStore.edited_objs.add(obj)
|
tool.Ifc.edit(obj)
|
||||||
bm = bmesh.from_edit_mesh(obj.data)
|
bm = bmesh.from_edit_mesh(obj.data)
|
||||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||||
bmesh.update_edit_mesh(obj.data)
|
bmesh.update_edit_mesh(obj.data)
|
||||||
@@ -97,8 +96,8 @@ class DumbProfileGenerator:
|
|||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.file = IfcStore.get_file()
|
self.file = tool.Ifc.get()
|
||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
material = ifcopenshell.util.element.get_material(self.relating_type)
|
material = ifcopenshell.util.element.get_material(self.relating_type)
|
||||||
if material and material.is_a("IfcMaterialProfileSet"):
|
if material and material.is_a("IfcMaterialProfileSet"):
|
||||||
self.profile_set = material
|
self.profile_set = material
|
||||||
@@ -328,7 +327,7 @@ class DumbProfileJoiner:
|
|||||||
body = copy.deepcopy(axis1)
|
body = copy.deepcopy(axis1)
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
si_length = unit_scale * length
|
si_length = unit_scale * length
|
||||||
end = profile1.matrix_world @ Vector((si_length, 0, 0))
|
end = profile1.matrix_world @ Vector((0, 0, si_length))
|
||||||
axis[1] = end
|
axis[1] = end
|
||||||
body[1] = end
|
body[1] = end
|
||||||
self.recreate_profile(element1, profile1, axis, body)
|
self.recreate_profile(element1, profile1, axis, body)
|
||||||
@@ -500,9 +499,7 @@ class DumbProfileJoiner:
|
|||||||
# Openings should move with the host overall ...
|
# Openings should move with the host overall ...
|
||||||
# ... except their position should stay the same along the local Z axis of the wall
|
# ... except their position should stay the same along the local Z axis of the wall
|
||||||
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
|
for opening in [r.RelatedOpeningElement for r in element.HasOpenings]:
|
||||||
percent = tool.Cad.edge_percent(
|
percent = tool.Cad.edge_percent(self.body[0], (previous_origin, (previous_matrix @ Vector((0, 0, 1)))))
|
||||||
self.body[0], (previous_origin, (previous_matrix @ Vector((0, 0, 1))))
|
|
||||||
)
|
|
||||||
is_z_offset_increased = True if percent < 0 else False
|
is_z_offset_increased = True if percent < 0 else False
|
||||||
|
|
||||||
change_in_z = (self.body[0] - previous_origin).length / self.unit_scale
|
change_in_z = (self.body[0] - previous_origin).length / self.unit_scale
|
||||||
@@ -913,3 +910,121 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
DumbProfileRecalculator().recalculate(objs)
|
DumbProfileRecalculator().recalculate(objs)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.enable_editing_extrusion_axis"
|
||||||
|
bl_label = "Enable Editing Extrusion Axis"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.selected_objects
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
obj = context.active_object
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
|
||||||
|
axis = ifcopenshell.util.representation.get_representation(element, "Model", "Axis", "GRAPH_VIEW")
|
||||||
|
if axis:
|
||||||
|
position = obj.matrix_world.copy()
|
||||||
|
tool.Model.import_axis(axis.Items[0], obj=obj)
|
||||||
|
else:
|
||||||
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
extrusion = tool.Model.get_extrusion(body)
|
||||||
|
|
||||||
|
if extrusion.Position:
|
||||||
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(extrusion.Position).tolist())
|
||||||
|
position[0][3] *= self.unit_scale
|
||||||
|
position[1][3] *= self.unit_scale
|
||||||
|
position[2][3] *= self.unit_scale
|
||||||
|
else:
|
||||||
|
position = Matrix()
|
||||||
|
|
||||||
|
direction = Vector(extrusion.ExtrudedDirection.DirectionRatios).normalized()
|
||||||
|
tool.Model.import_axis([Vector((0, 0, 0)), direction * extrusion.Depth], obj=obj, position=position)
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
|
ProfileDecorator.install(context)
|
||||||
|
if not bpy.app.background:
|
||||||
|
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.disable_editing_extrusion_axis"
|
||||||
|
bl_label = "Disable Editing Extrusion Axis"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.selected_objects
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
ProfileDecorator.uninstall()
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
|
obj = context.active_object
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=body,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.edit_extrusion_axis"
|
||||||
|
bl_label = "Edit Extrusion Axis"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
ProfileDecorator.uninstall()
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
|
obj = context.active_object
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
|
||||||
|
start = obj.matrix_world @ obj.data.vertices[0].co.copy()
|
||||||
|
end = obj.matrix_world @ obj.data.vertices[1].co.copy()
|
||||||
|
depth = (end - start).length
|
||||||
|
z_axis = (end - start).normalized()
|
||||||
|
y_axis = Vector((0, 0, 1))
|
||||||
|
x_axis = y_axis.cross(z_axis).normalized()
|
||||||
|
y_axis = z_axis.cross(x_axis).normalized()
|
||||||
|
|
||||||
|
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||||
|
blenderbim.core.geometry.switch_representation(
|
||||||
|
tool.Ifc,
|
||||||
|
tool.Geometry,
|
||||||
|
obj=obj,
|
||||||
|
representation=body,
|
||||||
|
should_reload=True,
|
||||||
|
is_global=True,
|
||||||
|
should_sync_changes_first=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
matrix = Matrix(
|
||||||
|
(
|
||||||
|
[x_axis[0], y_axis[0], z_axis[0], start.x],
|
||||||
|
[x_axis[1], y_axis[1], z_axis[1], start.y],
|
||||||
|
[x_axis[2], y_axis[2], z_axis[2], start.z],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
obj.matrix_world = matrix
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
|
joiner = DumbProfileJoiner()
|
||||||
|
joiner.set_depth(obj, depth / self.unit_scale)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -16,30 +16,21 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# 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/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
|
||||||
import gpu
|
|
||||||
import blf
|
|
||||||
import bgl
|
|
||||||
import bpy
|
import bpy
|
||||||
import math
|
|
||||||
import json
|
import json
|
||||||
import bmesh
|
import bmesh
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.util.type
|
import ifcopenshell.util.type
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import mathutils.geometry
|
|
||||||
import blenderbim.bim.handler
|
import blenderbim.bim.handler
|
||||||
import blenderbim.core.type
|
import blenderbim.core.type
|
||||||
import blenderbim.core.geometry
|
import blenderbim.core.geometry
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from bpy.types import SpaceView3D
|
from math import radians
|
||||||
from blenderbim.bim.ifc import IfcStore
|
|
||||||
from math import pi, degrees, sin, cos, radians
|
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from blenderbim.bim.module.geometry.helper import Helper
|
from blenderbim.bim.module.geometry.helper import Helper
|
||||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_quantities(usecase_path, ifc_file, settings):
|
def calculate_quantities(usecase_path, ifc_file, settings):
|
||||||
@@ -123,8 +114,8 @@ class DumbSlabGenerator:
|
|||||||
self.relating_type = relating_type
|
self.relating_type = relating_type
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.file = IfcStore.get_file()
|
self.file = tool.Ifc.get()
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
thicknesses = []
|
thicknesses = []
|
||||||
for rel in self.relating_type.HasAssociations:
|
for rel in self.relating_type.HasAssociations:
|
||||||
if rel.is_a("IfcRelAssociatesMaterial"):
|
if rel.is_a("IfcRelAssociatesMaterial"):
|
||||||
@@ -268,7 +259,7 @@ class DumbSlabPlaner:
|
|||||||
|
|
||||||
def change_thickness(self, element, thickness):
|
def change_thickness(self, element, thickness):
|
||||||
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||||
obj = IfcStore.get_element(element.id())
|
obj = tool.Ifc.get_object(element)
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -573,7 +564,7 @@ class DisableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
return context.selected_objects
|
return context.selected_objects
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
DecorationsHandler.uninstall()
|
ProfileDecorator.uninstall()
|
||||||
bpy.ops.object.mode_set(mode="OBJECT")
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
@@ -620,7 +611,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position)
|
tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position)
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
DecorationsHandler.install(context)
|
ProfileDecorator.install(context)
|
||||||
if not bpy.app.background:
|
if not bpy.app.background:
|
||||||
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -633,7 +624,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
DecorationsHandler.uninstall()
|
ProfileDecorator.uninstall()
|
||||||
bpy.ops.object.mode_set(mode="OBJECT")
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
@@ -657,7 +648,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
self.layout.label(text="INVALID PROFILE")
|
self.layout.label(text="INVALID PROFILE")
|
||||||
|
|
||||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
DecorationsHandler.install(context)
|
ProfileDecorator.install(context)
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -749,245 +740,3 @@ class SetArcIndex(bpy.types.Operator):
|
|||||||
group.add(selected_vertices, 1, "REPLACE")
|
group.add(selected_vertices, 1, "REPLACE")
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class DecorationsHandler:
|
|
||||||
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 __call__(self, context):
|
|
||||||
obj = context.active_object
|
|
||||||
if obj.mode != "EDIT":
|
|
||||||
return
|
|
||||||
|
|
||||||
bgl.glLineWidth(2)
|
|
||||||
bgl.glPointSize(6)
|
|
||||||
bgl.glEnable(bgl.GL_BLEND)
|
|
||||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
|
||||||
|
|
||||||
all_vertices = []
|
|
||||||
error_vertices = []
|
|
||||||
selected_vertices = []
|
|
||||||
unselected_vertices = []
|
|
||||||
special_vertices = []
|
|
||||||
special_vertex_indices = {}
|
|
||||||
selected_edges = []
|
|
||||||
unselected_edges = []
|
|
||||||
special_edges = [] # edges that have a circle or an arc associated with them
|
|
||||||
|
|
||||||
arc_groups = []
|
|
||||||
circle_groups = []
|
|
||||||
for i, group in enumerate(obj.vertex_groups):
|
|
||||||
if "IFCARCINDEX" in group.name:
|
|
||||||
arc_groups.append(i)
|
|
||||||
elif "IFCCIRCLE" in group.name:
|
|
||||||
circle_groups.append(i)
|
|
||||||
|
|
||||||
arcs = {}
|
|
||||||
circles = {}
|
|
||||||
|
|
||||||
bm = bmesh.from_edit_mesh(obj.data)
|
|
||||||
|
|
||||||
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
|
||||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
|
||||||
deform_layer = bm.verts.layers.deform.active
|
|
||||||
|
|
||||||
for vertex in bm.verts:
|
|
||||||
co = tuple(obj.matrix_world @ vertex.co)
|
|
||||||
all_vertices.append(co)
|
|
||||||
if vertex.hide:
|
|
||||||
continue
|
|
||||||
|
|
||||||
is_arc = False
|
|
||||||
for group_index in arc_groups:
|
|
||||||
if group_index in vertex[deform_layer]:
|
|
||||||
is_arc = True
|
|
||||||
break
|
|
||||||
if is_arc:
|
|
||||||
arcs.setdefault(group_index, []).append(vertex)
|
|
||||||
special_vertex_indices[vertex.index] = group_index
|
|
||||||
|
|
||||||
is_circle = False
|
|
||||||
for group_index in circle_groups:
|
|
||||||
if group_index in vertex[deform_layer]:
|
|
||||||
is_circle = True
|
|
||||||
break
|
|
||||||
if is_circle:
|
|
||||||
circles.setdefault(group_index, []).append(vertex)
|
|
||||||
special_vertex_indices[vertex.index] = group_index
|
|
||||||
|
|
||||||
if vertex.select:
|
|
||||||
selected_vertices.append(co)
|
|
||||||
else:
|
|
||||||
if len(vertex.link_edges) > 1 and is_circle:
|
|
||||||
error_vertices.append(co)
|
|
||||||
elif is_circle:
|
|
||||||
special_vertices.append(co)
|
|
||||||
elif len(vertex.link_edges) != 2:
|
|
||||||
error_vertices.append(co)
|
|
||||||
elif is_arc:
|
|
||||||
special_vertices.append(co)
|
|
||||||
else:
|
|
||||||
unselected_vertices.append(co)
|
|
||||||
|
|
||||||
for edge in bm.edges:
|
|
||||||
edge_indices = [v.index for v in edge.verts]
|
|
||||||
if edge.hide:
|
|
||||||
continue
|
|
||||||
if edge.select:
|
|
||||||
selected_edges.append(edge_indices)
|
|
||||||
else:
|
|
||||||
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
|
||||||
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
|
||||||
special_edges.append(edge_indices)
|
|
||||||
else:
|
|
||||||
unselected_edges.append(edge_indices)
|
|
||||||
indices = [[v.index for v in e.verts] for e in bm.edges]
|
|
||||||
|
|
||||||
white = (1, 1, 1, 1)
|
|
||||||
green = (0.545, 0.863, 0, 1)
|
|
||||||
red = (1, 0.2, 0.322, 1)
|
|
||||||
blue = (0.157, 0.565, 1, 1)
|
|
||||||
grey = (0.2, 0.2, 0.2, 1)
|
|
||||||
|
|
||||||
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=unselected_edges)
|
|
||||||
self.shader.bind()
|
|
||||||
self.shader.uniform_float("color", white)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=selected_edges)
|
|
||||||
self.shader.uniform_float("color", green)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "LINES", {"pos": all_vertices}, indices=special_edges)
|
|
||||||
self.shader.uniform_float("color", grey)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": unselected_vertices})
|
|
||||||
self.shader.uniform_float("color", white)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": error_vertices})
|
|
||||||
self.shader.uniform_float("color", red)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": special_vertices})
|
|
||||||
self.shader.uniform_float("color", blue)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": selected_vertices})
|
|
||||||
self.shader.uniform_float("color", green)
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
# Draw arcs
|
|
||||||
|
|
||||||
arc_centroids = []
|
|
||||||
arc_segments = []
|
|
||||||
for arc in arcs.values():
|
|
||||||
if len(arc) != 3:
|
|
||||||
continue
|
|
||||||
sorted_arc = [None, None, None]
|
|
||||||
for v1 in arc:
|
|
||||||
connections = 0
|
|
||||||
for link_edge in v1.link_edges:
|
|
||||||
v2 = link_edge.other_vert(v1)
|
|
||||||
if v2 in arc:
|
|
||||||
connections += 1
|
|
||||||
if connections == 2: # Midpoint
|
|
||||||
sorted_arc[1] = v1
|
|
||||||
else:
|
|
||||||
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
|
||||||
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
|
||||||
centroid = tool.Cad.get_center_of_arc(points)
|
|
||||||
if centroid:
|
|
||||||
arc_centroids.append(tuple(centroid))
|
|
||||||
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
|
|
||||||
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
for verts, edges in arc_segments:
|
|
||||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
|
||||||
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
# Draw circles
|
|
||||||
|
|
||||||
circle_centroids = []
|
|
||||||
circle_segments = []
|
|
||||||
for circle in circles.values():
|
|
||||||
if len(circle) != 2:
|
|
||||||
continue
|
|
||||||
p1 = obj.matrix_world @ circle[0].co
|
|
||||||
p2 = obj.matrix_world @ circle[1].co
|
|
||||||
radius = (p2 - p1).length / 2
|
|
||||||
centroid = p1.lerp(p2, 0.5)
|
|
||||||
circle_centroids.append(tuple(centroid))
|
|
||||||
segments = self.create_circle_segments(360, 20, radius)
|
|
||||||
matrix = obj.matrix_world.copy()
|
|
||||||
matrix.col[3] = centroid.to_4d()
|
|
||||||
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
|
||||||
circle_segments.append(segments)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, "POINTS", {"pos": circle_centroids})
|
|
||||||
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
for verts, edges in circle_segments:
|
|
||||||
batch = batch_for_shader(self.shader, "LINES", {"pos": verts}, indices=edges)
|
|
||||||
self.shader.uniform_float("color", (0.157, 0.565, 1, 1))
|
|
||||||
batch.draw(self.shader)
|
|
||||||
|
|
||||||
def create_matrix(self, p, x, y, z):
|
|
||||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
|
||||||
|
|
||||||
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
|
|
||||||
# This function is taken from Sverchok, licensed under GPL v2-or-later.
|
|
||||||
# This is a combination of the make_verts and make_edges function.
|
|
||||||
def create_circle_segments(self, Angle, Vertices, Radius):
|
|
||||||
if Angle < 360:
|
|
||||||
theta = Angle / (Vertices - 1)
|
|
||||||
else:
|
|
||||||
theta = Angle / Vertices
|
|
||||||
listVertX = []
|
|
||||||
listVertY = []
|
|
||||||
for i in range(Vertices):
|
|
||||||
listVertX.append(Radius * cos(radians(theta * i)))
|
|
||||||
listVertY.append(Radius * sin(radians(theta * i)))
|
|
||||||
|
|
||||||
if Angle < 360 and self.mode_ == 0:
|
|
||||||
sigma = radians(Angle)
|
|
||||||
listVertX[-1] = Radius * cos(sigma)
|
|
||||||
listVertY[-1] = Radius * sin(sigma)
|
|
||||||
elif Angle < 360 and self.mode_ == 1:
|
|
||||||
listVertX.append(0.0)
|
|
||||||
listVertY.append(0.0)
|
|
||||||
|
|
||||||
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
|
|
||||||
|
|
||||||
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
|
|
||||||
|
|
||||||
if Angle < 360 and self.mode_ == 1:
|
|
||||||
listEdg.append((0, Vertices))
|
|
||||||
listEdg.append((Vertices - 1, Vertices))
|
|
||||||
else:
|
|
||||||
listEdg.append((Vertices - 1, 0))
|
|
||||||
|
|
||||||
return points, listEdg
|
|
||||||
|
|||||||
@@ -83,17 +83,18 @@ class BimTool(WorkSpaceTool):
|
|||||||
("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.hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
("bim.hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
|
||||||
("bim.hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
("bim.hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
|
||||||
|
("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": "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")]}),
|
||||||
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
|
||||||
("bim.hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
("bim.hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||||
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
|
||||||
("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||||
("bim.hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
|
("bim.hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
|
||||||
("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
|
("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
|
||||||
|
("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
|
||||||
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
|
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
|
||||||
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def draw_settings(context, layout, ws_tool):
|
def draw_settings(context, layout, ws_tool):
|
||||||
@@ -253,6 +254,10 @@ class BimToolUI:
|
|||||||
row.label(text="", icon="EVENT_E")
|
row.label(text="", icon="EVENT_E")
|
||||||
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
|
row.operator("bim.hotkey", text="Extend").hotkey = "S_E"
|
||||||
row = cls.layout.row(align=True)
|
row = cls.layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_ALT")
|
||||||
|
row.label(text="", icon="EVENT_E")
|
||||||
|
row.operator("bim.hotkey", text="Edit Axis").hotkey = "A_E"
|
||||||
|
row = cls.layout.row(align=True)
|
||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="", icon="EVENT_T")
|
row.label(text="", icon="EVENT_T")
|
||||||
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
|
row.operator("bim.hotkey", text="Butt").hotkey = "S_T"
|
||||||
@@ -607,6 +612,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
return
|
return
|
||||||
bpy.ops.bim.select_decomposition()
|
bpy.ops.bim.select_decomposition()
|
||||||
|
|
||||||
|
def hotkey_A_E(self):
|
||||||
|
if not bpy.context.selected_objects:
|
||||||
|
return
|
||||||
|
if self.active_material_usage == "PROFILE":
|
||||||
|
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||||
|
|
||||||
def hotkey_A_O(self):
|
def hotkey_A_O(self):
|
||||||
if not bpy.context.selected_objects:
|
if not bpy.context.selected_objects:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -72,4 +72,4 @@ class ProfileData:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def is_editing_arbitrary_profile(cls):
|
def is_editing_arbitrary_profile(cls):
|
||||||
obj = bpy.context.active_object
|
obj = bpy.context.active_object
|
||||||
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
|
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import ifcopenshell.api
|
|||||||
import blenderbim.bim.helper
|
import blenderbim.bim.helper
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import blenderbim.bim.module.model.profile as model_profile
|
import blenderbim.bim.module.model.profile as model_profile
|
||||||
from blenderbim.bim.module.model.slab import DecorationsHandler
|
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||||
from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile
|
from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bpy.context.scene.collection.objects.link(obj)
|
bpy.context.scene.collection.objects.link(obj)
|
||||||
bpy.context.view_layer.objects.active = obj
|
bpy.context.view_layer.objects.active = obj
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
DecorationsHandler.install(context)
|
ProfileDecorator.install(context)
|
||||||
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
|
||||||
|
|
||||||
|
|
||||||
@@ -149,8 +149,8 @@ class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if obj and obj.data and obj.data.BIMMeshProperties.is_profile:
|
if obj and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||||
DecorationsHandler.uninstall()
|
ProfileDecorator.uninstall()
|
||||||
bpy.ops.object.mode_set(mode="OBJECT")
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
bpy.data.objects.remove(obj)
|
bpy.data.objects.remove(obj)
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
|
|
||||||
DecorationsHandler.uninstall()
|
ProfileDecorator.uninstall()
|
||||||
bpy.ops.object.mode_set(mode="OBJECT")
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
|
||||||
profile = tool.Model.export_profile(obj)
|
profile = tool.Model.export_profile(obj)
|
||||||
@@ -176,7 +176,7 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
self.layout.label(text="INVALID PROFILE: " + indices[1])
|
self.layout.label(text="INVALID PROFILE: " + indices[1])
|
||||||
|
|
||||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
DecorationsHandler.install(context)
|
ProfileDecorator.install(context)
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class BIMMeshProperties(PropertyGroup):
|
|||||||
is_native: BoolProperty(name="Is Native", default=False)
|
is_native: BoolProperty(name="Is Native", default=False)
|
||||||
is_swept_solid: BoolProperty(name="Is Swept Solid")
|
is_swept_solid: BoolProperty(name="Is Swept Solid")
|
||||||
is_parametric: BoolProperty(name="Is Parametric", default=False)
|
is_parametric: BoolProperty(name="Is Parametric", default=False)
|
||||||
is_profile: BoolProperty(name="Is Profile", default=False)
|
subshape_type: StringProperty(name="Subshape Type")
|
||||||
ifc_definition: StringProperty(name="IFC Definition")
|
ifc_definition: StringProperty(name="IFC Definition")
|
||||||
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
|
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
|
||||||
material_checksum: StringProperty(name="Material Checksum", default="[]")
|
material_checksum: StringProperty(name="Material Checksum", default="[]")
|
||||||
|
|||||||
@@ -140,6 +140,38 @@ class Model(blenderbim.core.tool.Model):
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def import_axis(cls, axis, obj=None, position=None):
|
||||||
|
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
|
||||||
|
if position is None:
|
||||||
|
position = Matrix()
|
||||||
|
|
||||||
|
cls.vertices = []
|
||||||
|
cls.edges = []
|
||||||
|
cls.arcs = []
|
||||||
|
cls.circles = []
|
||||||
|
|
||||||
|
if isinstance(axis, list):
|
||||||
|
cls.vertices.extend([
|
||||||
|
position @ Vector(cls.convert_unit_to_si(axis[0])).to_3d(),
|
||||||
|
position @ Vector(cls.convert_unit_to_si(axis[1])).to_3d(),
|
||||||
|
])
|
||||||
|
cls.edges.append([0, 1])
|
||||||
|
else:
|
||||||
|
cls.import_curve(obj, position, axis)
|
||||||
|
|
||||||
|
mesh = bpy.data.meshes.new("Axis")
|
||||||
|
mesh.from_pydata(cls.vertices, cls.edges, [])
|
||||||
|
mesh.BIMMeshProperties.subshape_type = "AXIS"
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
obj = bpy.data.objects.new("Axis", mesh)
|
||||||
|
else:
|
||||||
|
obj.data = mesh
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def import_profile(cls, profile, obj=None, position=None):
|
def import_profile(cls, profile, obj=None, position=None):
|
||||||
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
@@ -162,7 +194,7 @@ class Model(blenderbim.core.tool.Model):
|
|||||||
|
|
||||||
mesh = bpy.data.meshes.new("Profile")
|
mesh = bpy.data.meshes.new("Profile")
|
||||||
mesh.from_pydata(cls.vertices, cls.edges, [])
|
mesh.from_pydata(cls.vertices, cls.edges, [])
|
||||||
mesh.BIMMeshProperties.is_profile = True
|
mesh.BIMMeshProperties.subshape_type = "PROFILE"
|
||||||
|
|
||||||
if obj is None:
|
if obj is None:
|
||||||
obj = bpy.data.objects.new("Profile", mesh)
|
obj = bpy.data.objects.new("Profile", mesh)
|
||||||
@@ -195,6 +227,7 @@ class Model(blenderbim.core.tool.Model):
|
|||||||
cls.edges[-1] = (len(cls.vertices) - 1, offset) # Close the loop
|
cls.edges[-1] = (len(cls.vertices) - 1, offset) # Close the loop
|
||||||
elif curve.is_a("IfcIndexedPolyCurve"):
|
elif curve.is_a("IfcIndexedPolyCurve"):
|
||||||
is_arc = False
|
is_arc = False
|
||||||
|
is_closed = False
|
||||||
if curve.Segments:
|
if curve.Segments:
|
||||||
for segment in curve.Segments:
|
for segment in curve.Segments:
|
||||||
if len(segment[0]) == 3: # IfcArcIndex
|
if len(segment[0]) == 3: # IfcArcIndex
|
||||||
@@ -213,15 +246,21 @@ class Model(blenderbim.core.tool.Model):
|
|||||||
if is_arc:
|
if is_arc:
|
||||||
cls.arcs[-1].append(len(cls.vertices) - 1)
|
cls.arcs[-1].append(len(cls.vertices) - 1)
|
||||||
is_arc = False
|
is_arc = False
|
||||||
|
|
||||||
|
if curve.Segments[0][0][0] == curve.Segments[-1][0][-1]:
|
||||||
|
is_closed = True
|
||||||
else:
|
else:
|
||||||
for local_point in curve.Points.CoordList:
|
for local_point in curve.Points.CoordList:
|
||||||
global_point = position @ Vector(cls.convert_unit_to_si(local_point)).to_3d()
|
global_point = position @ Vector(cls.convert_unit_to_si(local_point)).to_3d()
|
||||||
cls.vertices.append(global_point)
|
cls.vertices.append(global_point)
|
||||||
# Curves without segments are cls closing
|
|
||||||
del cls.vertices[-1]
|
|
||||||
|
|
||||||
cls.edges.extend([(i, i + 1) for i in range(offset, len(cls.vertices))])
|
if cls.vertices[offset] == cls.vertices[-1]:
|
||||||
cls.edges[-1] = (len(cls.vertices) - 1, offset) # Close the loop
|
is_closed = True
|
||||||
|
del cls.vertices[-1]
|
||||||
|
|
||||||
|
cls.edges.extend([(i, i + 1) for i in range(offset, len(cls.vertices) - 1)])
|
||||||
|
if is_closed:
|
||||||
|
cls.edges.append([len(cls.vertices) - 1, offset]) # Close the loop
|
||||||
elif curve.is_a("IfcCircle"):
|
elif curve.is_a("IfcCircle"):
|
||||||
center = cls.convert_unit_to_si(
|
center = cls.convert_unit_to_si(
|
||||||
Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist()).col[3].to_3d()
|
Matrix(ifcopenshell.util.placement.get_axis2placement(curve.Position).tolist()).col[3].to_3d()
|
||||||
|
|||||||
Reference in New Issue
Block a user