diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2016-08-03 03:47:46 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2016-08-03 03:47:46 +0300 |
commit | cf4ac00ea0aabfbc1c84b085c53991862003f5ae (patch) | |
tree | 6f145d6cb875e314ee2f2672d213b36087641927 | |
parent | 5cd9040c995effc4c6bf233e820f741cbaf083ca (diff) |
add mesureit to release "warning temporary status" pending T48704
-rw-r--r-- | measureit/__init__.py | 403 | ||||
-rw-r--r-- | measureit/measureit_geometry.py | 1350 | ||||
-rw-r--r-- | measureit/measureit_main.py | 2061 | ||||
-rw-r--r-- | measureit/measureit_render.py | 343 |
4 files changed, 4157 insertions, 0 deletions
diff --git a/measureit/__init__.py b/measureit/__init__.py new file mode 100644 index 00000000..3401a6a6 --- /dev/null +++ b/measureit/__init__.py @@ -0,0 +1,403 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Antonio Vazquez (antonioya) +# ---------------------------------------------------------- + +# ---------------------------------------------- +# Define Addon info +# ---------------------------------------------- +bl_info = { + "name": "MeasureIt", + "author": "Antonio Vazquez (antonioya)", + "location": "View3D > Tools Panel /Properties panel", + "version": (1, 6, 6), + "blender": (2, 7, 4), + "description": "Tools for measuring objects.", + 'warning': 'Temporary: pending review fixes T48704', + "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "category": "3D View"} + +import sys +import os + +# ---------------------------------------------- +# Add to Phyton path (once only) +# ---------------------------------------------- +path = sys.path +flag = False +for item in path: + if "measureit" in item: + flag = True +if flag is False: + sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'measureit')) + print("measureit: added to phytonpath") + +# ---------------------------------------------- +# Import modules +# ---------------------------------------------- +if "bpy" in locals(): + import imp + + imp.reload(measureit_main) + print("measureit: Reloaded multifiles") +else: + import measureit_main + + print("measureit: Imported multifiles") + +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.props import * + + +# -------------------------------------------------------------- +# Register all operators and panels +# -------------------------------------------------------------- +def register(): + bpy.utils.register_class(measureit_main.RunHintDisplayButton) + bpy.utils.register_class(measureit_main.AddSegmentButton) + bpy.utils.register_class(measureit_main.AddAreaButton) + bpy.utils.register_class(measureit_main.AddSegmentOrtoButton) + bpy.utils.register_class(measureit_main.AddAngleButton) + bpy.utils.register_class(measureit_main.AddArcButton) + bpy.utils.register_class(measureit_main.AddLabelButton) + bpy.utils.register_class(measureit_main.AddNoteButton) + bpy.utils.register_class(measureit_main.AddLinkButton) + bpy.utils.register_class(measureit_main.AddOriginButton) + bpy.utils.register_class(measureit_main.DeleteSegmentButton) + bpy.utils.register_class(measureit_main.DeleteAllSegmentButton) + bpy.utils.register_class(measureit_main.DeleteAllSumButton) + bpy.utils.register_class(measureit_main.MeasureitEditPanel) + bpy.utils.register_class(measureit_main.MeasureitMainPanel) + bpy.utils.register_class(measureit_main.MeasureitConfPanel) + bpy.utils.register_class(measureit_main.MeasureitRenderPanel) + bpy.utils.register_class(measureit_main.RenderSegmentButton) + + # Define properties + bpy.types.Scene.measureit_default_color = bpy.props.FloatVectorProperty( + name="Default color", + description="Default Color", + default=(0.173, 0.545, 1.0, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_font_size = bpy.props.IntProperty(name="Text Size", + description="Default text size", + default=14, min=10, max=150) + bpy.types.Scene.measureit_hint_space = bpy.props.FloatProperty(name='Separation', min=0, max=100, default=0.1, + precision=3, + description="Default distance to display measure") + bpy.types.Scene.measureit_gl_ghost = bpy.props.BoolProperty(name="All", + description="Display measures for all objects," + " not only selected", + default=True) + bpy.types.Scene.measureit_gl_txt = bpy.props.StringProperty(name="Text", maxlen=256, + description="Short description (use | for line break)") + + bpy.types.Scene.measureit_gl_precision = bpy.props.IntProperty(name='Precision', min=0, max=5, default=2, + description="Number of decimal precision") + bpy.types.Scene.measureit_gl_show_d = bpy.props.BoolProperty(name="ShowDist", + description="Display distances", + default=True) + bpy.types.Scene.measureit_gl_show_n = bpy.props.BoolProperty(name="ShowName", + description="Display texts", + default=False) + bpy.types.Scene.measureit_scale = bpy.props.BoolProperty(name="Scale", + description="Use scale factor", + default=False) + bpy.types.Scene.measureit_scale_factor = bpy.props.FloatProperty(name='Factor', min=0.001, max=9999999, + default=1.0, + precision=3, + description="Scale factor 1:x") + bpy.types.Scene.measureit_scale_color = bpy.props.FloatVectorProperty(name="Scale color", + description="Scale Color", + default=(1, 1, 0, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_scale_font = bpy.props.IntProperty(name="Font", + description="Text size", + default=14, min=10, max=150) + bpy.types.Scene.measureit_scale_pos_x = bpy.props.IntProperty(name="Position X", + description="Margin on the X axis", + default=5, + min=0, + max=100) + bpy.types.Scene.measureit_scale_pos_y = bpy.props.IntProperty(name="Position Y", + description="Margin on the Y axis", + default=5, + min=0, + max=100) + bpy.types.Scene.measureit_gl_scaletxt = bpy.props.StringProperty(name="ScaleText", maxlen=48, + description="Scale title", + default="Scale:") + bpy.types.Scene.measureit_scale_precision = bpy.props.IntProperty(name='Precision', min=0, max=5, default=0, + description="Number of decimal precision") + + bpy.types.Scene.measureit_ovr = bpy.props.BoolProperty(name="Override", + description="Override colors and fonts", + default=False) + bpy.types.Scene.measureit_ovr_font = bpy.props.IntProperty(name="Font", + description="Override text size", + default=14, min=10, max=150) + bpy.types.Scene.measureit_ovr_color = bpy.props.FloatVectorProperty(name="Override color", + description="Override Color", + default=(1, 0, 0, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_ovr_width = bpy.props.IntProperty(name='Override width', min=1, max=10, default=1, + description='override line width') + + bpy.types.Scene.measureit_units = bpy.props.EnumProperty(items=(('1', "Automatic", "Use scene units"), + ('2', "Meters", ""), + ('3', "Centimeters", ""), + ('4', "Milimiters", ""), + ('5', "Feet", ""), + ('6', "Inches", "")), + name="Units", + default="2", + description="Units") + bpy.types.Scene.measureit_render = bpy.props.BoolProperty(name="Render", + description="Save an image with measures over" + " render image", + default=False) + bpy.types.Scene.measureit_render_type = bpy.props.EnumProperty(items=(('1', "*Current", "Use current render"), + ('2', "OpenGL", ""), + ('3', "Animation OpenGL", ""), + ('4', "Image", ""), + ('5', "Animation", "")), + name="Render type", + description="Type of render image") + bpy.types.Scene.measureit_sum = bpy.props.EnumProperty(items=(('99', "-", "Select a group for sum"), + ('0', "A", ""), + ('1', "B", ""), + ('2', "C", ""), + ('3', "D", ""), + ('4', "E", ""), + ('5', "F", ""), + ('6', "G", ""), + ('7', "H", ""), + ('8', "I", ""), + ('9', "J", ""), + ('10', "K", ""), + ('11', "L", ""), + ('12', "M", ""), + ('13', "N", ""), + ('14', "O", ""), + ('15', "P", ""), + ('16', "Q", ""), + ('17', "R", ""), + ('18', "S", ""), + ('19', "T", ""), + ('20', "U", ""), + ('21', "V", ""), + ('22', "W", ""), + ('23', "X", ""), + ('24', "Y", ""), + ('25', "Z", "")), + name="Sum in Group", + description="Add segment length in selected group") + + bpy.types.Scene.measureit_rf = bpy.props.BoolProperty(name="render_frame", + description="Add a frame in render output", + default=False) + bpy.types.Scene.measureit_rf_color = bpy.props.FloatVectorProperty(name="Fcolor", + description="Frame Color", + default=(0.9, 0.9, 0.9, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_rf_border = bpy.props.IntProperty(name='fborder ', min=1, max=1000, default=10, + description='Frame space from border') + bpy.types.Scene.measureit_rf_line = bpy.props.IntProperty(name='fline', min=1, max=10, default=1, + description='Line width for border') + + bpy.types.Scene.measureit_glarrow_a = bpy.props.EnumProperty(items=(('99', "--", "No arrow"), + ('1', "Line", + "The point of the arrow are lines"), + ('2', "Triangle", + "The point of the arrow is triangle"), + ('3', "TShape", + "The point of the arrow is a T")), + name="A end", + description="Add arrows to point A") + bpy.types.Scene.measureit_glarrow_b = bpy.props.EnumProperty(items=(('99', "--", "No arrow"), + ('1', "Line", + "The point of the arrow are lines"), + ('2', "Triangle", + "The point of the arrow is triangle"), + ('3', "TShape", + "The point of the arrow is a T")), + name="B end", + description="Add arrows to point B") + bpy.types.Scene.measureit_glarrow_s = bpy.props.IntProperty(name="Size", + description="Arrow size", + default=15, min=6, max=500) + + bpy.types.Scene.measureit_debug = bpy.props.BoolProperty(name="Debug", + description="Display information for debuging" + " (expand/collapse for enabling or disabling)" + " this information is only renderered for " + "selected objects", + default=False) + bpy.types.Scene.measureit_debug_select = bpy.props.BoolProperty(name="Selected", + description="Display information " + "for selected vertices/faces", + default=False) + bpy.types.Scene.measureit_debug_vertices = bpy.props.BoolProperty(name="Vertices", + description="Display vertex number", + default=True) + bpy.types.Scene.measureit_debug_location = bpy.props.BoolProperty(name="Location", + description="Display vertex location", + default=False) + bpy.types.Scene.measureit_debug_faces = bpy.props.BoolProperty(name="Faces", + description="Display face number", + default=False) + bpy.types.Scene.measureit_debug_normals = bpy.props.BoolProperty(name="Normals", + description="Display face normal " + "vector and creation order", + default=False) + bpy.types.Scene.measureit_debug_normal_details = bpy.props.BoolProperty(name="Details", + description="Display face normal details", + default=True) + bpy.types.Scene.measureit_debug_font = bpy.props.IntProperty(name="Font", + description="Debug text size", + default=14, min=10, max=150) + bpy.types.Scene.measureit_debug_color = bpy.props.FloatVectorProperty(name="Debug color", + description="Debug Color", + default=(1, 0, 0, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_debug_color2 = bpy.props.FloatVectorProperty(name="Debug face color", + description="Debug face Color", + default=(0, 1, 0, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_debug_color3 = bpy.props.FloatVectorProperty(name="Debug vector color", + description="Debug vector Color", + default=(1.0, 1.0, 0.1, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + bpy.types.Scene.measureit_debug_normal_size = bpy.props.FloatProperty(name='Len', min=0.001, max=9, + default=0.5, + precision=2, + description="Normal arrow size") + bpy.types.Scene.measureit_debug_width = bpy.props.IntProperty(name='Debug width', min=1, max=10, default=2, + description='Vector line thickness') + bpy.types.Scene.measureit_debug_precision = bpy.props.IntProperty(name='Precision', min=0, max=5, default=1, + description="Number of decimal precision") + + # OpenGL flag + wm = bpy.types.WindowManager + # register internal property + wm.measureit_run_opengl = bpy.props.BoolProperty(default=False) + + +def unregister(): + bpy.utils.unregister_class(measureit_main.RunHintDisplayButton) + bpy.utils.unregister_class(measureit_main.AddSegmentButton) + bpy.utils.unregister_class(measureit_main.AddAreaButton) + bpy.utils.unregister_class(measureit_main.AddSegmentOrtoButton) + bpy.utils.unregister_class(measureit_main.AddAngleButton) + bpy.utils.unregister_class(measureit_main.AddArcButton) + bpy.utils.unregister_class(measureit_main.AddLabelButton) + bpy.utils.unregister_class(measureit_main.AddNoteButton) + bpy.utils.unregister_class(measureit_main.AddLinkButton) + bpy.utils.unregister_class(measureit_main.AddOriginButton) + bpy.utils.unregister_class(measureit_main.DeleteSegmentButton) + bpy.utils.unregister_class(measureit_main.DeleteAllSegmentButton) + bpy.utils.unregister_class(measureit_main.DeleteAllSumButton) + bpy.utils.unregister_class(measureit_main.MeasureitEditPanel) + bpy.utils.unregister_class(measureit_main.MeasureitMainPanel) + bpy.utils.unregister_class(measureit_main.MeasureitConfPanel) + bpy.utils.unregister_class(measureit_main.MeasureitRenderPanel) + bpy.utils.unregister_class(measureit_main.RenderSegmentButton) + + # Remove properties + del bpy.types.Scene.measureit_default_color + del bpy.types.Scene.measureit_font_size + del bpy.types.Scene.measureit_hint_space + del bpy.types.Scene.measureit_gl_ghost + del bpy.types.Scene.measureit_gl_txt + del bpy.types.Scene.measureit_gl_precision + del bpy.types.Scene.measureit_gl_show_d + del bpy.types.Scene.measureit_gl_show_n + del bpy.types.Scene.measureit_scale + del bpy.types.Scene.measureit_scale_factor + del bpy.types.Scene.measureit_scale_color + del bpy.types.Scene.measureit_scale_font + del bpy.types.Scene.measureit_scale_pos_x + del bpy.types.Scene.measureit_scale_pos_y + del bpy.types.Scene.measureit_gl_scaletxt + del bpy.types.Scene.measureit_scale_precision + del bpy.types.Scene.measureit_ovr + del bpy.types.Scene.measureit_ovr_font + del bpy.types.Scene.measureit_ovr_color + del bpy.types.Scene.measureit_ovr_width + del bpy.types.Scene.measureit_units + del bpy.types.Scene.measureit_render + del bpy.types.Scene.measureit_render_type + del bpy.types.Scene.measureit_sum + del bpy.types.Scene.measureit_rf + del bpy.types.Scene.measureit_rf_color + del bpy.types.Scene.measureit_rf_border + del bpy.types.Scene.measureit_rf_line + del bpy.types.Scene.measureit_glarrow_a + del bpy.types.Scene.measureit_glarrow_b + del bpy.types.Scene.measureit_glarrow_s + del bpy.types.Scene.measureit_debug + del bpy.types.Scene.measureit_debug_select + del bpy.types.Scene.measureit_debug_vertices + del bpy.types.Scene.measureit_debug_faces + del bpy.types.Scene.measureit_debug_normals + del bpy.types.Scene.measureit_debug_normal_details + del bpy.types.Scene.measureit_debug_font + del bpy.types.Scene.measureit_debug_color + del bpy.types.Scene.measureit_debug_color2 + del bpy.types.Scene.measureit_debug_color3 + del bpy.types.Scene.measureit_debug_normal_size + del bpy.types.Scene.measureit_debug_width + del bpy.types.Scene.measureit_debug_precision + del bpy.types.Scene.measureit_debug_location + + # remove OpenGL data + measureit_main.RunHintDisplayButton.handle_remove(measureit_main.RunHintDisplayButton, bpy.context) + wm = bpy.context.window_manager + p = 'measureit_run_opengl' + if p in wm: + del wm[p] + + +if __name__ == '__main__': + register() diff --git a/measureit/measureit_geometry.py b/measureit/measureit_geometry.py new file mode 100644 index 00000000..c8c6170d --- /dev/null +++ b/measureit/measureit_geometry.py @@ -0,0 +1,1350 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# support routines for OpenGL +# Author: Antonio Vazquez (antonioya) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +import bgl +# noinspection PyUnresolvedReferences +import blf +import math +# noinspection PyUnresolvedReferences +import mathutils +# noinspection PyUnresolvedReferences +import bmesh +# noinspection PyUnresolvedReferences +from bpy_extras import view3d_utils, mesh_utils +# noinspection PyUnresolvedReferences +import bpy_extras.object_utils as object_utils +import sys + + +# ------------------------------------------------------------- +# Draw segments +# +# rgb: Color +# fsize: Font size +# ------------------------------------------------------------- +# 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 + ovrline = scene.measureit_ovr_width + units = scene.measureit_units + # -------------------- + # Scene Scale + # -------------------- + if scene.measureit_scale is True: + prs = scene.measureit_scale_precision + fmts = "%1." + str(prs) + "f" + pos_x, pos_y = 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_x, pos_y, + tx_scale, scene.measureit_scale_color, scene.measureit_scale_font) + # -------------------- + # Loop + # -------------------- + for idx in range(0, op.measureit_num): + ms = op.measureit_segments[idx] + if ovr is False: + fsize = ms.glfont_size + else: + fsize = ovrfsize + # ------------------------------ + # 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: + rgb = ms.glcolor + else: + rgb = 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 = mathutils.Vector((an_p1[0] - an_p2[0], an_p1[1] - an_p2[1], an_p1[2] - an_p2[2])) + ang_2 = mathutils.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 = mathutils.Vector((an_p1[0] - an_p2[0], an_p1[1] - an_p2[1], an_p1[2] - an_p2[2])) + an_p13 = mathutils.Vector((an_p1[0] - an_p3[0], an_p1[1] - an_p3[1], an_p1[2] - an_p3[2])) + an_p21 = mathutils.Vector((an_p2[0] - an_p1[0], an_p2[1] - an_p1[1], an_p2[2] - an_p1[2])) + an_p23 = mathutils.Vector((an_p2[0] - an_p3[0], an_p2[1] - an_p3[1], an_p2[2] - an_p3[2])) + an_p31 = mathutils.Vector((an_p3[0] - an_p1[0], an_p3[1] - an_p1[1], an_p3[2] - an_p1[2])) + an_p32 = mathutils.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, math.fabs(dist / 2)) + vn = mathutils.Vector((midpoint3d[0] - loc[0], + midpoint3d[1] - loc[1], + midpoint3d[2] - loc[2])) + else: + vn = mathutils.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: + bgl.glLineWidth(ms.glwidth) + else: + bgl.glLineWidth(ovrline) + + bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) + + # ------------------------------------ + # 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, math.fabs(dist / 2)) + gap3d = (midpoint3d[0], midpoint3d[1], midpoint3d[2] + s / 2) + txtpoint2d = get_2d_point(region, rv3d, gap3d) + # 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[0] + ms.glfontx, txtpoint2d[1] + ms.glfonty, + msg, rgb, fsize) + + # ------------------------------ + # 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[0], txtpoint2d[1], txt, rgb, fsize - 1) + + except: + pass + # ------------------------------------ + # Text (label) and Angles + # ------------------------------------ + # noinspection PyBroadException + if ms.gltype == 2 or ms.gltype == 9 or ms.gltype == 11: + right = False + tx_dist = "" + # noinspection PyBroadException + try: + if ms.gltype == 2: + tx_dist = ms.gltxt + right = False + if ms.gltype == 9: # Angles + ang = ang_1.angle(ang_2) + right = True + if bpy.context.scene.unit_settings.system_rotation == "DEGREES": + ang = math.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 + right = True + # print length or arc and angle + if ms.glarc_len is True: + tx_dist = ms.glarc_txlen + format_distance(fmt, units, arc_length) + else: + tx_dist = " " + + if bpy.context.scene.unit_settings.system_rotation == "DEGREES": + arc_d = math.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 = mathutils.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]) + txtpoint2d = get_2d_point(region, rv3d, gap3d) + draw_text(myobj, txtpoint2d[0] + ms.glfontx, txtpoint2d[1] + ms.glfonty, msg, rgb, + fsize, right) + # 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) + 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]) + + txtpoint2d = get_2d_point(region, rv3d, gap3d) + draw_text(myobj, txtpoint2d[0] + ms.glfontx, txtpoint2d[1] + ms.glfonty, tx_dist, rgb, + fsize, right) + except: + pass + # ------------------------------------ + # Annotation + # ------------------------------------ + # noinspection PyBroadException + if ms.gltype == 10: + # noinspection PyBroadException + try: + tx_dist = ms.gltxt + gap3d = (vn1[0], vn1[1], vn1[2]) + txtpoint2d = get_2d_point(region, rv3d, gap3d) + draw_text(myobj, txtpoint2d[0] + ms.glfontx, txtpoint2d[1] + ms.glfonty, + tx_dist, rgb, fsize) + except: + pass + # ------------------------------------ + # Draw lines + # ------------------------------------ + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) + + if ms.gltype == 1: # Segment + draw_line(screen_point_ap1, screen_point_v11) + draw_line(screen_point_bp1, screen_point_v22) + draw_arrow(screen_point_v1, screen_point_v2, 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) + draw_line(screen_point_bp1, screen_point_v22) + draw_arrow(screen_point_v1, screen_point_v2, a_size, a_type, b_type) + + if ms.gltype == 2: # Label + draw_line(screen_point_v11a, screen_point_v11b) + draw_arrow(screen_point_ap1, screen_point_v11, 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, a_size, a_type, b_type) + + if ms.gltype == 9: # Angle + dist, distloc = distance(an_p1, an_p2) + mp1 = interpolate3d(an_p1, an_p2, math.fabs(dist / 1.1)) + + dist, distloc = distance(an_p3, an_p2) + mp2 = interpolate3d(an_p3, an_p2, math.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) + draw_line(screen_point_an_p2, screen_point_an_p3) + draw_line(screen_point_an_p1, screen_point_an_p3) + + if ms.gltype == 11: # arc + # draw line from center of arc second point + c = mathutils.Vector(a_p1) + if ms.glarc_rad is True: + if ms.glarc_extrad is False: + draw_arrow(screen_point_ap1, screen_point_bp1, a_size, a_type, b_type) + else: + vne = mathutils.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, a_size, a_type, b_type) + + # create arc around the centerpoint + # rotation matrix around normal vector at center point + mat_trans1 = mathutils.Matrix.Translation(-c) + # get step + n_step = 36.0 + if ms.glarc_full is False: + step = arc_angle / n_step + else: + step = math.radians(360.0) / n_step + # + mat_rot1 = mathutils.Matrix.Rotation(step, 4, vn) + mat_trans2 = mathutils.Matrix.Translation(c) + p1 = mathutils.Vector(an_p1) # first point of arc + # Normal vector + vn = mathutils.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(0, 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 = mathutils.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, ms.glarc_s, ms.glarc_a, "99") + elif i == int(n_step) - 1: + draw_arrow(screen_point_p1, screen_point_p2, ms.glarc_s, "99", ms.glarc_b) + else: + draw_line(screen_point_p1, screen_point_p2) + + 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) + draw_line(screen_point_p2a, screen_point_p2b) + + if ms.gltype == 20: # Area + obverts = get_mesh_vertices(myobj) + tot = 0 + for face in ms.measureit_faces: + myvertices = [] + for v in face.measureit_index: + myvertices.extend([v.glidx]) + + area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d) + 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, math.fabs(d1 / 2)) + txtpoint2d = get_2d_point(region, rv3d, midpoint3d) + # Scale + if scene.measureit_scale is True: + tot = tot * scene.measureit_scale_factor + + # 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: + draw_text(myobj, txtpoint2d[0] + ms.glfontx, txtpoint2d[1] + ms.glfonty, + msg, ms.glcolorarea, + fsize) + + except IndexError: + ms.glfree = True + except: + # print("Unexpected error:" + str(sys.exc_info())) + # if error, disable segment + pass + + return + + +# ------------------------------------------ +# Get polygon area and paint area +# +# ------------------------------------------ +def get_area_and_paint(myvertices, myobj, obverts, region, rv3d): + 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 = bmesh.from_edit_mesh(myobj.data) + myv = [] + for v in bm.verts: + myv.extend([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) + + # Area + area = get_triangle_area(p1, p2, p3) + + 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) + + # Area + area = get_triangle_area(p1, p2, p3) + totarea += area + else: + return 0.0 + + return totarea + + +# ------------------------------------------ +# Get area using Heron formula +# +# ------------------------------------------ +def get_triangle_area(p1, p2, p3): + d1, dn = distance(p1, p2) + d2, dn = distance(p2, p3) + d3, dn = distance(p1, p3) + per = (d1 + d2 + d3) / 2.0 + area = math.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(0, 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 +# +# right: Align to right +# ------------------------------------------------------------- +def draw_text(myobj, x_pos, y_pos, display_text, rgb, fsize, right=False): + gap = 12 + font_id = 0 + blf.size(font_id, fsize, 72) + # 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 right is True: + newx = x_pos - text_width - gap + else: + newx = x_pos + # calculate new Y position + new_y = y_pos + (mheight * idx) + # Draw + blf.position(font_id, newx, new_y, 0) + bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) + blf.draw(font_id, " " + line) + # sub line + idx -= 1 + # saves max width + if maxwidth < text_width: + maxwidth = text_width + + return maxwidth, maxheight + + +# ------------------------------------------------------------- +# Draw an OpenGL line +# +# ------------------------------------------------------------- +def draw_line(v1, v2): + # noinspection PyBroadException + try: + if v1 is not None and v2 is not None: + bgl.glBegin(bgl.GL_LINES) + bgl.glVertex2f(*v1) + bgl.glVertex2f(*v2) + bgl.glEnd() + except: + pass + + +# ------------------------------------------------------------- +# Draw an OpenGL triangle +# +# ------------------------------------------------------------- +def draw_triangle(v1, v2, v3): + # noinspection PyBroadException + try: + if v1 is not None and v2 is not None and v3 is not None: + bgl.glBegin(bgl.GL_TRIANGLES) + bgl.glVertex2f(*v1) + bgl.glVertex2f(*v2) + bgl.glVertex2f(*v3) + bgl.glEnd() + except: + pass + + +# ------------------------------------------------------------- +# Draw an Arrow +# +# ------------------------------------------------------------- +def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"): + + rad45 = math.radians(45) + rad315 = math.radians(315) + rad90 = math.radians(90) + rad270 = math.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] * math.cos(rad_a) - v1i[1] * math.sin(rad_a) + v1[0]), + int(v1i[1] * math.cos(rad_a) + v1i[0] * math.sin(rad_a)) + v1[1]) + v1b = (int(v1i[0] * math.cos(rad_b) - v1i[1] * math.sin(rad_b) + v1[0]), + int(v1i[1] * math.cos(rad_b) + v1i[0] * math.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] * math.cos(rad_a) - v2i[1] * math.sin(rad_a) + v2[0]), + int(v2i[1] * math.cos(rad_a) + v2i[0] * math.sin(rad_a)) + v2[1]) + v2b = (int(v2i[0] * math.cos(rad_b) - v2i[1] * math.sin(rad_b) + v2[0]), + int(v2i[1] * math.cos(rad_b) + v2i[0] * math.sin(rad_b) + v2[1])) + + # Triangle o Lines + if a_typ == "1" or a_typ == "3": + draw_line(v1, v1a) + draw_line(v1, v1b) + + if b_typ == "1" or b_typ == "3": + draw_line(v2, v2a) + draw_line(v2, v2b) + + if a_typ == "2": + draw_triangle(v1, v1a, v1b) + if b_typ == "2": + draw_triangle(v2, v2a, v2b) + + draw_line(v1, v2) + + +# ------------------------------------------------------------- +# Draw an OpenGL Rectangle +# +# v1, v2 are corners (bottom left / top right) +# ------------------------------------------------------------- +def draw_rectangle(v1, v2): + # 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) + draw_line(v1b, v2) + draw_line(v2, v2b) + draw_line(v2b, v1) + 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 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 + rgb = scene.measureit_debug_color + fsize = scene.measureit_debug_font + precision = scene.measureit_debug_precision + # -------------------- + # vertex Loop + # -------------------- + if myobj.mode == 'EDIT': + bm = bmesh.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) + # colour + bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) + # converting to screen coordinates + txtpoint2d = get_2d_point(region, rv3d, a_p1) + # Text + txt = str(v.index) + if scene.measureit_debug_location is True: + txt += format_point(v.co, precision) + draw_text(myobj, txtpoint2d[0], txtpoint2d[1], txt, rgb, fsize) + except: + print("Unexpected error:" + str(sys.exc_info())) + pass + + 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 + rgb = scene.measureit_debug_color2 + rgb2 = scene.measureit_debug_color3 + 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 = bmesh.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) + # colour + line setup + bgl.glEnable(bgl.GL_BLEND) + bgl.glLineWidth(th) + bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) + # 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[0], txtpoint2d[1], str(f.index), rgb, fsize) + # Draw Normal + if scene.measureit_debug_normals is True: + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(rgb2[0], rgb2[1], rgb2[2], rgb2[3]) + draw_arrow(txtpoint2d, point2, 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, 10, "99", "1") + draw_arrow(b2d, c2d, 10, "99", "1") + # Normal vector data + txt = format_point(normal, precision) + draw_text(myobj, point2[0], point2[1], txt, rgb2, fsize) + + except: + print("Unexpected error:" + str(sys.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 = math.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 = math.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 = mathutils.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 = bmesh.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 = mathutils.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 = mathutils.Vector((pointa[0] - pointb[0], pointa[1] - pointb[1], pointa[2] - pointb[2])) + v2 = mathutils.Vector((pointc[0] - pointb[0], pointc[1] - pointb[1], pointc[2] - pointb[2])) + v3 = mathutils.Vector((pointd[0] - pointb[0], pointd[1] - pointb[1], pointd[2] - pointb[2])) + + angle = v1.angle(v2) + v2.angle(v3) + + rclength = math.pi * 2 * v2.length * (angle / (math.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 + # ------------------------ + # 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: + fmt += " ft" + if factor == 2: + fmt += s_code + tx_dist = fmt % feet + else: + inches = value * (39.3700787 ** factor) + 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: + fmt += " m" + if factor == 2: + fmt += s_code + tx_dist = fmt % value + else: + if round(value, 2) >= 0.01: + fmt += " cm" + if factor == 2: + fmt += s_code + d_cm = value * (100 ** factor) + tx_dist = fmt % d_cm + else: + 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": + fmt += " m" + if factor == 2: + fmt += s_code + tx_dist = fmt % value + # ------------------------ + # Units centimeters + # ------------------------ + elif units == "3": + fmt += " cm" + if factor == 2: + fmt += s_code + d_cm = value * (100 ** factor) + tx_dist = fmt % d_cm + # ------------------------ + # Units milimiters + # ------------------------ + elif units == "4": + fmt += " mm" + if factor == 2: + fmt += s_code + d_mm = value * (1000 ** factor) + tx_dist = fmt % d_mm + # ------------------------ + # Units feet + # ------------------------ + elif units == "5": + fmt += " ft" + if factor == 2: + fmt += s_code + feet = value * (3.2808399 ** factor) + tx_dist = fmt % feet + # ------------------------ + # Units inches + # ------------------------ + elif units == "6": + 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 diff --git a/measureit/measureit_main.py b/measureit/measureit_main.py new file mode 100644 index 00000000..798bb5b7 --- /dev/null +++ b/measureit/measureit_main.py @@ -0,0 +1,2061 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# File: measureit_main.py +# Main panel for different Measureit general actions +# Author: Antonio Vazquez (antonioya) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +import bmesh +# noinspection PyUnresolvedReferences +import bgl +# noinspection PyUnresolvedReferences +from bpy.app.handlers import persistent +# noinspection PyUnresolvedReferences +from measureit_geometry import * +from measureit_render import * + + +# ------------------------------------------------------ +# Handler to detect new Blend load +# +# ------------------------------------------------------ +# noinspection PyUnusedLocal +@persistent +def load_handler(dummy): + RunHintDisplayButton.handle_remove(None, bpy.context) + + +# ------------------------------------------------------ +# Handler to detect save Blend +# Clear not used measured +# +# ------------------------------------------------------ +# noinspection PyUnusedLocal +@persistent +def save_handler(dummy): + # noinspection PyBroadException + try: + print("MeasureIt: Cleaning data") + objlist = bpy.context.scene.objects + for myobj in objlist: + if 'MeasureGenerator' in myobj: + mp = myobj.MeasureGenerator[0] + x = 0 + for ms in mp.measureit_segments: + ms.name = "segment_" + str(x) + x += 1 + if ms.glfree is True: + idx = mp.measureit_segments.find(ms.name) + if idx > -1: + print("MeasureIt: Removed segment not used") + mp.measureit_segments.remove(idx) + + # reset size + mp.measureit_num = len(mp.measureit_segments) + except: + pass + +bpy.app.handlers.load_post.append(load_handler) +bpy.app.handlers.save_pre.append(save_handler) + + +# ------------------------------------------------------------------ +# Define property group class for measureit faces index +# ------------------------------------------------------------------ +class MeasureitIndex(bpy.types.PropertyGroup): + glidx = bpy.props.IntProperty(name="index", + description="vertex index") + +# Register +bpy.utils.register_class(MeasureitIndex) + + +# ------------------------------------------------------------------ +# Define property group class for measureit faces +# ------------------------------------------------------------------ +class MeasureitFaces(bpy.types.PropertyGroup): + glface = bpy.props.IntProperty(name="glface", + description="Face number") + # Array of index + measureit_index = bpy.props.CollectionProperty(type=MeasureitIndex) + +# Register +bpy.utils.register_class(MeasureitFaces) + + +# ------------------------------------------------------------------ +# Define property group class for measureit data +# ------------------------------------------------------------------ +class MeasureitProperties(bpy.types.PropertyGroup): + gltype = bpy.props.IntProperty(name="gltype", + description="Measure type (1-Segment, 2-Label, etc..)", default=1) + glpointa = bpy.props.IntProperty(name="glpointa", + description="Hidden property for opengl") + glpointb = bpy.props.IntProperty(name="glpointb", + description="Hidden property for opengl") + glpointc = bpy.props.IntProperty(name="glpointc", + description="Hidden property for opengl") + glcolor = bpy.props.FloatVectorProperty(name="glcolor", + description="Color for the measure", + default=(0.173, 0.545, 1.0, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + glview = bpy.props.BoolProperty(name="glview", + description="Measure visible/hide", + default=True) + glspace = bpy.props.FloatProperty(name='glspace', min=-100, max=100, default=0.1, + precision=3, + description='Distance to display measure') + glwidth = bpy.props.IntProperty(name='glwidth', min=1, max=10, default=1, + description='line width') + glfree = bpy.props.BoolProperty(name="glfree", + description="This measure is free and can be deleted", + default=False) + gltxt = bpy.props.StringProperty(name="gltxt", maxlen=256, + description="Short description (use | for line break)") + gladvance = bpy.props.BoolProperty(name="gladvance", + description="Advanced options as line width or position", + default=False) + gldefault = bpy.props.BoolProperty(name="gldefault", + description="Display measure in position calculated by default", + default=True) + glnormalx = bpy.props.FloatProperty(name="glnormalx", + description="Change orientation in X axis", + default=1, min=-1, max=1, precision=2) + glnormaly = bpy.props.FloatProperty(name="glnormaly", + description="Change orientation in Y axis", + default=0, min=-1, max=1, precision=2) + glnormalz = bpy.props.FloatProperty(name="glnormalz", + description="Change orientation in Z axis", + default=0, min=-1, max=1, precision=2) + glfont_size = bpy.props.IntProperty(name="Text Size", + description="Text size", + default=14, min=6, max=150) + gllink = bpy.props.StringProperty(name="gllink", + description="linked object for linked measures") + glocwarning = bpy.props.BoolProperty(name="glocwarning", + description="Display a warning if some axis is not used in distance", + default=True) + glocx = bpy.props.BoolProperty(name="glocx", + description="Include changes in X axis for calculating the distance", + default=True) + glocy = bpy.props.BoolProperty(name="glocy", + description="Include changes in Y axis for calculating the distance", + default=True) + glocz = bpy.props.BoolProperty(name="glocz", + description="Include changes in Z axis for calculating the distance", + default=True) + glfontx = bpy.props.IntProperty(name="glfontx", + description="Change font position in X axis", + default=0, min=-3000, max=3000) + glfonty = bpy.props.IntProperty(name="glfonty", + description="Change font position in Y axis", + default=0, min=-3000, max=3000) + gldist = bpy.props.BoolProperty(name="gldist", + description="Display distance for this measure", + default=True) + glnames = bpy.props.BoolProperty(name="glnames", + description="Display text for this measure", + default=True) + gltot = bpy.props.EnumProperty(items=(('99', "-", "Select a group for sum"), + ('0', "A", ""), + ('1', "B", ""), + ('2', "C", ""), + ('3', "D", ""), + ('4', "E", ""), + ('5', "F", ""), + ('6', "G", ""), + ('7', "H", ""), + ('8', "I", ""), + ('9', "J", ""), + ('10', "K", ""), + ('11', "L", ""), + ('12', "M", ""), + ('13', "N", ""), + ('14', "O", ""), + ('15', "P", ""), + ('16', "Q", ""), + ('17', "R", ""), + ('18', "S", ""), + ('19', "T", ""), + ('20', "U", ""), + ('21', "V", ""), + ('22', "W", ""), + ('23', "X", ""), + ('24', "Y", ""), + ('25', "Z", "")), + name="Sum in Group", + description="Add segment length in selected group") + glorto = bpy.props.EnumProperty(items=(('99', "None", ""), + ('0', "A", "Point A must use selected point B location"), + ('1', "B", "Point B must use selected point A location")), + name="Orthogonal", + description="Display point selected as orthogonal (select axis to copy)") + glorto_x = bpy.props.BoolProperty(name="ox", + description="Copy X location", + default=False) + glorto_y = bpy.props.BoolProperty(name="oy", + description="Copy Y location", + default=False) + glorto_z = bpy.props.BoolProperty(name="oz", + description="Copy Z location", + default=False) + glarrow_a = bpy.props.EnumProperty(items=(('99', "--", "No arrow"), + ('1', "Line", "The point of the arrow are lines"), + ('2', "Triangle", "The point of the arrow is triangle"), + ('3', "TShape", "The point of the arrow is a T")), + name="A end", + description="Add arrows to point A") + glarrow_b = bpy.props.EnumProperty(items=(('99', "--", "No arrow"), + ('1', "Line", "The point of the arrow are lines"), + ('2', "Triangle", "The point of the arrow is triangle"), + ('3', "TShape", "The point of the arrow is a T")), + name="B end", + description="Add arrows to point B") + glarrow_s = bpy.props.IntProperty(name="Size", + description="Arrow size", + default=15, min=6, max=500) + + glarc_full = bpy.props.BoolProperty(name="arcfull", + description="Create full circunference", + default=False) + glarc_extrad = bpy.props.BoolProperty(name="arcextrad", + description="Adapt radio lengh to arc line", + default=True) + glarc_rad = bpy.props.BoolProperty(name="arc rad", + description="Show arc radius", + default=True) + glarc_len = bpy.props.BoolProperty(name="arc len", + description="Show arc length", + default=True) + glarc_ang = bpy.props.BoolProperty(name="arc ang", + description="Show arc angle", + default=True) + + glarc_a = bpy.props.EnumProperty(items=(('99', "--", "No arrow"), + ('1', "Line", "The point of the arrow are lines"), + ('2', "Triangle", "The point of the arrow is triangle"), + ('3', "TShape", "The point of the arrow is a T")), + name="Ar end", + description="Add arrows to point A") + glarc_b = bpy.props.EnumProperty(items=(('99', "--", "No arrow"), + ('1', "Line", "The point of the arrow are lines"), + ('2', "Triangle", "The point of the arrow is triangle"), + ('3', "TShape", "The point of the arrow is a T")), + name="Br end", + description="Add arrows to point B") + glarc_s = bpy.props.IntProperty(name="Size", + description="Arrow size", + default=15, min=6, max=500) + glarc_txradio = bpy.props.StringProperty(name="txradio", + description="Text for radius", default="r=") + glarc_txlen = bpy.props.StringProperty(name="txlen", + description="Text for length", default="L=") + glarc_txang = bpy.props.StringProperty(name="txang", + description="Text for angle", default="A=") + glcolorarea = bpy.props.FloatVectorProperty(name="glcolorarea", + description="Color for the measure of area", + default=(0.1, 0.1, 0.1, 1.0), + min=0.1, + max=1, + subtype='COLOR', + size=4) + + # Array of faces + measureit_faces = bpy.props.CollectionProperty(type=MeasureitFaces) + +# Register +bpy.utils.register_class(MeasureitProperties) + + +# ------------------------------------------------------------------ +# Define object class (container of segments) +# Measureit +# ------------------------------------------------------------------ +class MeasureContainer(bpy.types.PropertyGroup): + measureit_num = bpy.props.IntProperty(name='Number of measures', min=0, max=1000, default=0, + description='Number total of measureit elements') + # Array of segments + measureit_segments = bpy.props.CollectionProperty(type=MeasureitProperties) + + +bpy.utils.register_class(MeasureContainer) +bpy.types.Object.MeasureGenerator = bpy.props.CollectionProperty(type=MeasureContainer) + + +# ------------------------------------------------------------------ +# Define UI class +# Measureit +# ------------------------------------------------------------------ +class MeasureitEditPanel(bpy.types.Panel): + bl_idname = "measureit.editpanel" + bl_label = "Measureit" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'MesureIt' + + # ----------------------------------------------------- + # Verify if visible + # ----------------------------------------------------- + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + if 'MeasureGenerator' not in o: + return False + else: + mp = context.object.MeasureGenerator[0] + if mp.measureit_num > 0: + return True + else: + return False + + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + # noinspection PyUnusedLocal + def draw(self, context): + layout = self.layout + scene = context.scene + if context.object is not None: + if 'MeasureGenerator' in context.object: + box = layout.box() + row = box.row() + row.label(context.object.name) + row = box.row() + row.prop(scene, 'measureit_gl_precision', text="Precision") + row.prop(scene, 'measureit_units') + row = box.row() + row.prop(scene, 'measureit_gl_show_d', text="Distances", toggle=True, icon="ALIGN") + row.prop(scene, 'measureit_gl_show_n', text="Texts", toggle=True, icon="FONT_DATA") + + # Scale factor + row = box.row() + row.prop(scene, 'measureit_scale', text="Scale") + if scene.measureit_scale is True: + row.prop(scene, 'measureit_scale_factor', text="1") + row.prop(scene, 'measureit_scale_precision', text="") + row.prop(scene, 'measureit_gl_scaletxt', text="") + row = box.row() + row.prop(scene, 'measureit_scale_color') + row.prop(scene, 'measureit_scale_font') + row = box.row() + row.prop(scene, 'measureit_scale_pos_x') + row.prop(scene, 'measureit_scale_pos_y') + + # Override + row = box.row() + row.prop(scene, 'measureit_ovr', text="Override") + if scene.measureit_ovr is True: + row.prop(scene, 'measureit_ovr_color', text="") + row.prop(scene, 'measureit_ovr_font', text="Font") + row.prop(scene, 'measureit_ovr_width', text="Width") + + mp = context.object.MeasureGenerator[0] + # ----------------- + # loop + # ----------------- + if mp.measureit_num > 0: + box = layout.box() + for idx in range(0, mp.measureit_num): + if mp.measureit_segments[idx].glfree is False: + add_item(box, idx, mp.measureit_segments[idx]) + + row = box.row() + row.operator("measureit.deleteallsegmentbutton", text="Delete all", icon="X") + # ----------------- + # Sum loop segments + # ----------------- + if mp.measureit_num > 0: + scale = bpy.context.scene.unit_settings.scale_length + 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"] + tot = [0.0] * len(tx) + ac = [False] * len(tx) + myobj = context.object + obverts = get_mesh_vertices(myobj) + viewtot = False + for idx in range(0, 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: # 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[int(ms.gltot)] += usedist + ac[int(ms.gltot)] = True + viewtot = True + # ----------------- + # Print values + # ----------------- + if viewtot is True: + pr = scene.measureit_gl_precision + fmt = "%1." + str(pr) + "f" + units = scene.measureit_units + + box = layout.box() + box.label("Totals", icon='SOLO_ON') + final = 0 + for idx in range(0, len(tot)): + if ac[idx] is True: + final += tot[idx] + tx_dist = format_distance(fmt, units, tot[idx]) + row = box.row(True) + row.label("Group " + tx[idx] + ":") + row.label(" ") + row.label(tx_dist) + + # Grand total + row = box.row(True) + row.label("") + row.label(" ") + row.label("-" * 20) + tx_dist = format_distance(fmt, units, final) + + row = box.row(True) + row.label("") + row.label(" ") + row.label(tx_dist) + # delete all + row = box.row() + row.operator("measureit.deleteallsumbutton", text="Delete all", icon="X") + + +# ----------------------------------------------------- +# Add segment to the panel. +# ----------------------------------------------------- +def add_item(box, idx, segment): + row = box.row(True) + if segment.glview is True: + icon = "VISIBLE_IPO_ON" + else: + icon = "VISIBLE_IPO_OFF" + + row.prop(segment, 'glview', text="", toggle=True, icon=icon) + row.prop(segment, 'gladvance', text="", toggle=True, icon="MANIPUL") + row.prop(segment, 'gltxt', text="") + row.prop(segment, 'glcolor', text="") + op = row.operator("measureit.deletesegmentbutton", text="", icon="X") + op.tag = idx # saves internal data + if segment.gladvance is True: + row = box.row(True) + if segment.gltype != 10 and segment.gltype != 20: + row.prop(segment, 'glspace', text="Distance") + + row.prop(segment, 'glfont_size', text="Font") + row.prop(segment, 'glfontx', text="X") + row.prop(segment, 'glfonty', text="Y") + + # Arrows + if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20: + row = box.row(True) + row.prop(segment, 'glarrow_a', text="") + row.prop(segment, 'glarrow_b', text="") + row.prop(segment, 'glarrow_s', text="Size") + + if segment.gltype != 2 and segment.gltype != 10: + row = box.row(True) + row.prop(segment, 'gldist', text="Distance", toggle=True, icon="ALIGN") + row.prop(segment, 'glnames', text="Text", toggle=True, icon="FONT_DATA") + # sum distances + if segment.gltype == 1 or segment.gltype == 12 or segment.gltype == 13 or segment.gltype == 14: + row.prop(segment, 'gltot', text="Sum") + + if segment.gltype != 10 and segment.gltype != 20: + row = box.row(True) + row.prop(segment, 'glwidth', text="Line") + row.prop(segment, 'gldefault', text="Automatic position") + if segment.gldefault is False: + row = box.row(True) + row.prop(segment, 'glnormalx', text="X") + row.prop(segment, 'glnormaly', text="Y") + row.prop(segment, 'glnormalz', text="Z") + + # Loc axis + if segment.gltype != 2 and segment.gltype != 10 \ + and segment.gltype != 12 and segment.gltype != 13 and segment.gltype != 14 and segment.gltype != 20: + row = box.row(True) + row.prop(segment, 'glocx', text="X", toggle=True) + row.prop(segment, 'glocy', text="Y", toggle=True) + row.prop(segment, 'glocz', text="Z", toggle=True) + if segment.glocx is False or segment.glocy is False or segment.glocz is False: + row = box.row() + if segment.gltype == 1: + row.prop(segment, 'glorto', text="Orthogonal") + row.prop(segment, 'glocwarning', text="Warning") + # ortogonal (only segments) + if segment.gltype == 1: + if segment.glorto != "99": + row = box.row(True) + row.prop(segment, 'glorto_x', text="X", toggle=True) + row.prop(segment, 'glorto_y', text="Y", toggle=True) + row.prop(segment, 'glorto_z', text="Z", toggle=True) + + # Arc special + if segment.gltype == 11: + row = box.row(True) + row.prop(segment, 'glarc_rad', text="Radius") + row.prop(segment, 'glarc_len', text="Length") + row.prop(segment, 'glarc_ang', text="Angle") + + row = box.row(True) + row.prop(segment, 'glarc_txradio', text="") + row.prop(segment, 'glarc_txlen', text="") + row.prop(segment, 'glarc_txang', text="") + row = box.row(True) + row.prop(segment, 'glarc_full', text="Full Circle") + if segment.glarc_rad is True: + row.prop(segment, 'glarc_extrad', text="Adapt radio") + + row = box.row(True) + row.prop(segment, 'glarc_a', text="") + row.prop(segment, 'glarc_b', text="") + row.prop(segment, 'glarc_s', text="Size") + + # Area special + if segment.gltype == 20: + row = box.row(True) + row.prop(segment, 'glcolorarea', text="") + + +# ------------------------------------------------------------------ +# Define panel class for main functions. +# ------------------------------------------------------------------ +class MeasureitMainPanel(bpy.types.Panel): + bl_idname = "measureit_main_panel" + bl_label = "Tools" + bl_space_type = 'VIEW_3D' + bl_region_type = "TOOLS" + bl_category = 'Measureit' + + # ------------------------------ + # Draw UI + # ------------------------------ + def draw(self, context): + layout = self.layout + scene = context.scene + + # ------------------------------ + # Tool Buttons + # ------------------------------ + box = layout.box() + # ------------------------------ + # Display Buttons + # ------------------------------ + row = box.row() + if context.window_manager.measureit_run_opengl is False: + icon = 'PLAY' + txt = 'Show' + else: + icon = "PAUSE" + txt = 'Hide' + + row.operator("measureit.runopenglbutton", text=txt, icon=icon) + row.prop(scene, "measureit_gl_ghost", text="", icon='GHOST_ENABLED') + + # Tools + box = layout.box() + row = box.row() + row.operator("measureit.addsegmentbutton", text="Segment", icon="ALIGN") + row.prop(scene, "measureit_sum", text="Sum") + + # To origin + row = box.row() + op = row.operator("measureit.addsegmentortobutton", text="X", icon="ALIGN") + op.tag = 0 # saves internal data + op = row.operator("measureit.addsegmentortobutton", text="Y", icon="ALIGN") + op.tag = 1 # saves internal data + op = row.operator("measureit.addsegmentortobutton", text="Z", icon="ALIGN") + op.tag = 2 # saves internal data + + row = box.row() + row.operator("measureit.addanglebutton", text="Angle", icon="LINCURVE") + row.operator("measureit.addarcbutton", text="Arc", icon="MAN_ROT") + + row = box.row() + row.operator("measureit.addlabelbutton", text="Label", icon="FONT_DATA") + row.operator("measureit.addnotebutton", text="Annotation", icon="NEW") + + row = box.row() + row.operator("measureit.addlinkbutton", text="Link", icon="ROTATECENTER") + row.operator("measureit.addoriginbutton", text="Origin", icon="CURSOR") + + row = box.row() + row.operator("measureit.addareabutton", text="Area", icon="MESH_GRID") + + # ------------------------------ + # Debug data + # ------------------------------ + box = layout.box() + row = box.row(False) + if scene.measureit_debug is False: + row.prop(scene, "measureit_debug", icon="TRIA_RIGHT", + text="Mesh Debug", emboss=False) + else: + row.prop(scene, "measureit_debug", icon="TRIA_DOWN", + text="Mesh Debug", emboss=False) + + row = box.row() + row.prop(scene, "measureit_debug_vertices", icon="LOOPSEL") + row.prop(scene, "measureit_debug_location", icon="EMPTY_DATA") + row.prop(scene, "measureit_debug_faces", icon="FACESEL") + row = box.row() + row.prop(scene, "measureit_debug_select", icon="GHOST_ENABLED") + row.prop(scene, "measureit_debug_normals", icon="MAN_TRANS") + if scene.measureit_debug_normals is True: + row.prop(scene, "measureit_debug_normal_size") + row.prop(scene, "measureit_debug_normal_details") + row = box.row() + row.prop(scene, 'measureit_debug_color', text="") + row.prop(scene, 'measureit_debug_color2', text="") + row.prop(scene, 'measureit_debug_color3', text="") + row = box.row() + row.prop(scene, 'measureit_debug_font', text="Font") + row.prop(scene, 'measureit_debug_width', text="Thickness") + row.prop(scene, 'measureit_debug_precision', text="Precision") + + +# ------------------------------------------------------------------ +# Define panel class for conf functions. +# ------------------------------------------------------------------ +class MeasureitConfPanel(bpy.types.Panel): + bl_idname = "measureit_conf_panel" + bl_label = "Configuration" + bl_space_type = 'VIEW_3D' + bl_region_type = "TOOLS" + bl_category = 'Measureit' + + # ------------------------------ + # Draw UI + # ------------------------------ + def draw(self, context): + layout = self.layout + scene = context.scene + + # Configuration data + box = layout.box() + row = box.row() + row.prop(scene, "measureit_gl_txt", text="Text") + row = box.row() + row.prop(scene, "measureit_default_color", text="") + row.prop(scene, "measureit_hint_space") + # Arrow + row = box.row(True) + row.prop(scene, "measureit_glarrow_a", text="") + row.prop(scene, "measureit_glarrow_b", text="") + row.prop(scene, "measureit_glarrow_s", text="Size") + + row = box.row() + row.prop(scene, "measureit_font_size") + + +# ------------------------------------------------------------------ +# Define panel class for render functions. +# ------------------------------------------------------------------ +class MeasureitRenderPanel(bpy.types.Panel): + bl_idname = "measureit_render_panel" + bl_label = "Render" + bl_space_type = 'VIEW_3D' + bl_region_type = "TOOLS" + bl_category = 'Measureit' + + # ------------------------------ + # Draw UI + # ------------------------------ + def draw(self, context): + layout = self.layout + scene = context.scene + + # Render settings + box = layout.box() + row = box.row() + row.prop(scene, "measureit_render_type") + row = box.row() + row.operator("measureit.rendersegmentbutton", icon='SCRIPT') + row = box.row() + row.prop(scene, "measureit_render", text="Save render image") + row = box.row() + row.prop(scene, "measureit_rf", text="Frame") + if scene.measureit_rf is True: + row.prop(scene, "measureit_rf_color", text="Color") + row = box.row() + row.prop(scene, "measureit_rf_border", text="Space") + row.prop(scene, "measureit_rf_line", text="Width") + + +# ------------------------------------------------------------- +# Defines button for add a measure segment +# +# ------------------------------------------------------------- +class AddSegmentButton(bpy.types.Operator): + bl_idname = "measureit.addsegmentbutton" + bl_label = "Add" + bl_description = "(EDITMODE only) Add a new measure segment between 2 vertices (select 2 vertices or more)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH": + if bpy.context.mode == 'EDIT_MESH': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_smart_selected(mainobject) + if len(mylist) < 2: # if not selected linked vertex + mylist = get_selected_vertex(mainobject) + + if len(mylist) >= 2: + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + for x in range(0, len(mylist) - 1, 2): + # ----------------------- + # Only if not exist + # ----------------------- + if exist_segment(mp, mylist[x], mylist[x + 1]) is False: + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 1 + ms.glpointa = mylist[x] + ms.glpointb = mylist[x + 1] + ms.glarrow_a = scene.measureit_glarrow_a + ms.glarrow_b = scene.measureit_glarrow_b + ms.glarrow_s = scene.measureit_glarrow_s + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Sum group + ms.gltot = scene.measureit_sum + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'ERROR'}, + "MeasureIt: Select at least two vertices for creating measure segment.") + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add area measure +# +# ------------------------------------------------------------- +class AddAreaButton(bpy.types.Operator): + bl_idname = "measureit.addareabutton" + bl_label = "Area" + bl_description = "(EDITMODE only) Add a new measure for area (select 1 o more faces)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH": + if bpy.context.mode == 'EDIT_MESH': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_selected_faces(mainobject) + if len(mylist) >= 1: + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + mp.measureit_segments.add() + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 20 + + f = -1 + for face in mylist: + # Create array elements + ms.measureit_faces.add() + f += 1 + # Set values + mf = ms.measureit_faces[f] + mf.glface = f + i = 0 + for v in face: + mf.measureit_index.add() + mi = mf.measureit_index[i] + mi.glidx = v + i += 1 + + # color + rgb = scene.measureit_default_color + ms.glcolor = (rgb[0], rgb[1], rgb[2], 0.4) + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Sum group + ms.gltot = scene.measureit_sum + # Add index + mp.measureit_num += 1 + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'ERROR'}, + "MeasureIt: Select at least one face for creating area measure. ") + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add a measure segment to x/y/z origin +# +# ------------------------------------------------------------- +class AddSegmentOrtoButton(bpy.types.Operator): + bl_idname = "measureit.addsegmentortobutton" + bl_label = "Add" + bl_description = "(EDITMODE only) Add a new measure segment from vertex to object origin for one " \ + "axis (select 1 vertex)" + bl_category = 'Measureit' + tag = bpy.props.IntProperty() + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH": + if bpy.context.mode == 'EDIT_MESH': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_smart_selected(mainobject) + if len(mylist) < 1: # if not selected linked vertex + mylist = get_selected_vertex(mainobject) + + if len(mylist) >= 1: + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + for x in range(0, len(mylist)): + # ----------------------- + # Only if not exist + # ----------------------- + if exist_segment(mp, mylist[x], mylist[x], 12 + int(self.tag)) is False: + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 12 + int(self.tag) + ms.glpointa = mylist[x] + ms.glpointb = mylist[x] + ms.glarrow_a = scene.measureit_glarrow_a + ms.glarrow_b = scene.measureit_glarrow_b + ms.glarrow_s = scene.measureit_glarrow_s + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Sum group + ms.gltot = scene.measureit_sum + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'ERROR'}, + "MeasureIt: Select at least one vertex for creating measure segment.") + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add a angle +# +# ------------------------------------------------------------- +class AddAngleButton(bpy.types.Operator): + bl_idname = "measureit.addanglebutton" + bl_label = "Angle" + bl_description = "(EDITMODE only) Add a new angle measure (select 3 vertices, 2nd is angle vertex)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH": + if bpy.context.mode == 'EDIT_MESH': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_selected_vertex_history(mainobject) + if len(mylist) == 3: + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + # ----------------------- + # Only if not exist + # ----------------------- + if exist_segment(mp, mylist[0], mylist[1], 9, mylist[2]) is False: + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 9 + ms.glpointa = mylist[0] + ms.glpointb = mylist[1] + ms.glpointc = mylist[2] + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'ERROR'}, + "MeasureIt: Select three vertices for creating angle measure") + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add a arc +# +# ------------------------------------------------------------- +class AddArcButton(bpy.types.Operator): + bl_idname = "measureit.addarcbutton" + bl_label = "Angle" + bl_description = "(EDITMODE only) Add a new arc measure (select 3 vertices of the arc," \ + " vertices 1st and 3rd are arc extremes)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH": + if bpy.context.mode == 'EDIT_MESH': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_selected_vertex_history(mainobject) + if len(mylist) == 3: + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + # ----------------------- + # Only if not exist + # ----------------------- + if exist_segment(mp, mylist[0], mylist[1], 11, mylist[2]) is False: + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 11 + ms.glpointa = mylist[0] + ms.glpointb = mylist[1] + ms.glpointc = mylist[2] + ms.glarrow_a = scene.measureit_glarrow_a + ms.glarrow_b = scene.measureit_glarrow_b + ms.glarrow_s = scene.measureit_glarrow_s + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'ERROR'}, + "MeasureIt: Select three vertices for creating arc measure") + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add a label segment +# +# ------------------------------------------------------------- +class AddLabelButton(bpy.types.Operator): + bl_idname = "measureit.addlabelbutton" + bl_label = "Add" + bl_description = "(EDITMODE only) Add a new measure label (select 1 vertex)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH": + if bpy.context.mode == 'EDIT_MESH': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_selected_vertex(mainobject) + if len(mylist) == 1: + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + # ----------------------- + # Only if not exist + # ----------------------- + if exist_segment(mp, mylist[0], mylist[0], 2) is False: # Both equal + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 2 + ms.glpointa = mylist[0] + ms.glpointb = mylist[0] # Equal + ms.glarrow_a = scene.measureit_glarrow_a + ms.glarrow_b = scene.measureit_glarrow_b + ms.glarrow_s = scene.measureit_glarrow_s + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'ERROR'}, + "MeasureIt: Select one vertex for creating measure label") + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add a link +# +# ------------------------------------------------------------- +class AddLinkButton(bpy.types.Operator): + bl_idname = "measureit.addlinkbutton" + bl_label = "Add" + bl_description = "(OBJECT mode only) Add a new measure between objects (select 2 " \ + "objects and optionally 1 or 2 vertices)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LAMP": + if bpy.context.mode == 'OBJECT': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + scene = context.scene + mainobject = context.object + # ------------------------------- + # Verify number of objects + # ------------------------------- + if len(context.selected_objects) != 2: + self.report({'ERROR'}, + "MeasureIt: Select two objects only, and optionally 1 vertex or 2 vertices " + "(one of each object)") + return {'FINISHED'} + # Locate other object + linkobject = None + for o in context.selected_objects: + if o.name != mainobject.name: + linkobject = o.name + # Verify destination vertex + lkobj = bpy.data.objects[linkobject] + mylinkvertex = get_selected_vertex(lkobj) + if len(mylinkvertex) > 1: + self.report({'ERROR'}, + "MeasureIt: The destination object has more than one vertex selected. " + "Select only 1 or none") + return {'FINISHED'} + # Verify origin vertex + myobjvertex = get_selected_vertex(mainobject) + if len(mylinkvertex) > 1: + self.report({'ERROR'}, + "MeasureIt: The active object has more than one vertex selected. Select only 1 or none") + return {'FINISHED'} + + # ------------------------------- + # Add properties + # ------------------------------- + flag = False + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + + # if exist_segment(mp, mylist[0], mylist[0], 3) is False: + # flag = True + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + # ----------------------- + # Vertex to Vertex + # ----------------------- + if len(myobjvertex) == 1 and len(mylinkvertex) == 1: + ms.gltype = 3 + ms.glpointa = myobjvertex[0] + ms.glpointb = mylinkvertex[0] + flag = True + # ----------------------- + # Vertex to Object + # ----------------------- + if len(myobjvertex) == 1 and len(mylinkvertex) == 0: + ms.gltype = 4 + ms.glpointa = myobjvertex[0] + ms.glpointb = 0 + flag = True + # ----------------------- + # Object to Vertex + # ----------------------- + if len(myobjvertex) == 0 and len(mylinkvertex) == 1: + ms.gltype = 5 + ms.glpointa = 0 + ms.glpointb = mylinkvertex[0] + flag = True + # ----------------------- + # Object to Object + # ----------------------- + if len(myobjvertex) == 0 and len(mylinkvertex) == 0: + ms.gltype = 8 + ms.glpointa = 0 + ms.glpointb = 0 # Equal + flag = True + + # ------------------ + # only if created + # ------------------ + if flag is True: + ms.glarrow_a = scene.measureit_glarrow_a + ms.glarrow_b = scene.measureit_glarrow_b + ms.glarrow_s = scene.measureit_glarrow_s + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # link + ms.gllink = linkobject + # Add index + mp.measureit_num += 1 + + # ----------------------- + # Only if not exist + # ----------------------- + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for add a origin segment +# +# ------------------------------------------------------------- +class AddOriginButton(bpy.types.Operator): + bl_idname = "measureit.addoriginbutton" + bl_label = "Add" + bl_description = "(OBJECT mode only) Add a new measure to origin (select object and optionally 1 vertex)" + bl_category = 'Measureit' + + # ------------------------------ + # Poll + # ------------------------------ + @classmethod + def poll(cls, context): + o = context.object + if o is None: + return False + else: + if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LAMP": + if bpy.context.mode == 'OBJECT': + return True + else: + return False + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + scene = context.scene + mainobject = context.object + mylist = get_selected_vertex(mainobject) + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # ----------------------- + # Set values + # ----------------------- + ms = mp.measureit_segments[mp.measureit_num] + flag = False + if len(mylist) > 0: + if len(mylist) == 1: + if exist_segment(mp, mylist[0], mylist[0], 6) is False: # Both equal + flag = True + # Vertex to origin + ms.gltype = 6 + ms.glpointa = mylist[0] + ms.glpointb = mylist[0] + else: + self.report({'ERROR'}, + "MeasureIt: Enter in EDITMODE and select one vertex only for creating " + "measure from vertex to origin") + return {'FINISHED'} + else: + # Object to origin + if exist_segment(mp, 0, 0, 7) is False: # Both equal + flag = True + ms.gltype = 7 + ms.glpointa = 0 + ms.glpointb = 0 + # ------------------ + # only if created + # ------------------ + if flag is True: + ms.glarrow_a = scene.measureit_glarrow_a + ms.glarrow_b = scene.measureit_glarrow_b + ms.glarrow_s = scene.measureit_glarrow_s + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for delete a measure segment +# +# ------------------------------------------------------------- +class DeleteSegmentButton(bpy.types.Operator): + bl_idname = "measureit.deletesegmentbutton" + bl_label = "Delete" + bl_description = "Delete a measure" + bl_category = 'Measureit' + tag = bpy.props.IntProperty() + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + mainobject = context.object + mp = mainobject.MeasureGenerator[0] + ms = mp.measureit_segments[self.tag] + ms.glfree = True + # Delete element + mp.measureit_segments.remove(self.tag) + mp.measureit_num -= 1 + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for delete all measure segment +# +# ------------------------------------------------------------- +class DeleteAllSegmentButton(bpy.types.Operator): + bl_idname = "measureit.deleteallsegmentbutton" + bl_label = "Delete" + bl_description = "Delete all measures (it cannot be undone)" + bl_category = 'Measureit' + tag = bpy.props.IntProperty() + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + # Add properties + mainobject = context.object + mp = mainobject.MeasureGenerator[0] + + while len(mp.measureit_segments) > 0: + mp.measureit_segments.remove(0) + + # reset size + mp.measureit_num = len(mp.measureit_segments) + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for delete all measure segment +# +# ------------------------------------------------------------- +class DeleteAllSumButton(bpy.types.Operator): + bl_idname = "measureit.deleteallsumbutton" + bl_label = "Delete" + bl_description = "Delete all sum groups" + bl_category = 'Measureit' + tag = bpy.props.IntProperty() + + # ------------------------------ + # Execute button action + # ------------------------------ + # noinspection PyMethodMayBeStatic + def execute(self, context): + if context.object is not None: + if 'MeasureGenerator' in context.object: + mp = context.object.MeasureGenerator[0] + for idx in range(0, mp.measureit_num): + ms = mp.measureit_segments[idx] + ms.gltot = '99' + + return {'FINISHED'} + + +# ------------------------------------------------------------- +# Defines button for render +# +# ------------------------------------------------------------- +class RenderSegmentButton(bpy.types.Operator): + bl_idname = "measureit.rendersegmentbutton" + bl_label = "Render" + bl_description = "Create a render image with measures. Use UV/Image editor to view image generated" + bl_category = 'Measureit' + tag = bpy.props.IntProperty() + + # ------------------------------ + # Execute button action + # ------------------------------ + # noinspection PyMethodMayBeStatic,PyUnusedLocal + def execute(self, context): + scene = context.scene + msg = "New image created with measures. Open it in UV/image editor" + camera_msg = "Unable to render. No camera found" + # ----------------------------- + # Check camera + # ----------------------------- + if scene.camera is None: + self.report({'ERROR'}, camera_msg) + return {'FINISHED'} + # ----------------------------- + # Use default render + # ----------------------------- + if scene.measureit_render_type == "1": + # noinspection PyBroadException + try: + result = bpy.data.images['Render Result'] + if result.has_data is False: + bpy.ops.render.render() + except: + bpy.ops.render.render() + print("MeasureIt: Using current render image on buffer") + if render_main(self, context) is True: + self.report({'INFO'}, msg) + + # ----------------------------- + # OpenGL image + # ----------------------------- + if scene.measureit_render_type == "2": + self.set_camera_view() + self.set_only_render(True) + + print("MeasureIt: Rendering opengl image") + bpy.ops.render.opengl() + if render_main(self, context) is True: + self.report({'INFO'}, msg) + + self.set_only_render(False) + + # ----------------------------- + # OpenGL Animation + # ----------------------------- + if scene.measureit_render_type == "3": + oldframe = scene.frame_current + self.set_camera_view() + self.set_only_render(True) + flag = False + # loop frames + for frm in range(scene.frame_start, scene.frame_end + 1): + scene.frame_set(frm) + print("MeasureIt: Rendering opengl frame %04d" % frm) + bpy.ops.render.opengl() + flag = render_main(self, context, True) + if flag is False: + break + + self.set_only_render(False) + scene.frame_current = oldframe + if flag is True: + self.report({'INFO'}, msg) + + # ----------------------------- + # Image + # ----------------------------- + if scene.measureit_render_type == "4": + print("MeasureIt: Rendering image") + bpy.ops.render.render() + if render_main(self, context) is True: + self.report({'INFO'}, msg) + + # ----------------------------- + # Animation + # ----------------------------- + if scene.measureit_render_type == "5": + oldframe = scene.frame_current + flag = False + # loop frames + for frm in range(scene.frame_start, scene.frame_end + 1): + scene.frame_set(frm) + print("MeasureIt: Rendering frame %04d" % frm) + bpy.ops.render.render() + flag = render_main(self, context, True) + if flag is False: + break + + scene.frame_current = oldframe + if flag is True: + self.report({'INFO'}, msg) + + return {'FINISHED'} + + # --------------------- + # Set cameraView + # --------------------- + # noinspection PyMethodMayBeStatic + def set_camera_view(self): + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + area.spaces[0].region_3d.view_perspective = 'CAMERA' + + # ------------------------------------- + # Set only render status + # ------------------------------------- + # noinspection PyMethodMayBeStatic + def set_only_render(self, status): + screen = bpy.context.screen + + v3d = False + s = None + # get spaceview_3d in current screen + for a in screen.areas: + if a.type == 'VIEW_3D': + for s in a.spaces: + if s.type == 'VIEW_3D': + v3d = s + break + + if v3d is not False: + s.show_only_render = status + + +# ------------------------------------------------------------- +# Defines a new note +# +# ------------------------------------------------------------- +class AddNoteButton(bpy.types.Operator): + bl_idname = "measureit.addnotebutton" + bl_label = "Note" + bl_description = "(OBJECT mode only) Add a new annotation" + bl_category = 'Measureit' + tag = bpy.props.IntProperty() + + # ------------------------------ + # Poll + # ------------------------------ + # noinspection PyUnusedLocal + @classmethod + def poll(cls, context): + if bpy.context.mode == 'OBJECT': + return True + else: + return False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + bpy.ops.object.empty_add(type='PLAIN_AXES') + myempty = bpy.data.objects[bpy.context.active_object.name] + myempty.location = bpy.context.scene.cursor_location + myempty.empty_draw_size = 0.01 + myempty.name = "Annotation" + # Add properties + scene = context.scene + mainobject = myempty + if 'MeasureGenerator' not in mainobject: + mainobject.MeasureGenerator.add() + + mp = mainobject.MeasureGenerator[0] + # Create all array elements + for cont in range(len(mp.measureit_segments) - 1, mp.measureit_num): + mp.measureit_segments.add() + + # Set values + ms = mp.measureit_segments[mp.measureit_num] + ms.gltype = 10 + ms.glpointa = 0 + ms.glpointb = 0 # Equal + # color + ms.glcolor = scene.measureit_default_color + # dist + ms.glspace = scene.measureit_hint_space + # text + ms.gltxt = scene.measureit_gl_txt + ms.glfont_size = scene.measureit_font_size + # Add index + mp.measureit_num += 1 + + # redraw + context.area.tag_redraw() + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Defines button for enable/disable the tip display +# +# ------------------------------------------------------------- +class RunHintDisplayButton(bpy.types.Operator): + bl_idname = "measureit.runopenglbutton" + bl_label = "Display hint data manager" + bl_description = "Main control for enabling or disabling the display of measurements in the viewport" + bl_category = 'Measureit' + + _handle = None # keep function handler + + # ---------------------------------- + # Enable gl drawing adding handler + # ---------------------------------- + @staticmethod + def handle_add(self, context): + if RunHintDisplayButton._handle is None: + RunHintDisplayButton._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), + 'WINDOW', + 'POST_PIXEL') + context.window_manager.measureit_run_opengl = True + + # ------------------------------------ + # Disable gl drawing removing handler + # ------------------------------------ + # noinspection PyUnusedLocal + @staticmethod + def handle_remove(self, context): + if RunHintDisplayButton._handle is not None: + bpy.types.SpaceView3D.draw_handler_remove(RunHintDisplayButton._handle, 'WINDOW') + RunHintDisplayButton._handle = None + context.window_manager.measureit_run_opengl = False + + # ------------------------------ + # Execute button action + # ------------------------------ + def execute(self, context): + if context.area.type == 'VIEW_3D': + if context.window_manager.measureit_run_opengl is False: + self.handle_add(self, context) + context.area.tag_redraw() + else: + self.handle_remove(self, context) + context.area.tag_redraw() + + return {'FINISHED'} + else: + self.report({'WARNING'}, + "View3D not found, cannot run operator") + + return {'CANCELLED'} + + +# ------------------------------------------------------------- +# Handle all draw routines (OpenGL main entry point) +# +# ------------------------------------------------------------- +def draw_main(context): + region = bpy.context.region + # Detect if Quadview to get drawing area + if not context.space_data.region_quadviews: + rv3d = bpy.context.space_data.region_3d + else: + # verify area + if context.area.type != 'VIEW_3D' or context.space_data.type != 'VIEW_3D': + return + i = -1 + for region in context.area.regions: + if region.type == 'WINDOW': + i += 1 + if context.region.id == region.id: + break + else: + return + + rv3d = context.space_data.region_quadviews[i] + + scene = bpy.context.scene + # Get visible layers + layers = [] + for x in range(0, 20): + if bpy.context.scene.layers[x] is True: + layers.extend([x]) + + # Display selected or all + if scene.measureit_gl_ghost is False: + objlist = context.selected_objects + else: + objlist = context.scene.objects + + # Enable GL drawing + bgl.glEnable(bgl.GL_BLEND) + # --------------------------------------- + # Generate all OpenGL calls for measures + # --------------------------------------- + for myobj in objlist: + if myobj.hide is False: + if 'MeasureGenerator' in myobj: + # verify visible layer + for x in range(0, 20): + if myobj.layers[x] is True: + if x in layers: + op = myobj.MeasureGenerator[0] + draw_segments(context, myobj, op, region, rv3d) + break + # --------------------------------------- + # Generate all OpenGL calls for debug + # --------------------------------------- + if scene.measureit_debug is True: + selobj = bpy.context.selected_objects + for myobj in selobj: + if scene.measureit_debug_vertices is True: + draw_vertices(context, myobj, region, rv3d) + if scene.measureit_debug_faces is True or scene.measureit_debug_normals is True: + draw_faces(context, myobj, region, rv3d) + + # ----------------------- + # restore opengl defaults + # ----------------------- + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + + +# ------------------------------------------------------------- +# Handler for drawing OpenGl +# ------------------------------------------------------------- +# noinspection PyUnusedLocal +def draw_callback_px(self, context): + draw_main(context) + + +# ------------------------------------------------------------- +# Check if the segment already exist +# +# ------------------------------------------------------------- +def exist_segment(mp, pointa, pointb, typ=1, pointc=None): + # for ms in mp.measureit_segments[mp.measureit_num] + for ms in mp.measureit_segments: + if ms.gltype == typ and ms.glfree is False: + if typ != 9: + if ms.glpointa == pointa and ms.glpointb == pointb: + return True + if ms.glpointa == pointb and ms.glpointb == pointa: + return True + else: + if ms.glpointa == pointa and ms.glpointb == pointb and ms.glpointc == pointc: + return True + + return False + + +# ------------------------------------------------------------- +# Get vertex selected +# ------------------------------------------------------------- +def get_selected_vertex(myobject): + mylist = [] + # if not mesh, no vertex + if myobject.type != "MESH": + return mylist + # -------------------- + # meshes + # -------------------- + oldobj = bpy.context.object + bpy.context.scene.objects.active = myobject + flag = False + if myobject.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + flag = True + + bm = bmesh.from_edit_mesh(myobject.data) + tv = len(bm.verts) + for v in bm.verts: + if v.select: + mylist.extend([v.index]) + + if flag is True: + bpy.ops.object.editmode_toggle() + # Back context object + bpy.context.scene.objects.active = oldobj + + # if select all vertices, then use origin + if tv == len(mylist): + return [] + + return mylist + + +# ------------------------------------------------------------- +# Get vertex selected +# ------------------------------------------------------------- +def get_selected_vertex_history(myobject): + mylist = [] + # if not mesh, no vertex + if myobject.type != "MESH": + return mylist + # -------------------- + # meshes + # -------------------- + oldobj = bpy.context.object + bpy.context.scene.objects.active = myobject + flag = False + if myobject.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + flag = True + + bm = bmesh.from_edit_mesh(myobject.data) + for v in bm.select_history: + mylist.extend([v.index]) + + if flag is True: + bpy.ops.object.editmode_toggle() + # Back context object + bpy.context.scene.objects.active = oldobj + + return mylist + + +# ------------------------------------------------------------- +# Get vertex selected segments +# ------------------------------------------------------------- +def get_smart_selected(myobject): + mylist = [] + # if not mesh, no vertex + if myobject.type != "MESH": + return mylist + # -------------------- + # meshes + # -------------------- + oldobj = bpy.context.object + bpy.context.scene.objects.active = myobject + flag = False + if myobject.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + flag = True + + bm = bmesh.from_edit_mesh(myobject.data) + for e in bm.edges: + if e.select is True: + mylist.extend([e.verts[0].index]) + mylist.extend([e.verts[1].index]) + + if flag is True: + bpy.ops.object.editmode_toggle() + # Back context object + bpy.context.scene.objects.active = oldobj + + return mylist + + +# ------------------------------------------------------------- +# Get vertex selected faces +# ------------------------------------------------------------- +def get_selected_faces(myobject): + mylist = [] + # if not mesh, no vertex + if myobject.type != "MESH": + return mylist + # -------------------- + # meshes + # -------------------- + oldobj = bpy.context.object + bpy.context.scene.objects.active = myobject + flag = False + if myobject.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + flag = True + + bm = bmesh.from_edit_mesh(myobject.data) + for e in bm.faces: + myface = [] + if e.select is True: + for i in range(0, len(e.verts)): + myface.extend([e.verts[i].index]) + + mylist.extend([myface]) + + if flag is True: + bpy.ops.object.editmode_toggle() + # Back context object + bpy.context.scene.objects.active = oldobj + + return mylist diff --git a/measureit/measureit_render.py b/measureit/measureit_render.py new file mode 100644 index 00000000..6c9733df --- /dev/null +++ b/measureit/measureit_render.py @@ -0,0 +1,343 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# support routines for render measures in final image +# Author: Antonio Vazquez (antonioya) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +import bgl +# noinspection PyUnresolvedReferences +import blf +# noinspection PyUnresolvedReferences +import mathutils +# noinspection PyUnresolvedReferences +import bmesh +import os +import sys +# noinspection PyUnresolvedReferences +import bpy_extras.image_utils as img_utils +# noinspection PyUnresolvedReferences +import bpy_extras.object_utils as object_utils +# noinspection PyUnresolvedReferences +from bpy_extras import view3d_utils +from math import ceil +from measureit_geometry import * + + +# ------------------------------------------------------------- +# Render image main entry point +# +# ------------------------------------------------------------- +def render_main(self, context, animation=False): + # noinspection PyBroadException,PyBroadException + # Save old info + settings = bpy.context.scene.render.image_settings + depth = settings.color_depth + settings.color_depth = '8' + # noinspection PyBroadException + try: + # Get visible layers + layers = [] + scene = context.scene + for x in range(0, 20): + if scene.layers[x] is True: + layers.extend([x]) + + # Get object list + objlist = context.scene.objects + # -------------------- + # Get resolution + # -------------------- + scene = bpy.context.scene + render_scale = scene.render.resolution_percentage / 100 + + width = int(scene.render.resolution_x * render_scale) + height = int(scene.render.resolution_y * render_scale) + # --------------------------------------- + # Get output path + # --------------------------------------- + ren_path = bpy.context.scene.render.filepath + if len(ren_path) > 0: + if ren_path.endswith(os.path.sep): + initpath = os.path.realpath(ren_path) + os.path.sep + else: + (initpath, filename) = os.path.split(ren_path) + outpath = os.path.join(initpath, "measureit_tmp_render.png") + else: + self.report({'ERROR'}, + "MeasureIt: Unable to save temporary render image. Define a valid render path") + settings.color_depth = depth + return False + + # Get Render Image + img = get_render_image(outpath) + if img is None: + self.report({'ERROR'}, + "MeasureIt: Unable to save temporary render image. Define a valid render path") + settings.color_depth = depth + return False + + # ----------------------------- + # Calculate rows and columns + # ----------------------------- + tile_x = 240 + tile_y = 216 + row_num = ceil(height / tile_y) + col_num = ceil(width / tile_x) + print("MeasureIt: Image divided in " + str(row_num) + "x" + str(col_num) + " tiles") + + # pixels out of visible area + cut4 = (col_num * tile_x * 4) - width * 4 # pixels aout of drawing area + totpixel4 = width * height * 4 # total pixels RGBA + + viewport_info = bgl.Buffer(bgl.GL_INT, 4) + bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info) + + # Load image on memory + img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST) + + # 2.77 API change + if bpy.app.version >= (2, 77, 0): + tex = img.bindcode[0] + else: + tex = img.bindcode + + # -------------------------------------------- + # Create output image (to apply texture) + # -------------------------------------------- + if "measureit_output" in bpy.data.images: + out_img = bpy.data.images["measureit_output"] + if out_img is not None: + out_img.user_clear() + bpy.data.images.remove(out_img) + + out = bpy.data.images.new("measureit_output", width, height) + tmp_pixels = [1] * totpixel4 + + # -------------------------------- + # Loop for all tiles + # -------------------------------- + for row in range(0, row_num): + for col in range(0, col_num): + buffer = bgl.Buffer(bgl.GL_FLOAT, width * height * 4) + bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image + bgl.glViewport(0, 0, tile_x, tile_y) + + bgl.glMatrixMode(bgl.GL_PROJECTION) + bgl.glLoadIdentity() + + # defines ortographic view for single tile + x1 = tile_x * col + y1 = tile_y * row + bgl.gluOrtho2D(x1, x1 + tile_x, y1, y1 + tile_y) + + # Clear + bgl.glClearColor(0.0, 0.0, 0.0, 0.0) + bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) + + bgl.glEnable(bgl.GL_TEXTURE_2D) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex) + + # defines drawing area + bgl.glBegin(bgl.GL_QUADS) + + bgl.glColor3f(1.0, 1.0, 1.0) + bgl.glTexCoord2f(0.0, 0.0) + bgl.glVertex2f(0.0, 0.0) + + bgl.glTexCoord2f(1.0, 0.0) + bgl.glVertex2f(width, 0.0) + + bgl.glTexCoord2f(1.0, 1.0) + bgl.glVertex2f(width, height) + + bgl.glTexCoord2f(0.0, 1.0) + bgl.glVertex2f(0.0, height) + + bgl.glEnd() + + # ----------------------------- + # Loop to draw all lines + # ----------------------------- + for myobj in objlist: + if myobj.hide is False: + if 'MeasureGenerator' in myobj: + # verify visible layer + for x in range(0, 20): + if myobj.layers[x] is True: + if x in layers: + op = myobj.MeasureGenerator[0] + draw_segments(context, myobj, op, None, None) + break + + # ----------------------------- + # Loop to draw all debug + # ----------------------------- + if scene.measureit_debug is True: + selobj = bpy.context.selected_objects + for myobj in selobj: + if scene.measureit_debug_vertices is True: + draw_vertices(context, myobj, None, None) + if scene.measureit_debug_faces is True or scene.measureit_debug_normals is True: + draw_faces(context, myobj, None, None) + + if scene.measureit_rf is True: + bgl.glColor3f(1.0, 1.0, 1.0) + rfcolor = scene.measureit_rf_color + rfborder = scene.measureit_rf_border + rfline = scene.measureit_rf_line + + bgl.glLineWidth(rfline) + bgl.glColor4f(rfcolor[0], rfcolor[1], rfcolor[2], rfcolor[3]) + + x1 = rfborder + x2 = width - rfborder + y1 = int(math.ceil(rfborder / (width / height))) + y2 = height - y1 + draw_rectangle((x1, y1), (x2, y2)) + + # -------------------------------- + # copy pixels to temporary area + # -------------------------------- + bgl.glFinish() + bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data + for y in range(0, tile_y): + # final image pixels position + p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4) + p2 = p1 + (tile_x * 4) + # buffer pixels position + b1 = y * width * 4 + b2 = b1 + (tile_x * 4) + + if p1 < totpixel4: # avoid pixel row out of area + if col == col_num - 1: # avoid pixel columns out of area + p2 -= cut4 + b2 -= cut4 + + tmp_pixels[p1:p2] = buffer[b1:b2] + + # ----------------------- + # Copy temporary to final + # ----------------------- + out.pixels = tmp_pixels[:] # Assign image data + img.gl_free() # free opengl image memory + + # delete image + img.user_clear() + bpy.data.images.remove(img) + # remove temp file + os.remove(outpath) + # reset + bgl.glEnable(bgl.GL_SCISSOR_TEST) + # ----------------------- + # restore opengl defaults + # ----------------------- + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + # Saves image + if out is not None and (scene.measureit_render is True or animation is True): + ren_path = bpy.context.scene.render.filepath + filename = "mit_frame" + if len(ren_path) > 0: + if ren_path.endswith(os.path.sep): + initpath = os.path.realpath(ren_path) + os.path.sep + else: + (initpath, filename) = os.path.split(ren_path) + + ftxt = "%04d" % scene.frame_current + outpath = os.path.join(initpath, filename + ftxt + ".png") + + save_image(self, outpath, out) + + settings.color_depth = depth + return True + + except: + settings.color_depth = depth + print("Unexpected error:" + str(sys.exc_info())) + self.report({'ERROR'}, "MeasureIt: Unable to create render image") + return False + + +# -------------------------------------------------------------------- +# Get the final render image and return as image object +# +# return None if no render available +# -------------------------------------------------------------------- +def get_render_image(outpath): + saved = False + # noinspection PyBroadException + try: + # noinspection PyBroadException + try: + result = bpy.data.images['Render Result'] + if result.has_data is False: + # this save produce to fill data image + result.save_render(outpath) + saved = True + except: + print("No render image found") + return None + + # Save and reload + if saved is False: + result.save_render(outpath) + + img = img_utils.load_image(outpath) + + return img + except: + print("Unexpected render image error") + return None + + +# ------------------------------------- +# Save image to file +# ------------------------------------- +def save_image(self, filepath, myimage): + # noinspection PyBroadException + try: + + # Save old info + settings = bpy.context.scene.render.image_settings + myformat = settings.file_format + mode = settings.color_mode + depth = settings.color_depth + + # Apply new info and save + settings.file_format = 'PNG' + settings.color_mode = "RGBA" + settings.color_depth = '8' + myimage.save_render(filepath) + print("MeasureIt: Image " + filepath + " saved") + + # Restore old info + settings.file_format = myformat + settings.color_mode = mode + settings.color_depth = depth + except: + print("Unexpected error:" + str(sys.exc_info())) + self.report({'ERROR'}, "MeasureIt: Unable to save render image") + return |