Skip to content
Snippets Groups Projects
measureit_main.py 82.3 KiB
Newer Older
# SPDX-License-Identifier: GPL-2.0-or-later

# ----------------------------------------------------------
# File: measureit_main.py
# Main panel for different Measureit general actions
# Author: Antonio Vazquez (antonioya)
#
# ----------------------------------------------------------
# noinspection PyUnresolvedReferences
import bpy
from bmesh import from_edit_mesh
# noinspection PyUnresolvedReferences
from bpy.types import PropertyGroup, Panel, Object, Operator, SpaceView3D
Antonioya's avatar
Antonioya committed
from bpy.props import IntProperty, CollectionProperty, FloatVectorProperty, BoolProperty, StringProperty, \
                      FloatProperty, EnumProperty
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):
    MEASUREIT_OT_RunHintDisplay.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

Antonioya's avatar
Antonioya committed

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(PropertyGroup):
    glidx: IntProperty(name="index", description="vertex index")
Antonioya's avatar
Antonioya committed


# Register
bpy.utils.register_class(MeasureitIndex)


# ------------------------------------------------------------------
# Define property group class for measureit faces
# ------------------------------------------------------------------
class MeasureitFaces(PropertyGroup):
    glface: IntProperty(name="glface", description="Face number")
    measureit_index: CollectionProperty(type=MeasureitIndex)
Antonioya's avatar
Antonioya committed

# Register
bpy.utils.register_class(MeasureitFaces)


# ------------------------------------------------------------------
# Define property group class for measureit data
# ------------------------------------------------------------------
class MeasureitProperties(PropertyGroup):
    gltype: IntProperty(name="gltype",
                       description="Measure type (1-Segment, 2-Label, etc..)", default=1)
    glpointa: IntProperty(name="glpointa",
                         description="Hidden property for opengl")
    glpointb: IntProperty(name="glpointb",
                         description="Hidden property for opengl")
    glpointc: IntProperty(name="glpointc",
                         description="Hidden property for opengl")
    glcolor: FloatVectorProperty(name="glcolor",
Antonioya's avatar
Antonioya committed
                                  description="Color for the measure",
                                  default=(0.173, 0.545, 1.0, 1.0),
                                  min=0.1,
                                  max=1,
                                  subtype='COLOR',
                                  size=4)
    glview: BoolProperty(name="glview",
Antonioya's avatar
Antonioya committed
                          description="Measure visible/hide",
                          default=True)
    glspace: FloatProperty(name='glspace', min=-100, max=100, default=0.1,
Antonioya's avatar
Antonioya committed
                            precision=3,
                            description='Distance to display measure')
    glwidth: IntProperty(name='glwidth', min=1, max=10, default=1,
Antonioya's avatar
Antonioya committed
                          description='line width')
    glfree: BoolProperty(name="glfree",
Antonioya's avatar
Antonioya committed
                          description="This measure is free and can be deleted",
                          default=False)
    gltxt: StringProperty(name="gltxt", maxlen=256,
Antonioya's avatar
Antonioya committed
                           description="Short description (use | for line break)")
    gladvance: BoolProperty(name="gladvance",
Antonioya's avatar
Antonioya committed
                             description="Advanced options as line width or position",
                             default=False)
    gldefault: BoolProperty(name="gldefault",
Antonioya's avatar
Antonioya committed
                             description="Display measure in position calculated by default",
                             default=True)
    glnormalx: FloatProperty(name="glnormalx",
Antonioya's avatar
Antonioya committed
                              description="Change orientation in X axis",
                              default=1, min=-1, max=1, precision=2)
    glnormaly: FloatProperty(name="glnormaly",
Antonioya's avatar
Antonioya committed
                              description="Change orientation in Y axis",
                              default=0, min=-1, max=1, precision=2)
    glnormalz: FloatProperty(name="glnormalz",
Antonioya's avatar
Antonioya committed
                              description="Change orientation in Z axis",
                              default=0, min=-1, max=1, precision=2)
    glfont_size: IntProperty(name="Text Size",
Antonioya's avatar
Antonioya committed
                              description="Text size",
                              default=14, min=6, max=150)
    glfont_align: EnumProperty(items=(('L', "Left Align", ""),
                                       ('C', "Center Align", ""),
                                       ('R', "Right Align", "")),
                                name="Align Font",
                                description="Set Font Alignment")
    glfont_rotat: IntProperty(name='Rotate', min=0, max=360, default=0,
                                description="Text rotation in degrees")
    gllink: StringProperty(name="gllink",
Antonioya's avatar
Antonioya committed
                            description="linked object for linked measures")
    glocwarning: BoolProperty(name="glocwarning",
Antonioya's avatar
Antonioya committed
                               description="Display a warning if some axis is not used in distance",
                               default=True)
    glocx: BoolProperty(name="glocx",
Antonioya's avatar
Antonioya committed
                         description="Include changes in X axis for calculating the distance",
                         default=True)
    glocy: BoolProperty(name="glocy",
Antonioya's avatar
Antonioya committed
                         description="Include changes in Y axis for calculating the distance",
                         default=True)
    glocz: BoolProperty(name="glocz",
Antonioya's avatar
Antonioya committed
                         description="Include changes in Z axis for calculating the distance",
                         default=True)
    glfontx: IntProperty(name="glfontx",
Antonioya's avatar
Antonioya committed
                          description="Change font position in X axis",
                          default=0, min=-3000, max=3000)
    glfonty: IntProperty(name="glfonty",
Antonioya's avatar
Antonioya committed
                          description="Change font position in Y axis",
                          default=0, min=-3000, max=3000)
    gldist: BoolProperty(name="gldist",
Antonioya's avatar
Antonioya committed
                          description="Display distance for this measure",
                          default=True)
    glnames: BoolProperty(name="glnames",
Antonioya's avatar
Antonioya committed
                           description="Display text for this measure",
                           default=True)
    gltot: EnumProperty(items=(('99', "-", "Select a group for sum"),
Antonioya's avatar
Antonioya committed
                                ('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", ""),
Antonioya's avatar
Antonioya committed
                                ('25', "Z", "")),
                         name="Sum in Group",
                         description="Add segment length in selected group")
    glorto: EnumProperty(items=(('99', "None", ""),
Antonioya's avatar
Antonioya committed
                                 ('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: BoolProperty(name="ox",
Antonioya's avatar
Antonioya committed
                            description="Copy X location",
                            default=False)
    glorto_y: BoolProperty(name="oy",
Antonioya's avatar
Antonioya committed
                            description="Copy Y location",
                            default=False)
    glorto_z: BoolProperty(name="oz",
Antonioya's avatar
Antonioya committed
                            description="Copy Z location",
                            default=False)
    glarrow_a: EnumProperty(items=(('99', "--", "No arrow"),
Antonioya's avatar
Antonioya committed
                                    ('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: EnumProperty(items=(('99', "--", "No arrow"),
Antonioya's avatar
Antonioya committed
                                    ('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: IntProperty(name="Size",
Antonioya's avatar
Antonioya committed
                            description="Arrow size",
                            default=15, min=6, max=500)
    glarc_full: BoolProperty(name="arcfull",
Brecht Van Lommel's avatar
Brecht Van Lommel committed
                              description="Create full circumference",
Antonioya's avatar
Antonioya committed
                              default=False)
    glarc_extrad: BoolProperty(name="arcextrad",
                                description="Adapt radio length to arc line",
Antonioya's avatar
Antonioya committed
                                default=True)
    glarc_rad: BoolProperty(name="arc rad",
Antonioya's avatar
Antonioya committed
                             description="Show arc radius",
                             default=True)
    glarc_len: BoolProperty(name="arc len",
Antonioya's avatar
Antonioya committed
                             description="Show arc length",
                             default=True)
    glarc_ang: BoolProperty(name="arc ang",
Antonioya's avatar
Antonioya committed
                             description="Show arc angle",
                             default=True)
    glarc_a: EnumProperty(items=(('99', "--", "No arrow"),
Antonioya's avatar
Antonioya committed
                                  ('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: EnumProperty(items=(('99', "--", "No arrow"),
Antonioya's avatar
Antonioya committed
                                  ('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: IntProperty(name="Size",
Antonioya's avatar
Antonioya committed
                          description="Arrow size",
                          default=15, min=6, max=500)
    glarc_txradio: StringProperty(name="txradio",
Antonioya's avatar
Antonioya committed
                                   description="Text for radius", default="r=")
    glarc_txlen: StringProperty(name="txlen",
Antonioya's avatar
Antonioya committed
                                 description="Text for length", default="L=")
    glarc_txang: StringProperty(name="txang",
Antonioya's avatar
Antonioya committed
                                 description="Text for angle", default="A=")
    glcolorarea: FloatVectorProperty(name="glcolorarea",
Antonioya's avatar
Antonioya committed
                                      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)
    measureit_faces: CollectionProperty(type=MeasureitFaces)
Antonioya's avatar
Antonioya committed

# Register
bpy.utils.register_class(MeasureitProperties)


# ------------------------------------------------------------------
# Define object class (container of segments)
# Measureit
# ------------------------------------------------------------------
class MeasureContainer(PropertyGroup):
    measureit_num: IntProperty(name='Number of measures', min=0, max=1000, default=0,
Antonioya's avatar
Antonioya committed
                                description='Number total of measureit elements')
    measureit_segments: CollectionProperty(type=MeasureitProperties)


bpy.utils.register_class(MeasureContainer)
Object.MeasureGenerator = CollectionProperty(type=MeasureContainer)


# ------------------------------------------------------------------
# Define UI class
# Measureit
# ------------------------------------------------------------------
class MEASUREIT_PT_Edit(Panel):
    bl_idname = "MEASUREIT_PT_Edit"
    bl_label = "Items"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
meta-androcto's avatar
meta-androcto committed
    bl_category= 'View'
    bl_parent_id = 'MEASUREIT_PT_Main'

    # -----------------------------------------------------
    # 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(text=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_CENTER")
                row.prop(scene, 'measureit_gl_show_n', text="Texts", toggle=True, icon="FONT_DATA")
                row = box.row()
                row.prop(scene, 'measureit_hide_units', text="Hide measurement unit")
                # Scale factor
                row = box.row()
                row.prop(scene, 'measureit_scale', text="Scale")
                if scene.measureit_scale is True:
                    split = row.split(factor=0.25, align=False)
                    split.prop(scene, 'measureit_scale_color', text="")
                    split.prop(scene, 'measureit_scale_factor', text="1")
                    row.separator()
                    row.prop(scene, 'measureit_gl_scaletxt', text="")
                    row.prop(scene, 'measureit_scale_font')
                    row.prop(scene, 'measureit_scale_precision', text="")
                    row.separator()
                    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:
                    split = row.split(factor=0.25, align=False)
                    split.prop(scene, 'measureit_ovr_color', text="")
                    split.prop(scene, 'measureit_ovr_width', text="Width")
                    row = box.row()
                    row.separator()
                    row.prop(scene, 'measureit_ovr_font', text="Font")
                    row.prop(scene, 'measureit_ovr_font_align', text="")
                    if scene.measureit_ovr_font_align == 'L':
                        row.prop(scene, 'measureit_ovr_font_rotation', text="Rotate")

                mp = context.object.MeasureGenerator[0]
                # -----------------
                # loop
                # -----------------
                if mp.measureit_num > 0:
                    box = layout.box()
                    row = box.row(align=True)
                    row.operator("measureit.expandallsegment", text="Expand all", icon="ZOOM_IN")
                    row.operator("measureit.collapseallsegment", text="Collapse all", icon="ZOOM_OUT")
NBurn's avatar
NBurn committed
                    for idx in range(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.deleteallsegment", 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
NBurn's avatar
NBurn committed
                    for idx in range(mp.measureit_num):
                        ms = mp.measureit_segments[idx]
                        if (ms.gltype == 1 or ms.gltype == 12
                            or ms.gltype == 13 or ms.gltype == 14) and ms.gltot != '99' \
                                and ms.glfree is False:  # only segments
                            if bpy.context.mode == "EDIT_MESH":
                                bm = bmesh.from_edit_mesh(bpy.context.edit_object.data)
                                if hasattr(bm.verts, "ensure_lookup_table"):
                                    bm.verts.ensure_lookup_table()
                            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(text="Totals", icon='SOLO_ON')
NBurn's avatar
NBurn committed
                        for idx in range(len(tot)):
                            if ac[idx] is True:
                                final += tot[idx]
                                tx_dist = format_distance(fmt, units, tot[idx])
                                row = box.row(align=True)
                                row.label(text="Group " + tx[idx] + ":")
                                row.label(text=" ")
                                row.label(text=tx_dist)
                        row = box.row(align=True)
                        row.label(text="")
                        row.label(text=" ")
                        row.label(text="-" * 20)
                        tx_dist = format_distance(fmt, units, final)

                        row = box.row(align=True)
                        row.label(text="")
                        row.label(text=" ")
                        row.label(text=tx_dist)
                        row.operator("measureit.deleteallsum", text="Delete all", icon="X")


# -----------------------------------------------------
# Add segment options to the panel.
# -----------------------------------------------------
def add_item(box, idx, segment):
    scene = bpy.context.scene
    row = box.row(align=True)
        icon = "HIDE_OFF"

    row.prop(segment, 'glview', text="", toggle=True, icon=icon)
    row.prop(segment, 'gladvance', text="", toggle=True, icon="PREFERENCES")
    if segment.gltype == 20:  # Area special
        split = row.split(factor=0.15, align=True)
        split.prop(segment, 'glcolorarea', text="")
        split = split.split(factor=0.20, align=True)
        split.prop(segment, 'glcolor', text="")
    else:
        split = row.split(factor=0.25, align=True)
        split.prop(segment, 'glcolor', text="")
    split.prop(segment, 'gltxt', text="")
    op = row.operator("measureit.deletesegment", text="", icon="X")
    op.tag = idx  # saves internal data
    if segment.gladvance is True:
        row = box.row(align=True)
        row.prop(segment, 'glfont_size', text="Font")
        row.prop(segment, 'glfont_align', text="")
        if segment.glfont_align == 'L':
            row.prop(segment, 'glfont_rotat', text="Rotate")
        row = box.row(align=True)
        if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20:
            row.prop(segment, 'glspace', text="Distance")
        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(align=True)
            row.prop(segment, 'glarrow_a', text="")
            row.prop(segment, 'glarrow_b', text="")
Campbell Barton's avatar
Campbell Barton committed
            if segment.glarrow_a != '99' or segment.glarrow_b != '99':
                row.prop(segment, 'glarrow_s', text="Size")

        if segment.gltype != 2 and segment.gltype != 10:
            row = box.row(align=True)
            if scene.measureit_gl_show_d is True and segment.gltype != 9:
                row.prop(segment, 'gldist', text="Distance", toggle=True, icon="ALIGN_CENTER")
            if scene.measureit_gl_show_n is True:
                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 != 9 and segment.gltype != 10 and segment.gltype != 20:
            row = box.row(align=True)
            row.prop(segment, 'glwidth', text="Line")
            row.prop(segment, 'gldefault', text="Automatic position")
            if segment.gldefault is False:
                row = box.row(align=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 != 9 and segment.gltype != 10 \
                and segment.gltype != 11 and segment.gltype != 12 and segment.gltype != 13 \
                and segment.gltype != 14 and segment.gltype != 20:
            row = box.row(align=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")
Brecht Van Lommel's avatar
Brecht Van Lommel committed
                # orthogonal (only segments)
                if segment.gltype == 1:
                    if segment.glorto != "99":
                        row = box.row(align=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(align=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(align=True)
            row.prop(segment, 'glarc_txradio', text="")
            row.prop(segment, 'glarc_txlen', text="")
            row.prop(segment, 'glarc_txang', text="")
            row = box.row(align=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(align=True)
            row.prop(segment, 'glarc_a', text="")
            row.prop(segment, 'glarc_b', text="")
            if segment.glarc_a != '99' or segment.glarc_b != '99':
                row.prop(segment, 'glarc_s', text="Size")


# ------------------------------------------------------------------
# Define panel class for main functions.
# ------------------------------------------------------------------
class MEASUREIT_PT_Main(Panel):
    bl_idname = "MEASUREIT_PT_Main"
    bl_label = "MeasureIt Tools"
    bl_region_type = 'UI'
meta-androcto's avatar
meta-androcto committed
    bl_category= 'View'
    bl_options = {'DEFAULT_CLOSED'}

    # ------------------------------
    # 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.runopengl", text=txt, icon=icon)
        row.prop(scene, "measureit_gl_ghost", text="", icon='GHOST_ENABLED')

        # Tools
        box = layout.box()
        box.label(text="Add Measures")
Antonioya's avatar
Antonioya committed
        row = box.row()
        row.operator("measureit.addsegment", text="Segment")
        row.prop(scene, "measureit_sum", text="Sum")

        # To origin
        row = box.row()
        op = row.operator("measureit.addsegmentorto", text="X")
        op.tag = 0  # saves internal data
        op = row.operator("measureit.addsegmentorto", text="Y")
        op.tag = 1  # saves internal data
        op = row.operator("measureit.addsegmentorto", text="Z")
        op.tag = 2  # saves internal data

        row = box.row()
        row.operator("measureit.addangle", text="Angle", icon="LINCURVE")
        row.operator("measureit.addarc", text="Arc")
        row.operator("measureit.addlabel", text="Label", icon="FONT_DATA")
        row.operator("measureit.addnote", text="Annotation")
        row.operator("measureit.addlink", text="Link")
        row.operator("measureit.addorigin", text="Origin")
        row.operator("measureit.addarea", text="Area", icon="MESH_GRID")

        # ------------------------------
        # Debug data
        # ------------------------------
        box = layout.box()
        row = box.row(align=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()
            split = row.split(factor=0.10, align=True)
            split.prop(scene, 'measureit_debug_obj_color', text="")
            split.prop(scene, "measureit_debug_objects", icon="OBJECT_DATA")
            split.prop(scene, "measureit_debug_object_loc", icon="EMPTY_DATA")

            split = row.split(factor=0.10, align=True)
            split.prop(scene, 'measureit_debug_vert_color', text="")
            split.prop(scene, "measureit_debug_vertices", icon="VERTEXSEL")
            split.prop(scene, "measureit_debug_vert_loc", icon="EMPTY_DATA")
            if scene.measureit_debug_vert_loc is True:
                split.prop(scene, 'measureit_debug_vert_loc_toggle', text="")

            split = row.split(factor=0.10, align=True)
            split.prop(scene, 'measureit_debug_edge_color', text="")
            split = split.split(factor=0.5, align=True)
            split.prop(scene, "measureit_debug_edges", icon="EDGESEL")

            row = box.row()
            split = row.split(factor=0.10, align=True)
            split.prop(scene, 'measureit_debug_face_color', text="")
            split = split.split(factor=0.5, align=True)
            split.prop(scene, "measureit_debug_faces", icon="FACESEL")

            split = row.split(factor=0.10, align=True)
            split.prop(scene, 'measureit_debug_norm_color', text="")
            if scene.measureit_debug_normals is False:
                split = split.split(factor=0.50, align=True)
                split.prop(scene, "measureit_debug_normals", icon="OBJECT_ORIGIN")
                split = split.split(factor=0.5, align=True)
                split.prop(scene, "measureit_debug_normals", icon="OBJECT_ORIGIN")
                split.prop(scene, "measureit_debug_normal_size")
                row = box.row()
                split = row.split(factor=0.10, align=True)
                split.separator()
                split.prop(scene, "measureit_debug_normal_details")
                split.prop(scene, 'measureit_debug_width', text="Thickness")

            row = box.row(align=True)
            row.prop(scene, "measureit_debug_select", icon="GHOST_ENABLED")
            row.prop(scene, 'measureit_debug_font', text="Font")
            row.prop(scene, 'measureit_debug_precision', text="Precision")


# ------------------------------------------------------------------
# Define panel class for conf functions.
# ------------------------------------------------------------------
class MEASUREIT_PT_Conf(Panel):
    bl_idname = "MEASUREIT_PT_Conf"
    bl_label = "Configuration"
    bl_region_type = 'UI'
meta-androcto's avatar
meta-androcto committed
    bl_category= 'View'
    bl_parent_id = 'MEASUREIT_PT_Main'
    bl_options = {'DEFAULT_CLOSED'}

    # ------------------------------
    # Draw UI
    # ------------------------------
    def draw(self, context):
        layout = self.layout
        scene = context.scene

        # Configuration data
        box = layout.box()
        row = box.row()
        split = row.split(factor=0.2, align=True)
        split.label(text="Text")
        split = split.split(factor=0.2, align=True)
        split.prop(scene, "measureit_default_color", text="")
        split.prop(scene, "measureit_gl_txt", text="")
        row = box.row(align=True)
        row.prop(scene, "measureit_hint_space")
        row.prop(scene, "measureit_font_align", text="")
        row = box.row(align=True)
        row.prop(scene, "measureit_glarrow_a", text="")
        row.prop(scene, "measureit_glarrow_b", text="")
        if scene.measureit_glarrow_a != '99' or scene.measureit_glarrow_b != '99':
            row.prop(scene, "measureit_glarrow_s", text="Size")
        row = box.row(align=True)
        row.prop(scene, "measureit_font_size")
        if scene.measureit_font_align == 'L':
            row.prop(scene, "measureit_font_rotation", text="Rotate")


# ------------------------------------------------------------------
# Define panel class for render functions.
# ------------------------------------------------------------------
class MEASUREIT_PT_Render(Panel):
    bl_idname = "MEASUREIT_PT_Render"
    bl_label = "Render"
    bl_region_type = 'UI'
    bl_category= 'Display'
    bl_parent_id = 'MEASUREIT_PT_Main'
    bl_options = {'DEFAULT_CLOSED'}

    # ------------------------------
    # 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.rendersegment", 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 that adds a measure segment
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddSegment(Operator):
    bl_idname = "measureit.addsegment"
    bl_label = "Add"
    bl_description = "(EDITMODE only) Add a new measure segment between 2 vertices (select 2 vertices or more)"

    # ------------------------------
    # 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
                        ms.glfont_align = scene.measureit_font_align
                        ms.glfont_rotat = scene.measureit_font_rotation
                        # 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 that adds an area measure
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddArea(Operator):
    bl_idname = "measureit.addarea"
    bl_label = "Area"
    bl_description = "(EDITMODE only) Add a new measure for area (select 1 o more faces)"

    # ------------------------------
    # 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
                ms.glfont_align = scene.measureit_font_align
                ms.glfont_rotat = scene.measureit_font_rotation
                # 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 that adds a measure segment to x/y/z origin
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddSegmentOrto(Operator):
    bl_idname = "measureit.addsegmentorto"
    bl_label = "Add"
    bl_description = "(EDITMODE only) Add a new measure segment from vertex to object origin for one " \
                     "axis (select 1 vertex)"
    tag: 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]
NBurn's avatar
NBurn committed
                for x in range(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
                        ms.glfont_align = scene.measureit_font_align
                        ms.glfont_rotat = scene.measureit_font_rotation
                        # 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 that adds an angle measure
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddAngle(Operator):
    bl_idname = "measureit.addangle"
    bl_label = "Angle"
    bl_description = "(EDITMODE only) Add a new angle measure (select 3 vertices, 2nd is angle vertex)"

    # ------------------------------
    # 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
                    ms.glfont_align = scene.measureit_font_align
                    ms.glfont_rotat = scene.measureit_font_rotation
                    # 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 that adds an arc measure
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddArc(Operator):
    bl_idname = "measureit.addarc"
    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)"

    # ------------------------------
    # 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
                    ms.glfont_align = scene.measureit_font_align
                    ms.glfont_rotat = scene.measureit_font_rotation
                    # 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 that adds a label segment
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddLabel(Operator):
    bl_idname = "measureit.addlabel"
    bl_label = "Add"
    bl_description = "(EDITMODE only) Add a new measure label (select 1 vertex)"

    # ------------------------------
    # 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
                    ms.glfont_align = scene.measureit_font_align
                    ms.glfont_rotat = scene.measureit_font_rotation
                    # 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 that adds a link
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddLink(Operator):
    bl_idname = "measureit.addlink"
    bl_label = "Add"
    bl_description = "(OBJECT mode only) Add a new measure between objects (select 2 " \
                     "objects and optionally 1 or 2 vertices)"

    # ------------------------------
    # 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 == "LIGHT":
                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
                ms.glfont_align = scene.measureit_font_align
                ms.glfont_rotat = scene.measureit_font_rotation
                # 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 that adds an origin segment
#
# -------------------------------------------------------------
class MEASUREIT_OT_AddOrigin(Operator):
    bl_idname = "measureit.addorigin"
    bl_label = "Add"
    bl_description = "(OBJECT mode only) Add a new measure to origin (select object and optionally 1 vertex)"

    # ------------------------------
    # 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 == "LIGHT":
                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
                ms.glfont_align = scene.measureit_font_align
                ms.glfont_rotat = scene.measureit_font_rotation
                # 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 that deletes a measure segment
#
# -------------------------------------------------------------
class MEASUREIT_OT_DeleteSegment(Operator):
    bl_idname = "measureit.deletesegment"
    bl_label = "Delete"
    bl_description = "Delete a measure"
    tag: 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 that deletes all measure segments
#
# -------------------------------------------------------------
class MEASUREIT_OT_DeleteAllSegment(Operator):
    bl_idname = "measureit.deleteallsegment"
    bl_label = "Delete"
    bl_description = "Delete all measures (it cannot be undone)"
    tag: 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 that deletes all measure segment sums
#
# -------------------------------------------------------------
class MEASUREIT_OT_DeleteAllSum(Operator):
    bl_idname = "measureit.deleteallsum"
    bl_label = "Delete"
    bl_description = "Delete all sum groups"
    tag: 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]
NBurn's avatar
NBurn committed
                for idx in range(mp.measureit_num):
                    ms = mp.measureit_segments[idx]
                    ms.gltot = '99'

            return {'FINISHED'}


# -------------------------------------------------------------
# Defines button that expands all measure segments
#
# -------------------------------------------------------------
class MEASUREIT_OT_ExpandAllSegment(Operator):
    bl_idname = "measureit.expandallsegment"
    bl_label = "Expand"
    bl_description = "Expand all measure properties"
    tag: IntProperty()

    # ------------------------------
    # Execute button action
    # ------------------------------
    def execute(self, context):
        if context.area.type == 'VIEW_3D':
            # Add properties
            mainobject = context.object
            mp = mainobject.MeasureGenerator[0]

            for i in mp.measureit_segments:
                i.gladvance = True

            return {'FINISHED'}
        else:
            self.report({'WARNING'},
                        "View3D not found, cannot run operator")

        return {'CANCELLED'}


# -------------------------------------------------------------
# Defines button that collapses all measure segments
#
# -------------------------------------------------------------
class MEASUREIT_OT_CollapseAllSegment(Operator):
    bl_idname = "measureit.collapseallsegment"
    bl_label = "Collapse"
    bl_description = "Collapses all measure properties"
    tag: IntProperty()

    # ------------------------------
    # Execute button action
    # ------------------------------
    def execute(self, context):
        if context.area.type == 'VIEW_3D':
            # Add properties
            mainobject = context.object
            mp = mainobject.MeasureGenerator[0]

            for i in mp.measureit_segments:
                i.gladvance = False

            return {'FINISHED'}
        else:
            self.report({'WARNING'},
                        "View3D not found, cannot run operator")

        return {'CANCELLED'}


# -------------------------------------------------------------
# Defines button for render option
#
# -------------------------------------------------------------
class MEASUREIT_OT_RenderSegment(Operator):
    bl_idname = "measureit.rendersegment"
    bl_label = "Render"
    bl_description = "Create a render image with measures. Use UV/Image editor to view image generated"
    tag: 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'}
        # -----------------------------
        # Frame render
        # -----------------------------
        if scene.measureit_render_type == "1":
            # noinspection PyBroadException
            if render_main(self, context) is True:
                self.report({'INFO'}, msg)
        # -----------------------------
        # Animation
        # -----------------------------
        if scene.measureit_render_type == "2":
            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)
                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 MEASUREIT_OT_AddNote(Operator):
    bl_idname = "measureit.addnote"
    bl_label = "Note"
    bl_description = "(OBJECT mode only) Add a new annotation"
    tag: 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_display_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
            ms.glfont_align = scene.measureit_font_align
            ms.glfont_rotat = scene.measureit_font_rotation
            # 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 that enables/disables the tip display
#
# -------------------------------------------------------------
class MEASUREIT_OT_RunHintDisplay(Operator):
    bl_idname = "measureit.runopengl"
    bl_label = "Display hint data manager"
    bl_description = "Main control for enabling or disabling the display of measurements in the viewport"

    _handle = None  # keep function handler

    # ----------------------------------
    # Enable gl drawing adding handler
    # ----------------------------------
    @staticmethod
    def handle_add(self, context):
        if MEASUREIT_OT_RunHintDisplay._handle is None:
            MEASUREIT_OT_RunHintDisplay._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context),
Antonioya's avatar
Antonioya committed
                                                                        'WINDOW',
                                                                        'POST_PIXEL')
            context.window_manager.measureit_run_opengl = True

    # ------------------------------------
    # Disable gl drawing removing handler
    # ------------------------------------
    # noinspection PyUnusedLocal
    @staticmethod
    def handle_remove(self, context):
        if MEASUREIT_OT_RunHintDisplay._handle is not None:
            SpaceView3D.draw_handler_remove(MEASUREIT_OT_RunHintDisplay._handle, 'WINDOW')
        MEASUREIT_OT_RunHintDisplay._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

    # Display selected or all
    if scene.measureit_gl_ghost is False:
        objlist = context.selected_objects
    else:
        objlist = context.view_layer.objects
    # Enable drawing
    gpu.state.blend_set('ALPHA')
    # ---------------------------------------
    # Generate all OpenGL calls for measures
    # ---------------------------------------
    for myobj in objlist:
        if myobj.visible_get() is True:
            if 'MeasureGenerator' in myobj:
                op = myobj.MeasureGenerator[0]
                draw_segments(context, myobj, op, region, rv3d)

    # ---------------------------------------
    # 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_objects is True:
                draw_object(context, myobj, region, rv3d)
            elif scene.measureit_debug_object_loc is True:
                draw_object(context, myobj, region, rv3d)
            if scene.measureit_debug_vertices is True:
                draw_vertices(context, myobj, region, rv3d)
            elif scene.measureit_debug_vert_loc is True:
                draw_vertices(context, myobj, region, rv3d)
            if scene.measureit_debug_edges is True:
                draw_edges(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 defaults
    gpu.state.blend_set('NONE')


# -------------------------------------------------------------
# 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.view_layer.objects.active = myobject
    flag = False
    if myobject.mode != 'EDIT':
        bpy.ops.object.mode_set(mode='EDIT')
        flag = True

    bm = from_edit_mesh(myobject.data)
    tv = len(bm.verts)
    for v in bm.verts:
        if v.select:
NBurn's avatar
NBurn committed
            mylist.append(v.index)

    if flag is True:
        bpy.ops.object.editmode_toggle()
    # Back context object
    bpy.context.view_layer.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.view_layer.objects.active = myobject
    flag = False
    if myobject.mode != 'EDIT':
        bpy.ops.object.mode_set(mode='EDIT')
        flag = True

    bm = from_edit_mesh(myobject.data)
NBurn's avatar
NBurn committed
        mylist.append(v.index)

    if flag is True:
        bpy.ops.object.editmode_toggle()
    # Back context object
    bpy.context.view_layer.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.view_layer.objects.active = myobject
    flag = False
    if myobject.mode != 'EDIT':
        bpy.ops.object.mode_set(mode='EDIT')
        flag = True

    bm = from_edit_mesh(myobject.data)
    for e in bm.edges:
        if e.select is True:
NBurn's avatar
NBurn committed
            mylist.append(e.verts[0].index)
            mylist.append(e.verts[1].index)

    if flag is True:
        bpy.ops.object.editmode_toggle()
    # Back context object
    bpy.context.view_layer.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.view_layer.objects.active = myobject
    flag = False
    if myobject.mode != 'EDIT':
        bpy.ops.object.mode_set(mode='EDIT')
        flag = True

    bm = from_edit_mesh(myobject.data)
NBurn's avatar
NBurn committed
        myfaces = []
NBurn's avatar
NBurn committed
            for i in range(len(e.verts)):
                myfaces.append(e.verts[i].index)
NBurn's avatar
NBurn committed
            mylist.extend([myfaces])

    if flag is True:
        bpy.ops.object.editmode_toggle()
    # Back context object
    bpy.context.view_layer.objects.active = oldobj