mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Added new angle annotations operator
New operator should be more intuitive to work with. I kept the old one too for now.
This commit is contained in:
@@ -32,6 +32,12 @@ from mathutils import Vector, Matrix
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from math import acos, pi
|
||||
|
||||
|
||||
def ccw(A, B, C):
|
||||
"""whether a-b-c located in counter-clockwise order in 2d space"""
|
||||
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
|
||||
|
||||
|
||||
class BaseDecorator:
|
||||
@@ -66,6 +72,42 @@ class BaseDecorator:
|
||||
}
|
||||
}
|
||||
|
||||
bool check_counterclockwise(in vec4 A, in vec4 B, in vec4 C) {
|
||||
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x);
|
||||
}
|
||||
|
||||
void angle_circle_head(
|
||||
in vec4 circle_start, in float circle_angle,
|
||||
in bool counterclockwise,
|
||||
out vec4 head[CIRCLE_SEGS+1], out float angle_segs) {
|
||||
|
||||
// 1 added to CIRCLE_SEGS because we're number of vertices
|
||||
// for n segments is n+1
|
||||
|
||||
float angle_d;
|
||||
angle_d = PI * 2 / CIRCLE_SEGS; // 30d
|
||||
// need to bottom clamp it to 1, otherwise it causes Blender crash at extruding the curve
|
||||
angle_segs = max(1, ceil(circle_angle / angle_d));
|
||||
angle_d = circle_angle / angle_segs;
|
||||
|
||||
for(int i = 0; i < (angle_segs + 1); i++) {
|
||||
float angle = angle_d * i;
|
||||
if (counterclockwise) {
|
||||
head[i] = vec4(
|
||||
circle_start.x * cos(-angle) + circle_start.y * sin(-angle),
|
||||
circle_start.x * -sin(-angle) + circle_start.y * cos(-angle),
|
||||
0, 0
|
||||
);
|
||||
} else {
|
||||
head[i] = vec4(
|
||||
circle_start.x * cos(angle) + circle_start.y * sin(angle),
|
||||
circle_start.x * -sin(angle) + circle_start.y * cos(angle),
|
||||
0, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cross_head(in vec4 dir, in float size, out vec4 head[3]) {
|
||||
vec4 nose = dir * size;
|
||||
float c = cos(PI/2), s = sin(PI/2);
|
||||
@@ -79,12 +121,15 @@ class BaseDecorator:
|
||||
uniform mat4 viewMatrix;
|
||||
in vec3 pos;
|
||||
in uint topo;
|
||||
in vec3 next_vert;
|
||||
out vec4 gl_Position;
|
||||
out uint type;
|
||||
out vec4 v_next_vert;
|
||||
|
||||
void main() {
|
||||
gl_Position = viewMatrix * vec4(pos, 1.0);
|
||||
type = topo;
|
||||
v_next_vert = viewMatrix * vec4(next_vert, 1.0);
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -267,7 +312,13 @@ class BaseDecorator:
|
||||
"""perform actual drawing stuff"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def draw_lines(self, context, obj, vertices, indices, topology=None, is_scale_dependant=True):
|
||||
def draw_lines(
|
||||
self, context, obj, vertices, indices, topology=None, is_scale_dependant=True, fill_next_vertices=False
|
||||
):
|
||||
# use is_scale_dependant = False if shader is not using uniform viewportDrawingScale
|
||||
# otherwise uniform will be discarded during the optimization process
|
||||
# and you will get "ValueError: GPUShader.uniform_float: uniform viewportDrawingScale not found"
|
||||
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
color = context.scene.DocProperties.decorations_colour
|
||||
@@ -276,12 +327,18 @@ class BaseDecorator:
|
||||
fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT")
|
||||
if topology:
|
||||
fmt.attr_add(id="topo", comp_type="U8", len=1, fetch_mode="INT")
|
||||
if fill_next_vertices:
|
||||
fmt.attr_add(id="next_vert", comp_type="F32", len=3, fetch_mode="FLOAT")
|
||||
|
||||
vbo = GPUVertBuf(len=len(vertices), format=fmt)
|
||||
vbo.attr_fill(id="pos", data=vertices)
|
||||
if topology:
|
||||
vbo.attr_fill(id="topo", data=topology)
|
||||
|
||||
if fill_next_vertices:
|
||||
shifted_vertices = vertices[1:] + [vertices[0]]
|
||||
vbo.attr_fill(id="next_vert", data=shifted_vertices)
|
||||
|
||||
ibo = GPUIndexBuf(type="LINES", seq=indices)
|
||||
|
||||
batch = GPUBatch(type="LINES", buf=vbo, elem=ibo)
|
||||
@@ -480,6 +537,132 @@ class DimensionDecorator(BaseDecorator):
|
||||
self.draw_label(context, text, p0 + (dir) * 0.5, dir)
|
||||
|
||||
|
||||
class AngleDecorator(BaseDecorator):
|
||||
"""Decorator for angle objects
|
||||
- each edge of a segment with arrow
|
||||
- every non-last edge has angle circle
|
||||
- every circle is labeled with angle in degrees
|
||||
"""
|
||||
|
||||
objecttype = "ANGLE"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define ARROW_ANGLE PI / 12.0
|
||||
#define ARROW_SIZE 8.0
|
||||
#define CIRCLE_SIZE 6.0
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
in uint type[];
|
||||
in vec4 v_next_vert[];
|
||||
|
||||
// per edge shader
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
vec4 p2 = v_next_vert[1];
|
||||
uint t0 = type[0], t1 = type[1];
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 p2w = CLIP2WIN(p2);
|
||||
vec4 edge0 = p1w - p0w, dir = normalize(edge0);
|
||||
|
||||
vec4 p;
|
||||
// draw a segment line
|
||||
p = p0w;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// end edge with angle circle for the non-last segment
|
||||
if (t1 == 0u) { // draws only on internal vertex
|
||||
edge0 = p0w - p1w;
|
||||
vec4 dir0 = normalize(edge0);
|
||||
vec4 edge1 = p2w - p1w;
|
||||
vec4 dir1 = normalize(edge1);
|
||||
|
||||
float angle_circle_size = min( length(edge0), length(edge1) );
|
||||
vec4 circle_start = dir0 * angle_circle_size;
|
||||
vec4 circle_end = dir1 * angle_circle_size;
|
||||
|
||||
float cos_a = dot( edge0, edge1 ) / ( length(edge0) * length(edge1) );
|
||||
float circle_angle = acos(cos_a);
|
||||
|
||||
vec4 circle_head_data[CIRCLE_SEGS+1];
|
||||
float angle_segs;
|
||||
bool counterclockwise = check_counterclockwise(p2w, p1w, p0w);
|
||||
angle_circle_head(
|
||||
circle_start,
|
||||
circle_angle,
|
||||
counterclockwise,
|
||||
circle_head_data,
|
||||
angle_segs);
|
||||
|
||||
for(int i=0; i<angle_segs+1; i++) {
|
||||
p = p1w + circle_head_data[i];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def decorate(self, context, obj):
|
||||
verts, idxs, topo = self.get_path_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs, topo, fill_next_vertices=True, is_scale_dependant=False)
|
||||
self.draw_labels(context, obj, verts, idxs)
|
||||
|
||||
def draw_labels(self, context, obj, vertices, indices):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
|
||||
last_segment_i = len(indices) - 1
|
||||
for edge_i, edge_vertices in enumerate(indices):
|
||||
if edge_i == last_segment_i:
|
||||
continue
|
||||
|
||||
# draw angle label
|
||||
i0, i1 = edge_vertices
|
||||
v0 = Vector(vertices[i0])
|
||||
v1 = Vector(vertices[i1])
|
||||
v2 = Vector(vertices[i1 + 1])
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
p2 = location_3d_to_region_2d(region, region3d, v2)
|
||||
|
||||
edge0 = p0 - p1
|
||||
edge1 = p2 - p1
|
||||
cos_a = edge0.dot(edge1) / (edge0.length * edge1.length)
|
||||
circle_angle = acos(cos_a) / pi * 180
|
||||
|
||||
text = f"{int(circle_angle)}d"
|
||||
|
||||
# TODO: set label position pased on p1
|
||||
# + y relative to p0p1 if p0p1p2 is clockwise
|
||||
# - y relative to p0p1 if p0p1p2 is counter-clockwise
|
||||
# counter_clockwise = ccw(p0, p1, p2)
|
||||
# label_position = (p1 + Vector( (0, 10) ) * (1 if counter_clockwise else -1)) + edge1 * 0.1
|
||||
label_position = p1 + edge1 * 0.1
|
||||
|
||||
# TODO: set label direction based on the first edge (p0, p1)
|
||||
label_dir = Vector((1, 0))
|
||||
self.draw_label(context, text, label_position, label_dir)
|
||||
|
||||
|
||||
class DiameterDecorator(DimensionDecorator):
|
||||
objecttype = "DIAMETER"
|
||||
|
||||
@@ -895,7 +1078,6 @@ class LevelDecorator(BaseDecorator):
|
||||
def decorate(self, context, obj):
|
||||
verts, idxs, topo = self.get_path_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs, topo)
|
||||
splines = self.get_splines(obj)
|
||||
self.draw_labels(context, obj, splines)
|
||||
|
||||
|
||||
@@ -1585,6 +1767,7 @@ class TextDecorator(BaseDecorator):
|
||||
class DecorationsHandler:
|
||||
decorators_classes = [
|
||||
DimensionDecorator,
|
||||
AngleDecorator,
|
||||
GridDecorator,
|
||||
HiddenDecorator,
|
||||
LeaderDecorator,
|
||||
|
||||
@@ -34,6 +34,7 @@ import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
class External(svgwrite.container.Group):
|
||||
@@ -140,6 +141,8 @@ class SvgWriter:
|
||||
self.draw_dimension_annotations(obj)
|
||||
elif element.ObjectType == "ANGLE":
|
||||
self.draw_angle_annotations(obj)
|
||||
elif element.ObjectType == "ANGLE_OLD":
|
||||
self.draw_angle_old_annotations(obj)
|
||||
elif element.ObjectType == "RADIUS":
|
||||
self.draw_radius_annotations(obj)
|
||||
elif element.ObjectType == "DIAMETER":
|
||||
@@ -619,19 +622,52 @@ class SvgWriter:
|
||||
)
|
||||
|
||||
def draw_angle_annotations(self, obj):
|
||||
# calculate p3 which is the center of the arc
|
||||
# to use draw_svg_3point_arc()
|
||||
points = obj.data.splines[0].points
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
points_2d = [view3d_utils.location_3d_to_region_2d(region, region_3d, p.co.xyz) for p in points]
|
||||
|
||||
edge0 = points_2d[0] - points_2d[1]
|
||||
edge1 = points_2d[2] - points_2d[1]
|
||||
angle_radius = min(edge0.length, edge1.length)
|
||||
dir0 = edge0.normalized()
|
||||
dir1 = edge1.normalized()
|
||||
dir2 = ((dir0 + dir1) / 2).normalized()
|
||||
|
||||
p3 = points_2d[1] + dir2 * angle_radius
|
||||
|
||||
# make all edges the same radius
|
||||
p0 = points_2d[1] + dir0 * angle_radius
|
||||
p2 = points_2d[1] + dir1 * angle_radius
|
||||
points = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
|
||||
# points = [p.co.xyz for p in bpy.context.object.data.splines[0].points[:3]]
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
new_verts = [bm.verts.new(p) for p in points]
|
||||
new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in ((0, 1), (1, 2))]
|
||||
self.draw_svg_3point_arc(obj, bm)
|
||||
|
||||
def draw_angle_old_annotations(self, obj):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bm.verts.ensure_lookup_table()
|
||||
self.draw_svg_3point_arc(obj, bm)
|
||||
|
||||
def draw_svg_3point_arc(self, obj, bm):
|
||||
# This implementation uses an SVG arc, which means that it can only draw
|
||||
# arcs that are orthogonal to the view (e.g. not arcs in 3D).
|
||||
# Gosh this is bad code :(
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
points = [v.co for v in bm.verts][:3]
|
||||
center = tool.Cad.get_center_of_arc(points, obj)
|
||||
classes = self.get_attribute_classes(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
|
||||
points = [v.co for v in obj.data.vertices][:3]
|
||||
center = tool.Cad.get_center_of_arc(points, obj)
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
bm.verts.ensure_lookup_table()
|
||||
arc_end_verts = [v for v in bm.verts if len(v.link_edges) == 1]
|
||||
arc_end_pts = [matrix_world @ v.co for v in arc_end_verts]
|
||||
@@ -680,11 +716,11 @@ class SvgWriter:
|
||||
|
||||
# Center of gravity of all vertices, used to help position the text
|
||||
cog = Vector((0, 0, 0))
|
||||
for vert in obj.data.vertices:
|
||||
cog += vert.co
|
||||
cog = matrix_world @ (cog / len(obj.data.vertices))
|
||||
for point in points:
|
||||
cog += point
|
||||
cog = matrix_world @ (cog / len(points))
|
||||
|
||||
radius = ((matrix_world @ obj.data.vertices[0].co) - center).length
|
||||
radius = ((matrix_world @ points[0]) - center).length
|
||||
|
||||
arc_midpoint = center + ((cog - center).normalized() * radius)
|
||||
|
||||
|
||||
@@ -171,10 +171,10 @@ class BIM_PT_drawings(Panel):
|
||||
active_drawing = self.props.drawings[self.props.active_drawing_index]
|
||||
row = self.layout.row(align=True)
|
||||
col = row.column()
|
||||
col.alignment = 'LEFT'
|
||||
col.alignment = "LEFT"
|
||||
col.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
|
||||
col = row.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.alignment = "RIGHT"
|
||||
op = row.operator("bim.open_view", icon="URL", text="")
|
||||
op.view = active_drawing.name
|
||||
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
|
||||
@@ -376,6 +376,9 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
op.object_type = "ANGLE"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Angle (old)", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
op.object_type = "ANGLE_OLD"
|
||||
op.data_type = "mesh"
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
||||
@@ -173,7 +173,7 @@ class DumbSlabGenerator:
|
||||
matrix_world[2][3] = self.collection_obj.location[2] - self.depth
|
||||
else:
|
||||
matrix_world[2][3] -= self.depth
|
||||
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, 'X') @ matrix_world
|
||||
obj.matrix_world = Matrix.Rotation(self.x_angle, 4, "X") @ matrix_world
|
||||
bpy.context.view_layer.update()
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
@@ -651,6 +651,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
profile = tool.Model.export_profile(obj, position=position)
|
||||
|
||||
if not profile:
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="INVALID PROFILE: " + indices[1])
|
||||
|
||||
@@ -784,7 +785,7 @@ class DecorationsHandler:
|
||||
special_vertex_indices = {}
|
||||
selected_edges = []
|
||||
unselected_edges = []
|
||||
special_edges = []
|
||||
special_edges = [] # edges that have a circle or an arc associated with them
|
||||
|
||||
arc_groups = []
|
||||
circle_groups = []
|
||||
|
||||
@@ -38,7 +38,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
@classmethod
|
||||
def create_annotation_object(cls, drawing, object_type):
|
||||
data_type = {
|
||||
"ANGLE": "mesh",
|
||||
"ANGLE": "curve",
|
||||
"ANGLE_OLD": "mesh",
|
||||
"BREAKLINE": "mesh",
|
||||
"DIAMETER": "curve",
|
||||
"DIMENSION": "curve",
|
||||
|
||||
Reference in New Issue
Block a user