# SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------------------------------------- # support routines for OpenGL # Author: Antonio Vazquez (antonioya) # # ---------------------------------------------------------- # noinspection PyUnresolvedReferences import bpy # noinspection PyUnresolvedReferences import blf from blf import ROTATION from math import fabs, degrees, radians, sqrt, cos, sin, pi from mathutils import Vector, Matrix from bmesh import from_edit_mesh from bpy_extras import view3d_utils, mesh_utils import bpy_extras.object_utils as object_utils from sys import exc_info # GPU import gpu from gpu_extras.batch import batch_for_shader shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') if not bpy.app.background else None shader_line = gpu.shader.from_builtin('3D_POLYLINE_UNIFORM_COLOR') if not bpy.app.background else None imm_line_width = 1.0 imm_viewport = (0, 0) def imm_set_line_width(width): global imm_line_width, imm_viewport region = bpy.context.region imm_viewport = (region.width, region.height) imm_line_width = width # ------------------------------------------------------------- # Draw segments # # ------------------------------------------------------------- # noinspection PyUnresolvedReferences,PyUnboundLocalVariable def draw_segments(context, myobj, op, region, rv3d): if op.measureit_num > 0: a_code = "\u00b0" # degree scale = bpy.context.scene.unit_settings.scale_length scene = bpy.context.scene pr = scene.measureit_gl_precision fmt = "%1." + str(pr) + "f" ovr = scene.measureit_ovr ovrcolor = scene.measureit_ovr_color ovrfsize = scene.measureit_ovr_font ovrfang = get_angle_in_rad(scene.measureit_ovr_font_rotation) ovrfaln = scene.measureit_ovr_font_align ovrline = scene.measureit_ovr_width units = scene.measureit_units fang = get_angle_in_rad(scene.measureit_font_rotation) # -------------------- # Scene Scale # -------------------- if scene.measureit_scale is True: prs = scene.measureit_scale_precision fmts = "%1." + str(prs) + "f" pos_2d = get_scale_txt_location(context) tx_dsp = fmts % scene.measureit_scale_factor tx_scale = scene.measureit_gl_scaletxt + " 1:" + tx_dsp draw_text(myobj, pos_2d, tx_scale, scene.measureit_scale_color, scene.measureit_scale_font, text_rot=fang) # -------------------- # Loop # -------------------- for idx in range(op.measureit_num): ms = op.measureit_segments[idx] if ovr is False: fsize = ms.glfont_size fang = get_angle_in_rad(ms.glfont_rotat) faln = ms.glfont_align else: fsize = ovrfsize fang = ovrfang faln = ovrfaln # ------------------------------ # only active and visible # ------------------------------ if ms.glview is True and ms.glfree is False: # Arrow data a_size = ms.glarrow_s a_type = ms.glarrow_a b_type = ms.glarrow_b # noinspection PyBroadException try: if ovr is False: rgba = ms.glcolor else: rgba = ovrcolor # ---------------------- # Segment or Label # ---------------------- if ms.gltype == 1 or ms.gltype == 2: obverts = get_mesh_vertices(myobj) if ms.glpointa <= len(obverts) and ms.glpointb <= len(obverts): a_p1 = get_point(obverts[ms.glpointa].co, myobj) b_p1 = get_point(obverts[ms.glpointb].co, myobj) # ---------------------- # Segment or Label # ---------------------- if ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14: obverts = get_mesh_vertices(myobj) if ms.glpointa <= len(obverts): a_p1 = get_point(obverts[ms.glpointa].co, myobj) if ms.gltype == 12: # X b_p1 = get_point((0.0, obverts[ms.glpointa].co[1], obverts[ms.glpointa].co[2]), myobj) elif ms.gltype == 13: # Y b_p1 = get_point((obverts[ms.glpointa].co[0], 0.0, obverts[ms.glpointa].co[2]), myobj) else: # Z b_p1 = get_point((obverts[ms.glpointa].co[0], obverts[ms.glpointa].co[1], 0.0), myobj) # ---------------------- # Vertex to Vertex (link) # ---------------------- if ms.gltype == 3: obverts = get_mesh_vertices(myobj) linkverts = bpy.data.objects[ms.gllink].data.vertices a_p1 = get_point(obverts[ms.glpointa].co, myobj) b_p1 = get_point(linkverts[ms.glpointb].co, bpy.data.objects[ms.gllink]) # ---------------------- # Vertex to Object (link) # ---------------------- if ms.gltype == 4: obverts = get_mesh_vertices(myobj) a_p1 = get_point(obverts[ms.glpointa].co, myobj) b_p1 = get_location(bpy.data.objects[ms.gllink]) # ---------------------- # Object to Vertex (link) # ---------------------- if ms.gltype == 5: linkverts = bpy.data.objects[ms.gllink].data.vertices a_p1 = get_location(myobj) b_p1 = get_point(linkverts[ms.glpointb].co, bpy.data.objects[ms.gllink]) # ---------------------- # Object to Object (link) # ---------------------- if ms.gltype == 8: a_p1 = get_location(myobj) b_p1 = get_location(bpy.data.objects[ms.gllink]) # ---------------------- # Vertex to origin # ---------------------- if ms.gltype == 6: obverts = get_mesh_vertices(myobj) a_p1 = (0, 0, 0) b_p1 = get_point(obverts[ms.glpointa].co, myobj) # ---------------------- # Object to origin # ---------------------- if ms.gltype == 7: a_p1 = (0, 0, 0) b_p1 = get_location(myobj) # ---------------------- # Angle # ---------------------- if ms.gltype == 9: obverts = get_mesh_vertices(myobj) if ms.glpointa <= len(obverts) and ms.glpointb <= len(obverts) and ms.glpointc <= len(obverts): an_p1 = get_point(obverts[ms.glpointa].co, myobj) an_p2 = get_point(obverts[ms.glpointb].co, myobj) an_p3 = get_point(obverts[ms.glpointc].co, myobj) ang_1 = Vector((an_p1[0] - an_p2[0], an_p1[1] - an_p2[1], an_p1[2] - an_p2[2])) ang_2 = Vector((an_p3[0] - an_p2[0], an_p3[1] - an_p2[1], an_p3[2] - an_p2[2])) ang_3 = ang_1 + ang_2 # Result vector a_p1 = (an_p2[0], an_p2[1], an_p2[2]) b_p1 = (0, 0, 0) # ---------------------- # Annotation # ---------------------- if ms.gltype == 10: a_p1 = get_location(myobj) b_p1 = get_location(myobj) # ---------------------- # Arc # ---------------------- if ms.gltype == 11: obverts = get_mesh_vertices(myobj) if ms.glpointa <= len(obverts) and ms.glpointb <= len(obverts) and ms.glpointc <= len(obverts): an_p1 = get_point(obverts[ms.glpointa].co, myobj) an_p2 = get_point(obverts[ms.glpointb].co, myobj) an_p3 = get_point(obverts[ms.glpointc].co, myobj) # reference for maths: http://en.wikipedia.org/wiki/Circumscribed_circle an_p12 = Vector((an_p1[0] - an_p2[0], an_p1[1] - an_p2[1], an_p1[2] - an_p2[2])) an_p13 = Vector((an_p1[0] - an_p3[0], an_p1[1] - an_p3[1], an_p1[2] - an_p3[2])) an_p21 = Vector((an_p2[0] - an_p1[0], an_p2[1] - an_p1[1], an_p2[2] - an_p1[2])) an_p23 = Vector((an_p2[0] - an_p3[0], an_p2[1] - an_p3[1], an_p2[2] - an_p3[2])) an_p31 = Vector((an_p3[0] - an_p1[0], an_p3[1] - an_p1[1], an_p3[2] - an_p1[2])) an_p32 = Vector((an_p3[0] - an_p2[0], an_p3[1] - an_p2[1], an_p3[2] - an_p2[2])) an_p12xp23 = an_p12.copy().cross(an_p23) # radius = an_p12.length * an_p23.length * an_p31.length / (2 * an_p12xp23.length) alpha = pow(an_p23.length, 2) * an_p12.dot(an_p13) / (2 * pow(an_p12xp23.length, 2)) beta = pow(an_p13.length, 2) * an_p21.dot(an_p23) / (2 * pow(an_p12xp23.length, 2)) gamma = pow(an_p12.length, 2) * an_p31.dot(an_p32) / (2 * pow(an_p12xp23.length, 2)) a_p1 = (alpha * an_p1[0] + beta * an_p2[0] + gamma * an_p3[0], alpha * an_p1[1] + beta * an_p2[1] + gamma * an_p3[1], alpha * an_p1[2] + beta * an_p2[2] + gamma * an_p3[2]) b_p1 = (an_p2[0], an_p2[1], an_p2[2]) a_n = an_p12.cross(an_p23) a_n.normalize() # normal vector arc_angle, arc_length = get_arc_data(an_p1, a_p1, an_p2, an_p3) # Apply scale to arc_length arc_length *= scene.measureit_scale_factor # ---------------------- # Area # ---------------------- if ms.gltype == 20: a_p1 = get_location(myobj) # Not used b_p1 = get_location(myobj) # Not used # Calculate distance dist, distloc = distance(a_p1, b_p1, ms.glocx, ms.glocy, ms.glocz) # ------------------------------------ # get normal vector # ------------------------------------ if ms.gldefault is True: if ms.gltype == 9: vn = ang_3 # if angle, vector is angle position elif ms.gltype == 11: vn = a_n # if arc, vector is perpendicular to surface of the three vertices else: loc = get_location(myobj) midpoint3d = interpolate3d(a_p1, b_p1, fabs(dist / 2)) vn = Vector((midpoint3d[0] - loc[0], midpoint3d[1] - loc[1], midpoint3d[2] - loc[2])) else: vn = Vector((ms.glnormalx, ms.glnormaly, ms.glnormalz)) vn.normalize() # ------------------------------------ # position vector # ------------------------------------ vi = vn * ms.glspace s = (14 / 200) if s > ms.glspace: s = ms.glspace / 5 vi2 = vn * (ms.glspace + s) # ------------------------------------ # apply vector # ------------------------------------ v1 = [a_p1[0] + vi[0], a_p1[1] + vi[1], a_p1[2] + vi[2]] v2 = [b_p1[0] + vi[0], b_p1[1] + vi[1], b_p1[2] + vi[2]] # Segment extreme v11 = [a_p1[0] + vi2[0], a_p1[1] + vi2[1], a_p1[2] + vi2[2]] v22 = [b_p1[0] + vi2[0], b_p1[1] + vi2[1], b_p1[2] + vi2[2]] # Labeling v11a = (a_p1[0] + vi2[0], a_p1[1] + vi2[1], a_p1[2] + vi2[2] + s / 30) v11b = (a_p1[0] + vi2[0], a_p1[1] + vi2[1], a_p1[2] + vi2[2] - s / 40) # Annotation vn1 = (a_p1[0], a_p1[1], a_p1[2]) # ------------------------------------------- # Orthogonal # ------------------------------------------- if ms.gltype == 1 and ms.glorto != "99": if ms.glorto == "0": # A if ms.glorto_x is True: v1[0] = v2[0] v11[0] = v22[0] if ms.glorto_y is True: v1[1] = v2[1] v11[1] = v22[1] if ms.glorto_z is True: v1[2] = v2[2] v11[2] = v22[2] if ms.glorto == "1": # B if ms.glorto_x is True: v2[0] = v1[0] v22[0] = v11[0] if ms.glorto_y is True: v2[1] = v1[1] v22[1] = v11[1] if ms.glorto_z is True: v2[2] = v1[2] v22[2] = v11[2] # ------------------------------------ # converting to screen coordinates # ------------------------------------ screen_point_ap1 = get_2d_point(region, rv3d, a_p1) screen_point_bp1 = get_2d_point(region, rv3d, b_p1) screen_point_v1 = get_2d_point(region, rv3d, v1) screen_point_v2 = get_2d_point(region, rv3d, v2) screen_point_v11 = get_2d_point(region, rv3d, v11) screen_point_v22 = get_2d_point(region, rv3d, v22) screen_point_v11a = get_2d_point(region, rv3d, v11a) screen_point_v11b = get_2d_point(region, rv3d, v11b) # ------------------------------------ # colour + line setup # ------------------------------------ if ovr is False: imm_set_line_width(ms.glwidth) else: imm_set_line_width(ovrline) # ------------------------------------ # Text (distance) # ------------------------------------ # noinspection PyBroadException if ms.gltype != 2 and ms.gltype != 9 and ms.gltype != 10 and ms.gltype != 11 and ms.gltype != 20: # noinspection PyBroadException try: midpoint3d = interpolate3d(v1, v2, fabs(dist / 2)) gap3d = (midpoint3d[0], midpoint3d[1], midpoint3d[2] + s / 2) tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is None: pass txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty # Scale if scene.measureit_scale is True: dist = dist * scene.measureit_scale_factor distloc = distloc * scene.measureit_scale_factor # decide dist to use if dist == distloc: locflag = False usedist = dist else: usedist = distloc locflag = True # Apply scene scale usedist *= scale tx_dist = str(format_distance(fmt, units, usedist)) # ----------------------------------- # Draw text # ----------------------------------- if scene.measureit_gl_show_d is True and ms.gldist is True: msg = tx_dist + " " else: msg = " " if scene.measureit_gl_show_n is True and ms.glnames is True: msg += ms.gltxt if scene.measureit_gl_show_d is True or scene.measureit_gl_show_n is True: draw_text(myobj, txtpoint2d, msg, rgba, fsize, faln, fang) # ------------------------------ # if axis loc, show a indicator # ------------------------------ if locflag is True and ms.glocwarning is True: txtpoint2d = get_2d_point(region, rv3d, (v2[0], v2[1], v2[2])) txt = "[" if ms.glocx is True: txt += "X" if ms.glocy is True: txt += "Y" if ms.glocz is True: txt += "Z" txt += "]" draw_text(myobj, txtpoint2d, txt, rgba, fsize - 1, text_rot=fang) except: pass # ------------------------------------ # Text (label) and Angles # ------------------------------------ # noinspection PyBroadException if ms.gltype == 2 or ms.gltype == 9 or ms.gltype == 11: tx_dist = "" # noinspection PyBroadException try: if ms.gltype == 2: tx_dist = ms.gltxt if ms.gltype == 9: # Angles ang = ang_1.angle(ang_2) if bpy.context.scene.unit_settings.system_rotation == "DEGREES": ang = degrees(ang) tx_dist = " " + fmt % ang # Add degree symbol if bpy.context.scene.unit_settings.system_rotation == "DEGREES": tx_dist += a_code if scene.measureit_gl_show_n is True: tx_dist += " " + ms.gltxt if ms.gltype == 11: # arc # print length or arc and angle if ms.glarc_len is True: tx_dist = ms.glarc_txlen + format_distance(fmt, units, arc_length * scale) else: tx_dist = " " if bpy.context.scene.unit_settings.system_rotation == "DEGREES": arc_d = degrees(arc_angle) else: arc_d = arc_angle if ms.glarc_ang is True: tx_dist += " " + ms.glarc_txang + format_distance(fmt, 9, arc_d) # Add degree symbol if bpy.context.scene.unit_settings.system_rotation == "DEGREES": tx_dist += a_code if scene.measureit_gl_show_d is True and ms.gldist is True: msg = tx_dist + " " else: msg = " " if scene.measureit_gl_show_n is True and ms.glnames is True: msg += ms.gltxt if scene.measureit_gl_show_d is True or scene.measureit_gl_show_n is True: # Normal vector vna = Vector((b_p1[0] - a_p1[0], b_p1[1] - a_p1[1], b_p1[2] - a_p1[2])) vna.normalize() via = vna * ms.glspace gap3d = (b_p1[0] + via[0], b_p1[1] + via[1], b_p1[2] + via[2]) tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty draw_text(myobj, txtpoint2d, msg, rgba, fsize, faln, fang) # Radius if scene.measureit_gl_show_d is True and ms.gldist is True and \ ms.glarc_rad is True: tx_dist = ms.glarc_txradio + format_distance(fmt, units, dist * scene.measureit_scale_factor * scale) else: tx_dist = " " if ms.gltype == 2: gap3d = (v11a[0], v11a[1], v11a[2]) else: gap3d = (a_p1[0], a_p1[1], a_p1[2]) tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty draw_text(myobj, txtpoint2d, tx_dist, rgba, fsize, faln, fang) except: pass # ------------------------------------ # Annotation # ------------------------------------ # noinspection PyBroadException if ms.gltype == 10: # noinspection PyBroadException tx_dist = ms.gltxt gap3d = (vn1[0], vn1[1], vn1[2]) tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty draw_text(myobj, txtpoint2d, tx_dist, rgba, fsize, faln, fang) # ------------------------------------ # Draw lines # ------------------------------------ gpu.state.blend_set('ALPHA') if ms.gltype == 1: # Segment draw_line(screen_point_ap1, screen_point_v11, rgba) draw_line(screen_point_bp1, screen_point_v22, rgba) draw_arrow(screen_point_v1, screen_point_v2, rgba, a_size, a_type, b_type) if ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14: # Segment to origin draw_line(screen_point_ap1, screen_point_v11, rgba) draw_line(screen_point_bp1, screen_point_v22, rgba) draw_arrow(screen_point_v1, screen_point_v2, rgba, a_size, a_type, b_type) if ms.gltype == 2: # Label draw_line(screen_point_v11a, screen_point_v11b, rgba) draw_arrow(screen_point_ap1, screen_point_v11, rgba, a_size, a_type, b_type) if ms.gltype == 3 or ms.gltype == 4 or ms.gltype == 5 or ms.gltype == 8 \ or ms.gltype == 6 or ms.gltype == 7: # Origin and Links draw_arrow(screen_point_ap1, screen_point_bp1, rgba, a_size, a_type, b_type) if ms.gltype == 9: # Angle dist, distloc = distance(an_p1, an_p2) mp1 = interpolate3d(an_p1, an_p2, fabs(dist / 1.1)) dist, distloc = distance(an_p3, an_p2) mp2 = interpolate3d(an_p3, an_p2, fabs(dist / 1.1)) screen_point_an_p1 = get_2d_point(region, rv3d, mp1) screen_point_an_p2 = get_2d_point(region, rv3d, an_p2) screen_point_an_p3 = get_2d_point(region, rv3d, mp2) draw_line(screen_point_an_p1, screen_point_an_p2, rgba) draw_line(screen_point_an_p2, screen_point_an_p3, rgba) draw_line(screen_point_an_p1, screen_point_an_p3, rgba) if ms.gltype == 11: # arc # draw line from center of arc second point c = Vector(a_p1) if ms.glarc_rad is True: if ms.glarc_extrad is False: draw_arrow(screen_point_ap1, screen_point_bp1, rgba, a_size, a_type, b_type) else: vne = Vector((b_p1[0] - a_p1[0], b_p1[1] - a_p1[1], b_p1[2] - a_p1[2])) vne.normalize() vie = vne * ms.glspace pe = (b_p1[0] + vie[0], b_p1[1] + vie[1], b_p1[2] + vie[2]) screen_point_pe = get_2d_point(region, rv3d, pe) draw_arrow(screen_point_ap1, screen_point_pe, rgba, a_size, a_type, b_type) # create arc around the centerpoint # rotation matrix around normal vector at center point mat_trans1 = Matrix.Translation(-c) # get step n_step = 36.0 if ms.glarc_full is False: step = arc_angle / n_step else: step = radians(360.0) / n_step mat_rot1 = Matrix.Rotation(step, 4, vn) mat_trans2 = Matrix.Translation(c) p1 = Vector(an_p1) # first point of arc # Normal vector vn = Vector((p1[0] - a_p1[0], p1[1] - a_p1[1], p1[2] - a_p1[2])) vn.normalize() vi = vn * ms.glspace p_01a = None p_01b = None p_02a = None p_02b = None # draw the arc for i in range(int(n_step)): p2 = mat_trans2 @ mat_rot1 @ mat_trans1 @ p1 p1_ = (p1[0] + vi[0], p1[1] + vi[1], p1[2] + vi[2]) # First Point if i == 0: p_01a = (p1_[0], p1_[1], p1_[2]) p_01b = (p1[0], p1[1], p1[2]) # Normal vector vn = Vector((p2[0] - a_p1[0], p2[1] - a_p1[1], p2[2] - a_p1[2])) vn.normalize() vi = vn * ms.glspace p2_ = (p2[0] + vi[0], p2[1] + vi[1], p2[2] + vi[2]) # convert to screen coordinates screen_point_p1 = get_2d_point(region, rv3d, p1_) screen_point_p2 = get_2d_point(region, rv3d, p2_) if i == 0: draw_arrow(screen_point_p1, screen_point_p2, rgba, ms.glarc_s, ms.glarc_a, "99") elif i == int(n_step) - 1: draw_arrow(screen_point_p1, screen_point_p2, rgba, ms.glarc_s, "99", ms.glarc_b) else: draw_line(screen_point_p1, screen_point_p2, rgba) p1 = p2.copy() # Last Point if i == int(n_step) - 1: p_02a = (p2_[0], p2_[1], p2_[2]) p_02b = (p2[0], p2[1], p2[2]) # Draw close lines if ms.glarc_full is False: screen_point_p1a = get_2d_point(region, rv3d, p_01a) screen_point_p1b = get_2d_point(region, rv3d, p_01b) screen_point_p2a = get_2d_point(region, rv3d, p_02a) screen_point_p2b = get_2d_point(region, rv3d, p_02b) draw_line(screen_point_p1a, screen_point_p1b, rgba) draw_line(screen_point_p2a, screen_point_p2b, rgba) if ms.gltype == 20: # Area obverts = get_mesh_vertices(myobj) tot = 0 if scene.measureit_scale is True: ms_scale = scene.measureit_scale_factor else: ms_scale = 1.0 for face in ms.measureit_faces: myvertices = [] for v in face.measureit_index: myvertices.append(v.glidx) area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d, rgba, ms_scale) tot += area # Draw Area number over first face if len(ms.measureit_faces) > 0: face = ms.measureit_faces[0] a = face.measureit_index[0].glidx b = face.measureit_index[2].glidx p1 = get_point(obverts[a].co, myobj) p2 = get_point(obverts[b].co, myobj) d1, dn = distance(p1, p2) midpoint3d = interpolate3d(p1, p2, fabs(d1 / 2)) # mult by world scale tot *= scale tx_dist = str(format_distance(fmt, units, tot, 2)) # ----------------------------------- # Draw text # ----------------------------------- if scene.measureit_gl_show_d is True and ms.gldist is True: msg = tx_dist + " " else: msg = " " if scene.measureit_gl_show_n is True and ms.glnames is True: msg += ms.gltxt if scene.measureit_gl_show_d is True or scene.measureit_gl_show_n is True: tmp_point = get_2d_point(region, rv3d, midpoint3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty # todo: swap ms.glcolorarea with ms.glcolor ? draw_text(myobj, txtpoint2d, msg, ms.glcolorarea, fsize, faln, fang) except IndexError: ms.glfree = True except: print("Unexpected error:" + str(exc_info())) pass return # ------------------------------------------ # Get polygon area and paint area # # ------------------------------------------ def get_area_and_paint(myvertices, myobj, obverts, region, rv3d, rgba, ms_scale): mymesh = myobj.data totarea = 0 if len(myvertices) > 3: # Tessellate the polygon if myobj.mode != 'EDIT': tris = mesh_utils.ngon_tessellate(mymesh, myvertices) else: bm = from_edit_mesh(myobj.data) myv = [] for v in bm.verts: myv.append(v.co) tris = mesh_utils.ngon_tessellate(myv, myvertices) for t in tris: v1, v2, v3 = t p1 = get_point(obverts[myvertices[v1]].co, myobj) p2 = get_point(obverts[myvertices[v2]].co, myobj) p3 = get_point(obverts[myvertices[v3]].co, myobj) screen_point_p1 = get_2d_point(region, rv3d, p1) screen_point_p2 = get_2d_point(region, rv3d, p2) screen_point_p3 = get_2d_point(region, rv3d, p3) draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3, rgba) # Area area = get_triangle_area(p1, p2, p3, ms_scale) totarea += area elif len(myvertices) == 3: v1, v2, v3 = myvertices p1 = get_point(obverts[v1].co, myobj) p2 = get_point(obverts[v2].co, myobj) p3 = get_point(obverts[v3].co, myobj) screen_point_p1 = get_2d_point(region, rv3d, p1) screen_point_p2 = get_2d_point(region, rv3d, p2) screen_point_p3 = get_2d_point(region, rv3d, p3) draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3, rgba) # Area area = get_triangle_area(p1, p2, p3, ms_scale) totarea += area else: return 0.0 # Apply world scale totarea *= bpy.context.scene.unit_settings.scale_length return totarea # ------------------------------------------ # Get area using Heron formula # # ------------------------------------------ def get_triangle_area(p1, p2, p3, scale=1.0): d1, dn = distance(p1, p2) d2, dn = distance(p2, p3) d3, dn = distance(p1, p3) d1 *= scale d2 *= scale d3 *= scale per = (d1 + d2 + d3) / 2.0 area = sqrt(per * (per - d1) * (per - d2) * (per - d3)) return area # ------------------------------------------ # Get point in 2d space # # ------------------------------------------ def get_2d_point(region, rv3d, point3d): if rv3d is not None and region is not None: return view3d_utils.location_3d_to_region_2d(region, rv3d, point3d) else: return get_render_location(point3d) # ------------------------------------------------------------- # Get sum of a group # # myobj: Current object # Tag: group # ------------------------------------------------------------- def get_group_sum(myobj, tag): # noinspection PyBroadException try: tx = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] g = tag[2:3] mp = myobj.MeasureGenerator[0] flag = False # ----------------- # Sum loop segments # ----------------- scale = bpy.context.scene.unit_settings.scale_length tot = 0.0 obverts = get_mesh_vertices(myobj) for idx in range(mp.measureit_num): ms = mp.measureit_segments[idx] if (ms.gltype == 1 or ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14) and ms.gltot != '99' \ and ms.glfree is False and g == tx[int(ms.gltot)]: # only segments if ms.glpointa <= len(obverts) and ms.glpointb <= len(obverts): p1 = get_point(obverts[ms.glpointa].co, myobj) if ms.gltype == 1: p2 = get_point(obverts[ms.glpointb].co, myobj) elif ms.gltype == 12: p2 = get_point((0.0, obverts[ms.glpointa].co[1], obverts[ms.glpointa].co[2]), myobj) elif ms.gltype == 13: p2 = get_point((obverts[ms.glpointa].co[0], 0.0, obverts[ms.glpointa].co[2]), myobj) else: p2 = get_point((obverts[ms.glpointa].co[0], obverts[ms.glpointa].co[1], 0.0), myobj) dist, distloc = distance(p1, p2, ms.glocx, ms.glocy, ms.glocz) if dist == distloc: usedist = dist else: usedist = distloc usedist *= scale tot += usedist flag = True if flag is True: # Return value pr = bpy.context.scene.measureit_gl_precision fmt = "%1." + str(pr) + "f" units = bpy.context.scene.measureit_units return format_distance(fmt, units, tot) else: return " " except: return " " # ------------------------------------------------------------- # Create OpenGL text # # ------------------------------------------------------------- def draw_text(myobj, pos2d, display_text, rgba, fsize, align='L', text_rot=0.0): if pos2d is None: return # dpi = bpy.context.preferences.system.dpi gap = 12 x_pos, y_pos = pos2d font_id = 0 ui_scale = bpy.context.preferences.system.ui_scale blf.size(font_id, round(fsize * ui_scale), 72) # blf.size(font_id, fsize, dpi) # height of one line mwidth, mheight = blf.dimensions(font_id, "Tp") # uses high/low letters # Calculate sum groups m = 0 while "<#" in display_text: m += 1 if m > 10: # limit loop break i = display_text.index("<#") tag = display_text[i:i + 4] display_text = display_text.replace(tag, get_group_sum(myobj, tag.upper())) # split lines mylines = display_text.split("|") idx = len(mylines) - 1 maxwidth = 0 maxheight = len(mylines) * mheight # ------------------- # Draw all lines # ------------------- for line in mylines: text_width, text_height = blf.dimensions(font_id, line) if align == 'C': newx = x_pos - text_width / 2 elif align == 'R': newx = x_pos - text_width - gap else: newx = x_pos blf.enable(font_id, ROTATION) blf.rotation(font_id, text_rot) # calculate new Y position new_y = y_pos + (mheight * idx) # Draw blf.position(font_id, newx, new_y, 0) blf.color(font_id, rgba[0], rgba[1], rgba[2], rgba[3]) blf.draw(font_id, " " + line) # sub line idx -= 1 # saves max width if maxwidth < text_width: maxwidth = text_width if align == 'L': blf.disable(font_id, ROTATION) return maxwidth, maxheight # ------------------------------------------------------------- # Draw an OpenGL line # # ------------------------------------------------------------- def draw_line(v1, v2, rgba): coords = [(v1[0], v1[1], 0), (v2[0], v2[1], 0)] batch = batch_for_shader(shader_line, 'LINES', {"pos": coords}) # noinspection PyBroadException try: if v1 is not None and v2 is not None: shader_line.bind() shader_line.uniform_float("color", rgba) shader_line.uniform_float("lineWidth", imm_line_width) shader_line.uniform_float("viewportSize", imm_viewport) batch.draw(shader_line) except: pass # ------------------------------------------------------------- # Draw an OpenGL triangle # # ------------------------------------------------------------- def draw_triangle(v1, v2, v3, rgba): coords = [(v1[0], v1[1]), (v2[0], v2[1]), (v3[0], v3[1])] batch = batch_for_shader(shader, 'TRIS', {"pos": coords}) # noinspection PyBroadException try: if v1 is not None and v2 is not None and v3 is not None: shader.bind() shader.uniform_float("color", rgba) batch.draw(shader) except: pass # ------------------------------------------------------------- # Draw an Arrow # # ------------------------------------------------------------- def draw_arrow(v1, v2, rgba, size=20, a_typ="1", b_typ="1"): if v1 is None or v2 is None: return rad45 = radians(45) rad315 = radians(315) rad90 = radians(90) rad270 = radians(270) v = interpolate3d((v1[0], v1[1], 0.0), (v2[0], v2[1], 0.0), size) v1i = (v[0] - v1[0], v[1] - v1[1]) v = interpolate3d((v2[0], v2[1], 0.0), (v1[0], v1[1], 0.0), size) v2i = (v[0] - v2[0], v[1] - v2[1]) # Point A if a_typ == "3": rad_a = rad90 rad_b = rad270 else: rad_a = rad45 rad_b = rad315 v1a = (int(v1i[0] * cos(rad_a) - v1i[1] * sin(rad_a) + v1[0]), int(v1i[1] * cos(rad_a) + v1i[0] * sin(rad_a)) + v1[1]) v1b = (int(v1i[0] * cos(rad_b) - v1i[1] * sin(rad_b) + v1[0]), int(v1i[1] * cos(rad_b) + v1i[0] * sin(rad_b) + v1[1])) # Point B if b_typ == "3": rad_a = rad90 rad_b = rad270 else: rad_a = rad45 rad_b = rad315 v2a = (int(v2i[0] * cos(rad_a) - v2i[1] * sin(rad_a) + v2[0]), int(v2i[1] * cos(rad_a) + v2i[0] * sin(rad_a)) + v2[1]) v2b = (int(v2i[0] * cos(rad_b) - v2i[1] * sin(rad_b) + v2[0]), int(v2i[1] * cos(rad_b) + v2i[0] * sin(rad_b) + v2[1])) # Triangle o Lines if a_typ == "1" or a_typ == "3": draw_line(v1, v1a, rgba) draw_line(v1, v1b, rgba) if b_typ == "1" or b_typ == "3": draw_line(v2, v2a, rgba) draw_line(v2, v2b, rgba) if a_typ == "2": draw_triangle(v1, v1a, v1b, rgba) if b_typ == "2": draw_triangle(v2, v2a, v2b, rgba) draw_line(v1, v2, rgba) # ------------------------------------------------------------- # Draw an OpenGL Rectangle # # v1, v2 are corners (bottom left / top right) # ------------------------------------------------------------- def draw_rectangle(v1, v2, rgba): # noinspection PyBroadException try: if v1 is not None and v2 is not None: v1b = (v2[0], v1[1]) v2b = (v1[0], v2[1]) draw_line(v1, v1b, rgba) draw_line(v1b, v2, rgba) draw_line(v2, v2b, rgba) draw_line(v2b, v1, rgba) except: pass # ------------------------------------------------------------- # format a point as (x, y, z) for display # # ------------------------------------------------------------- def format_point(mypoint, pr): pf = "%1." + str(pr) + "f" fmt = " (" fmt += pf % mypoint[0] fmt += ", " fmt += pf % mypoint[1] fmt += ", " fmt += pf % mypoint[2] fmt += ")" return fmt # ------------------------------------------------------------- # Draw object num for debug # # ------------------------------------------------------------- # noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyUnusedLocal def draw_object(context, myobj, region, rv3d): scene = bpy.context.scene rgba = scene.measureit_debug_obj_color fsize = scene.measureit_debug_font precision = scene.measureit_debug_precision # -------------------- # object Loop # -------------------- objs = bpy.context.scene.objects obidxs = list(range(len(bpy.context.scene.objects))) for o in obidxs: # Display only selected if scene.measureit_debug_select is True: if objs[o].select_get() is False: continue a_p1 = Vector(get_location(objs[o])) # Text txt = '' if scene.measureit_debug_objects is True: txt += str(o) if scene.measureit_debug_object_loc is True: txt += format_point(a_p1, precision) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) draw_text(myobj, txtpoint2d, txt, rgba, fsize) return # ------------------------------------------------------------- # Draw vertex num for debug # # ------------------------------------------------------------- # noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyUnusedLocal def draw_vertices(context, myobj, region, rv3d): # Only meshes if myobj.type != "MESH": return scene = bpy.context.scene rgba = scene.measureit_debug_vert_color fsize = scene.measureit_debug_font precision = scene.measureit_debug_precision # -------------------- # vertex Loop # -------------------- if scene.measureit_debug_vert_loc_toggle == '1': co_mult = lambda c: c else: # if global, convert local c to global co_mult = lambda c: myobj.matrix_world @ c if myobj.mode == 'EDIT': bm = from_edit_mesh(myobj.data) obverts = bm.verts else: obverts = myobj.data.vertices for v in obverts: # Display only selected if scene.measureit_debug_select is True: if v.select is False: continue # noinspection PyBroadException # try: a_p1 = get_point(v.co, myobj) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) # Text txt = '' if scene.measureit_debug_vertices is True: txt += str(v.index) if scene.measureit_debug_vert_loc is True: txt += format_point(co_mult(v.co), precision) draw_text(myobj, txtpoint2d, txt, rgba, fsize) # except: # print("Unexpected error:" + str(exc_info())) # pass return # ------------------------------------------------------------- # Draw edge num for debug # # ------------------------------------------------------------- # noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyUnusedLocal def draw_edges(context, myobj, region, rv3d): # Only meshes if myobj.type != "MESH": return scene = bpy.context.scene rgba = scene.measureit_debug_edge_color fsize = scene.measureit_debug_font precision = scene.measureit_debug_precision # -------------------- # edge Loop # # uses lambda for edge midpoint finder (midf) because edit mode # edge vert coordinate is not stored in same places as in obj mode # -------------------- if myobj.mode == 'EDIT': bm = from_edit_mesh(myobj.data) obedges = bm.edges obverts = None # dummy value to avoid duplicating for loop midf = lambda e, v: e.verts[0].co.lerp(e.verts[1].co, 0.5) else: obedges = myobj.data.edges obverts = myobj.data.vertices midf = lambda e, v: v[e.vertices[0]].co.lerp(v[e.vertices[1]].co, 0.5) for e in obedges: # Display only selected if scene.measureit_debug_select is True: if e.select is False: continue a_mp = midf(e, obverts) a_p1 = get_point(a_mp, myobj) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) draw_text(myobj, txtpoint2d, str(e.index), rgba, fsize) return # ------------------------------------------------------------- # Draw face num for debug # # ------------------------------------------------------------- # noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyUnusedLocal def draw_faces(context, myobj, region, rv3d): # Only meshes if myobj.type != "MESH": return scene = bpy.context.scene rgba = scene.measureit_debug_face_color rgba2 = scene.measureit_debug_norm_color fsize = scene.measureit_debug_font ln = scene.measureit_debug_normal_size th = scene.measureit_debug_width precision = scene.measureit_debug_precision # -------------------- # face Loop # -------------------- if myobj.mode == 'EDIT': bm = from_edit_mesh(myobj.data) obverts = bm.verts myfaces = bm.faces else: obverts = myobj.data.vertices myfaces = myobj.data.polygons for f in myfaces: normal = f.normal # Display only selected if scene.measureit_debug_select is True: if f.select is False: continue # noinspection PyBroadException try: if myobj.mode == 'EDIT': a_p1 = get_point(f.calc_center_median(), myobj) else: a_p1 = get_point(f.center, myobj) a_p2 = (a_p1[0] + normal[0] * ln, a_p1[1] + normal[1] * ln, a_p1[2] + normal[2] * ln) # line setup gpu.state.blend_set('ALPHA') imm_set_line_width(th) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) point2 = get_2d_point(region, rv3d, a_p2) # Text if scene.measureit_debug_faces is True: draw_text(myobj, txtpoint2d, str(f.index), rgba, fsize) # Draw Normal if scene.measureit_debug_normals is True: gpu.state.blend_set('ALPHA') draw_arrow(txtpoint2d, point2, rgba, 10, "99", "1") if len(obverts) > 2 and scene.measureit_debug_normal_details is True: if myobj.mode == 'EDIT': i1 = f.verts[0].index i2 = f.verts[1].index i3 = f.verts[2].index else: i1 = f.vertices[0] i2 = f.vertices[1] i3 = f.vertices[2] a_p1 = get_point(obverts[i1].co, myobj) a_p2 = get_point(obverts[i2].co, myobj) a_p3 = get_point(obverts[i3].co, myobj) # converting to screen coordinates a2d = get_2d_point(region, rv3d, a_p1) b2d = get_2d_point(region, rv3d, a_p2) c2d = get_2d_point(region, rv3d, a_p3) # draw vectors draw_arrow(a2d, b2d, rgba, 10, "99", "1") draw_arrow(b2d, c2d, rgba, 10, "99", "1") # Normal vector data txt = format_point(normal, precision) draw_text(myobj, point2, txt, rgba2, fsize) except: print("Unexpected error:" + str(exc_info())) pass return # -------------------------------------------------------------------- # Distance between 2 points in 3D space # v1: first point # v2: second point # locx/y/z: Use this axis # return: distance # -------------------------------------------------------------------- def distance(v1, v2, locx=True, locy=True, locz=True): x = sqrt((v2[0] - v1[0]) ** 2 + (v2[1] - v1[1]) ** 2 + (v2[2] - v1[2]) ** 2) # If axis is not used, make equal both (no distance) v1b = [v1[0], v1[1], v1[2]] v2b = [v2[0], v2[1], v2[2]] if locx is False: v2b[0] = v1b[0] if locy is False: v2b[1] = v1b[1] if locz is False: v2b[2] = v1b[2] xloc = sqrt((v2b[0] - v1b[0]) ** 2 + (v2b[1] - v1b[1]) ** 2 + (v2b[2] - v1b[2]) ** 2) return x, xloc # -------------------------------------------------------------------- # Interpolate 2 points in 3D space # v1: first point # v2: second point # d1: distance # return: interpolate point # -------------------------------------------------------------------- def interpolate3d(v1, v2, d1): # calculate vector v = (v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]) # calculate distance between points d0, dloc = distance(v1, v2) # calculate interpolate factor (distance from origin / distance total) # if d1 > d0, the point is projected in 3D space if d0 > 0: x = d1 / d0 else: x = d1 final = (v1[0] + (v[0] * x), v1[1] + (v[1] * x), v1[2] + (v[2] * x)) return final # -------------------------------------------------------------------- # Get point rotated and relative to parent # v1: point # mainobject # -------------------------------------------------------------------- def get_point(v1, mainobject): # Using World Matrix vt = Vector((v1[0], v1[1], v1[2], 1)) m4 = mainobject.matrix_world vt2 = m4 @ vt v2 = [vt2[0], vt2[1], vt2[2]] return v2 # -------------------------------------------------------------------- # Get location in world space # v1: point # mainobject # -------------------------------------------------------------------- def get_location(mainobject): # Using World Matrix m4 = mainobject.matrix_world return [m4[0][3], m4[1][3], m4[2][3]] # -------------------------------------------------------------------- # Get vertex data # mainobject # -------------------------------------------------------------------- def get_mesh_vertices(myobj): try: if myobj.mode == 'EDIT': bm = from_edit_mesh(myobj.data) obverts = bm.verts else: obverts = myobj.data.vertices return obverts except AttributeError: return None # -------------------------------------------------------------------- # Get position for scale text # # -------------------------------------------------------------------- def get_scale_txt_location(context): scene = context.scene pos_x = int(context.region.width * scene.measureit_scale_pos_x / 100) pos_y = int(context.region.height * scene.measureit_scale_pos_y / 100) return pos_x, pos_y # -------------------------------------------------------------------- # Get position in final render image # (Z < 0 out of camera) # return 2d position # -------------------------------------------------------------------- def get_render_location(mypoint): v1 = Vector(mypoint) scene = bpy.context.scene co_2d = object_utils.world_to_camera_view(scene, scene.camera, v1) # Get pixel coords render_scale = scene.render.resolution_percentage / 100 render_size = (int(scene.render.resolution_x * render_scale), int(scene.render.resolution_y * render_scale)) return [round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])] # --------------------------------------------------------- # Get center of circle base on 3 points # # Point a: (x,y,z) arc start # Point b: (x,y,z) center # Point c: (x,y,z) midle point in the arc # Point d: (x,y,z) arc end # Return: # ang: angle (radians) # len: len of arc # # --------------------------------------------------------- def get_arc_data(pointa, pointb, pointc, pointd): v1 = Vector((pointa[0] - pointb[0], pointa[1] - pointb[1], pointa[2] - pointb[2])) v2 = Vector((pointc[0] - pointb[0], pointc[1] - pointb[1], pointc[2] - pointb[2])) v3 = Vector((pointd[0] - pointb[0], pointd[1] - pointb[1], pointd[2] - pointb[2])) angle = v1.angle(v2) + v2.angle(v3) rclength = pi * 2 * v2.length * (angle / (pi * 2)) return angle, rclength # ------------------------------------------------------------- # Format a number to the right unit # # ------------------------------------------------------------- def format_distance(fmt, units, value, factor=1): s_code = "\u00b2" # Superscript two hide_units = bpy.context.scene.measureit_hide_units # ------------------------ # Units automatic # ------------------------ if units == "1": # Units if bpy.context.scene.unit_settings.system == "IMPERIAL": feet = value * (3.2808399 ** factor) if round(feet, 2) >= 1.0: if hide_units is False: fmt += " ft" if factor == 2: fmt += s_code tx_dist = fmt % feet else: inches = value * (39.3700787 ** factor) if hide_units is False: fmt += " in" if factor == 2: fmt += s_code tx_dist = fmt % inches elif bpy.context.scene.unit_settings.system == "METRIC": if round(value, 2) >= 1.0: if hide_units is False: fmt += " m" if factor == 2: fmt += s_code tx_dist = fmt % value else: if round(value, 2) >= 0.01: if hide_units is False: fmt += " cm" if factor == 2: fmt += s_code d_cm = value * (100 ** factor) tx_dist = fmt % d_cm else: if hide_units is False: fmt += " mm" if factor == 2: fmt += s_code d_mm = value * (1000 ** factor) tx_dist = fmt % d_mm else: tx_dist = fmt % value # ------------------------ # Units meters # ------------------------ elif units == "2": if hide_units is False: fmt += " m" if factor == 2: fmt += s_code tx_dist = fmt % value # ------------------------ # Units centimeters # ------------------------ elif units == "3": if hide_units is False: fmt += " cm" if factor == 2: fmt += s_code d_cm = value * (100 ** factor) tx_dist = fmt % d_cm # ------------------------ # Units millimeters # ------------------------ elif units == "4": if hide_units is False: fmt += " mm" if factor == 2: fmt += s_code d_mm = value * (1000 ** factor) tx_dist = fmt % d_mm # ------------------------ # Units feet # ------------------------ elif units == "5": if hide_units is False: fmt += " ft" if factor == 2: fmt += s_code feet = value * (3.2808399 ** factor) tx_dist = fmt % feet # ------------------------ # Units inches # ------------------------ elif units == "6": if hide_units is False: fmt += " in" if factor == 2: fmt += s_code inches = value * (39.3700787 ** factor) tx_dist = fmt % inches # ------------------------ # Default # ------------------------ else: tx_dist = fmt % value return tx_dist # ------------------------------------------------------------- # Get radian float based on angle choice # # ------------------------------------------------------------- def get_angle_in_rad(fangle): if fangle == 0: return 0.0 else: return radians(fangle)