Update for API change: scene.cursor_location -> scene.cursor.location
[blender-addons-contrib.git] / space_view3d_panel_measure.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18 #
19 # Uses volume calculation and manifold check code (GPL2+) from:
20 # http://www.shapeways.com/forum/index.php?t=msg&goto=3639
21 # Shapeways Volume Calculator by Benjamin Lauritzen (Loonsbury)
22 #
23 # #################################
24
25 bl_info = {
26     "name": "Measure Panel",
27     "author": "Buerbaum Martin (Pontiac), TNae (Normal patch), "
28               "Benjamin Lauritzen (Loonsbury; Volume code), "
29               "Alessandro Sala (patch: Units in 3D View), "
30               "Daniel Ashby (callback removal code) ",
31     "version": (0, 9, 1),
32     "blender": (2, 74, 0),
33     "location": "View3D > Properties > Measure Panel",
34     "description": "Measure distances between objects",
35     "warning": "Disable during Render. Broken register",
36     "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
37     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
38                 "Scripts/3D_interaction/Panel_Measure",
39     "category": "3D View",
40 }
41
42 """
43 Measure panel
44
45 This script displays in OBJECT MODE:
46 * The distance of the 3D cursor to the origin of the
47   3D space (if NOTHING is selected).
48 * The distance of the 3D cursor to the center of an object
49   (if exactly ONE object is selected).
50 * The distance between 2 object centers
51   (if exactly TWO objects are selected).
52 * The surface area of any selected mesh object.
53 * The average normal of the mesh surface of any selected mesh object.
54 * The volume of any selected mesh object.
55
56 Display in EDIT MODE (Local and Global space supported):
57 * The distance of the 3D cursor to the origin
58   (in Local space it is the object center instead).
59 * The distance of the 3D cursor to a selected vertex.
60 * The distance between 2 selected vertices.
61
62 Usage:
63
64 This functionality can be accessed via the
65 "Properties" panel in 3D View ([N] key).
66
67 It's very helpful to use one or two "Empty" objects with
68 "Snap during transform" enabled for fast measurement.
69
70 More links:
71 http://gitorious.org/blender-scripts/blender-measure-panel-script
72 http://blenderartists.org/forum/showthread.php?t=177800
73 """
74
75 import bpy
76 from bpy.props import *
77 from bpy.app.handlers import persistent
78 from mathutils import Vector, Matrix
79 import bgl
80 import blf
81 from bpy_extras.view3d_utils import location_3d_to_region_2d
82 from bpy_extras.mesh_utils import ngon_tessellate
83
84
85 # Precicion for display of float values.
86 PRECISION = 5
87
88 # Name of the custom properties as stored in the scene.
89 COLOR_LOCAL = (1.0, 0.5, 0.0, 0.8)
90 COLOR_GLOBAL = (0.5, 0.0, 1.0, 0.8)
91
92 # 3D View - text offset
93 OFFSET_LINE = 10   # Offset the text a bit to the right.
94 OFFSET_Y = 15      # Offset of the lines.
95 OFFSET_VALUE = 30  # Offset of value(s) from the text.
96
97 # 3D View - line width
98 LINE_WIDTH_XYZ = 1
99 LINE_WIDTH_DIST = 2
100
101
102 # Returns a tuple describing the current measuring system
103 # and formatting options.
104 # Returned data is meant to be passed to formatDistance().
105 # Original by Alessandro Sala (Feb, 12th 2012)
106 # Update by Alessandro Sala (Dec, 18th 2012)
107 def getUnitsInfo():
108         scale = bpy.context.scene.unit_settings.scale_length
109         unit_system = bpy.context.scene.unit_settings.system
110         separate_units = bpy.context.scene.unit_settings.use_separate
111         if unit_system == 'METRIC':
112                 scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
113                     (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
114         elif unit_system == 'IMPERIAL':
115                 scale_steps = ((5280, 'mi'), (1, '\''),
116                     (1 / 12, '"'), (1 / 12000, 'thou'))
117                 scale /= 0.3048  # BU to feet
118         else:
119                 scale_steps = ((1, ' BU'),)
120                 separate_units = False
121
122         return (scale, scale_steps, separate_units)
123
124
125 # Converts a distance from BU into the measuring system
126 # described by units_info.
127 # Original by Alessandro Sala (Feb, 12th 2012)
128 # Update by Alessandro Sala (Dec, 18th 2012)
129 def convertDistance(val, units_info):
130         scale, scale_steps, separate_units = units_info
131         sval = val * scale
132         idx = 0
133         while idx < len(scale_steps) - 1:
134                 if sval >= scale_steps[idx][0]:
135                         break
136                 idx += 1
137         factor, suffix = scale_steps[idx]
138         sval /= factor
139         if not separate_units or idx == len(scale_steps) - 1:
140                 dval = str(round(sval, PRECISION)) + suffix
141         else:
142                 ival = int(sval)
143                 dval = str(round(ival, PRECISION)) + suffix
144                 fval = sval - ival
145                 idx += 1
146                 while idx < len(scale_steps):
147                         fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
148                         if fval >= 1:
149                                 dval += ' ' \
150                                     + ("%.1f" % fval) \
151                                     + scale_steps[idx][1]
152                                 break
153                         idx += 1
154
155         return dval
156
157
158 # Returns a single selected object.
159 # Returns None if more than one (or nothing) is selected.
160 # Note: Ignores the active object.
161 def getSingleObject():
162     if len(bpy.context.selected_objects) == 1:
163         return bpy.context.selected_objects[0]
164
165     return None
166
167
168 # Returns a list with 2 3D points (Vector) and a color (RGBA)
169 # depending on the current view mode and the selection.
170 def getMeasurePoints(context):
171     sce = context.scene
172     mode = context.mode
173
174     # Get a single selected object (or nothing).
175     obj = getSingleObject()
176
177     if mode == 'EDIT_MESH':
178         obj = context.active_object
179
180         if obj and obj.type == 'MESH' and obj.data:
181             # Get mesh data from Object.
182             mesh = obj.data
183
184             # Get the selected vertices.
185             # @todo: Better (more efficient) way to do this?
186             verts_selected = [v for v in mesh.vertices if v.select == 1]
187
188             if len(verts_selected) == 0:
189                 # Nothing selected.
190                 # We measure the distance from...
191                 # local  ... the object center to the 3D cursor.
192                 # global ... the origin to the 3D cursor.
193                 cur_loc = sce.cursor.location
194                 obj_loc = obj.matrix_world.to_translation()
195
196                 # Convert to local space, if needed.
197                 if measureLocal(sce):
198                     p1 = cur_loc
199                     p2 = obj_loc
200                     return (p1, p2, COLOR_GLOBAL)
201
202                 else:
203                     p1 = Vector((0.0, 0.0, 0.0))
204                     p2 = cur_loc
205                     return (p1, p2, COLOR_GLOBAL)
206
207             elif len(verts_selected) == 1:
208                 # One vertex selected.
209                 # We measure the distance from the
210                 # selected vertex object to the 3D cursor.
211                 cur_loc = sce.cursor.location
212                 vert_loc = verts_selected[0].co.copy()
213
214                 # Convert to local or global space.
215                 if measureLocal(sce):
216                     p1 = vert_loc
217                     p2 = cur_loc
218                     return (p1, p2, COLOR_LOCAL)
219
220                 else:
221                     p1 = obj.matrix_world * vert_loc
222                     p2 = cur_loc
223                     return (p1, p2, COLOR_GLOBAL)
224
225             elif len(verts_selected) == 2:
226                 # Two vertices selected.
227                 # We measure the distance between the
228                 # two selected vertices.
229                 obj_loc = obj.matrix_world.to_translation()
230                 vert1_loc = verts_selected[0].co.copy()
231                 vert2_loc = verts_selected[1].co.copy()
232
233                 # Convert to local or global space.
234                 if measureLocal(sce):
235                     p1 = vert1_loc
236                     p2 = vert2_loc
237                     return (p1, p2, COLOR_LOCAL)
238
239                 else:
240                     p1 = obj.matrix_world * vert1_loc
241                     p2 = obj.matrix_world * vert2_loc
242                     return (p1, p2, COLOR_GLOBAL)
243
244             else:
245                 return None
246
247     elif mode == 'OBJECT':
248         # We are working in object mode.
249
250         if len(context.selected_objects) > 2:
251             return None
252         elif len(context.selected_objects) == 2:
253             # 2 objects selected.
254             # We measure the distance between the 2 selected objects.
255             obj1, obj2 = context.selected_objects
256             obj1_loc = obj1.matrix_world.to_translation()
257             obj2_loc = obj2.matrix_world.to_translation()
258             return (obj1_loc, obj2_loc, COLOR_GLOBAL)
259
260         elif obj:
261             # One object selected.
262             # We measure the distance from the object to the 3D cursor.
263             cur_loc = sce.cursor.location
264             obj_loc = obj.matrix_world.to_translation()
265             return (obj_loc, cur_loc, COLOR_GLOBAL)
266
267         elif not context.selected_objects:
268             # Nothing selected.
269             # We measure the distance from the origin to the 3D cursor.
270             p1 = Vector((0.0, 0.0, 0.0))
271             p2 = sce.cursor.location
272             return (p1, p2, COLOR_GLOBAL)
273
274         else:
275             return None
276
277
278 # Return the length of an edge (in global space if "obj" is set).
279 # Respects the scaling (via the "obj.matrix_world" parameter).
280 def edgeLengthGlobal(edge, obj, globalSpace):
281     v1, v2 = edge.vertices
282
283     # Get vertex data
284     v1 = obj.data.vertices[v1]
285     v2 = obj.data.vertices[v2]
286
287     if globalSpace:
288         mat = obj.matrix_world
289         # Apply transform matrix to vertex coordinates.
290         v1 = mat * v1.co
291         v2 = mat * v2.co
292     else:
293         v1 = v1.co
294         v2 = v2.co
295
296     return (v1 - v2).length
297
298
299 # Calculate the edge length of a mesh object.
300 # *) Set selectedOnly=1 if you only want to count selected edges.
301 # *) Set globalSpace=1 if you want to calculate
302 #    the global edge length (object mode).
303 # Note: Be sure you have updated the mesh data before
304 #       running this with selectedOnly=1!
305 # @todo Support other object types (surfaces, etc...)?
306 def objectEdgeLength(obj, selectedOnly, globalSpace):
307     if obj and obj.type == 'MESH' and obj.data:
308         edgeTotal = 0
309
310         mesh = obj.data
311
312         # Count the length of all edges.
313         for ed in mesh.edges:
314             if not selectedOnly or ed.select:
315                 edgeTotal += edgeLengthGlobal(ed, obj, globalSpace)
316
317         return edgeTotal
318
319     # We can not calculate a length for this object.
320     return -1
321
322
323 # Return the area of a face (in global space).
324 # @note Copies the functionality of the following functions,
325 # but also respects the scaling (via the "obj.matrix_world" parameter):
326 # @sa: rna_mesh.c:rna_MeshTessFace_area_get
327 # @sa: math_geom.c:area_quad_v3
328 # @sa: math_geom.c:area_tri_v3
329 # @sa: math_geom.c:area_poly_v3
330 # @todo Fix calculation of "n" for n-gons?
331 def polyAreaGlobal(poly, obj):
332     mesh = obj.data
333     mat = obj.matrix_world.copy()
334     norm = poly.normal
335
336     area = 0.0
337
338     if len(poly.vertices) > 3:
339         # Tesselate the polygon into multiple tris
340         tris = ngon_tessellate(mesh, poly.vertices)
341
342         for tri in tris:
343             # Get vertex data
344             v1, v2, v3 = tri
345
346             # Get indices from original poly
347             v1 = poly.vertices[v1]
348             v2 = poly.vertices[v2]
349             v3 = poly.vertices[v3]
350
351             # Get vertex information from indices
352             v1 = mesh.vertices[v1]
353             v2 = mesh.vertices[v2]
354             v3 = mesh.vertices[v3]
355
356             # Apply transform matrix to vertex coordinates.
357             v1 = mat * v1.co
358             v2 = mat * v2.co
359             v3 = mat * v3.co
360
361             # Calculate area for the new tri
362             vec1 = v3 - v2
363             vec2 = v1 - v2
364
365             n = vec1.cross(vec2)
366
367             area += n.length / 2.0
368
369     elif len(poly.vertices) == 3:
370         # Triangle
371
372         # Get vertex indices
373         v1, v2, v3 = poly.vertices
374
375         # Get vertex data
376         v1 = mesh.vertices[v1]
377         v2 = mesh.vertices[v2]
378         v3 = mesh.vertices[v3]
379
380         # Apply transform matrix to vertex coordinates.
381         v1 = mat * v1.co
382         v2 = mat * v2.co
383         v3 = mat * v3.co
384
385         vec1 = v3 - v2
386         vec2 = v1 - v2
387
388         n = vec1.cross(vec2)
389
390         area = n.length / 2.0
391
392     # Apply rotation and scale to the normal as well.
393     rot_mat = obj.matrix_world.to_quaternion()
394     scale = obj.matrix_world.to_scale()
395     norm = rot_mat * norm
396     norm = Vector((
397         norm.x * scale.x,
398         norm.y * scale.y,
399         norm.z * scale.z)).normalized()
400
401     return area, norm
402
403
404 # Calculate the surface area of a mesh object.
405 # *) Set selectedOnly=1 if you only want to count selected faces.
406 # *) Set globalSpace=1 if you want to calculate
407 #    the global surface area (object mode).
408 # Note: Be sure you have updated the mesh data before
409 #       running this with selectedOnly=1!
410 # @todo Support other object types (surfaces, etc...)?
411 def objectSurfaceArea(obj, selectedOnly, globalSpace):
412     if obj and obj.type == 'MESH' and obj.data:
413         areaTotal = 0
414         normTotal = Vector((0.0, 0.0, 0.0))
415
416         mesh = obj.data
417
418         # Count the area of all the faces.
419         for poly in mesh.polygons:
420             if not selectedOnly or poly.select:
421                 if globalSpace:
422                     a, n = polyAreaGlobal(poly, obj)
423                     areaTotal += a
424                     normTotal += n
425                 else:
426                     areaTotal += poly.area
427                     normTotal += poly.normal
428
429         return areaTotal, normTotal
430
431     # We can not calculate an area for this object.
432     return -1, Vector((0.0, 0.0, 0.0))
433
434
435 # Calculate the volume of a mesh object.
436 # Copyright Loonsbury (loonsbury@yahoo.com)
437 def objectVolume(obj, globalSpace):
438     if obj and obj.type == 'MESH' and obj.data:
439
440         # Check if mesh is non-manifold
441         if not checkManifold(obj):
442             return -1
443
444         # Check if mesh has n-gons
445         if checkNgon(obj):
446             return -2
447
448         mesh = obj.data
449
450         volTot = 0
451
452         for poly in mesh.polygons:
453             fzn = poly.normal.z
454
455             if len(poly.vertices) == 4:
456                 v1, v2, v3, v4 = poly.vertices
457             else:
458                 v1, v2, v3 = poly.vertices
459
460             v1 = mesh.vertices[v1]
461             v2 = mesh.vertices[v2]
462             v3 = mesh.vertices[v3]
463
464             # Scaled vert coordinates with object XYZ offsets for
465             # selection extremes/sizing.
466             if globalSpace:
467                 x1 = v1.co[0] * obj.scale[0] + obj.location[0]
468                 y1 = v1.co[1] * obj.scale[1] + obj.location[1]
469                 z1 = v1.co[2] * obj.scale[2] + obj.location[2]
470
471                 x2 = v2.co[0] * obj.scale[0] + obj.location[0]
472                 y2 = v2.co[1] * obj.scale[1] + obj.location[1]
473                 z2 = v2.co[2] * obj.scale[2] + obj.location[2]
474
475                 x3 = v3.co[0] * obj.scale[0] + obj.location[0]
476                 y3 = v3.co[1] * obj.scale[1] + obj.location[1]
477                 z3 = v3.co[2] * obj.scale[2] + obj.location[2]
478
479             else:
480                 x1, y1, z1 = v1.co
481                 x2, y2, z2 = v2.co
482                 x3, y3, z3 = v3.co
483
484             pa = 0.5 * abs(
485                 (x1 * (y3 - y2))
486                 + (x2 * (y1 - y3))
487                 + (x3 * (y2 - y1)))
488             volume = ((z1 + z2 + z3) / 3.0) * pa
489
490             # Allowing for quads
491             if len(poly.vertices) == 4:
492                 # Get vertex data
493                 v4 = mesh.vertices[v4]
494
495                 if globalSpace:
496                     x4 = v4.co[0] * obj.scale[0] + obj.location[0]
497                     y4 = v4.co[1] * obj.scale[1] + obj.location[1]
498                     z4 = v4.co[2] * obj.scale[2] + obj.location[2]
499
500                 else:
501                     x4, y4, z4 = v4.co
502
503                 pa = 0.5 * abs(
504                     (x1 * (y4 - y3))
505                     + (x3 * (y1 - y4))
506                     + (x4 * (y3 - y1)))
507
508                 volume += ((z1 + z3 + z4) / 3.0) * pa
509
510             if fzn < 0:
511                 fzn = -1
512
513             elif fzn > 0:
514                 fzn = 1
515
516             else:
517                 fzn = 0
518
519             volTot += fzn * volume
520
521         return volTot
522
523 #    else:
524 #        print obj.name, ': Object must be a mesh!'        # TODO
525
526     return -3
527
528
529 # Manifold Checks
530 # Copyright Loonsbury (loonsbury@yahoo.com)
531 def checkManifold(obj):
532     if obj and obj.type == 'MESH' and obj.data:
533         mesh = obj.data
534
535         mc = dict([(ed.key, 0) for ed in mesh.edges])     # TODO
536
537         for p in mesh.polygons:
538             for ek in p.edge_keys:
539                 mc[ek] += 1
540                 if mc[ek] > 2:
541                     return 0
542
543         mt = [e[1] for e in mc.items()]
544         mt.sort()
545
546         if mt[0] < 2:
547             return 0
548
549         if mt[len(mt) - 1] > 2:
550             return 0
551
552         return 1
553
554     else:
555         return -1
556
557
558 # Check if a mesh has n-gons (polygon with more than 4 edges).
559 def checkNgon(obj):
560     if obj and obj.type == 'MESH' and obj.data:
561         mesh = obj.data
562
563         for p in mesh.polygons:
564             if len(p.vertices) > 4:
565                 return 1
566
567         return 0
568
569     else:
570         return -1
571
572
573 # User friendly access to the "space" setting.
574 def measureGlobal(sce):
575     return (sce.measure_panel_transform == "measure_global")
576
577
578 # User friendly access to the "space" setting.
579 def measureLocal(sce):
580     return (sce.measure_panel_transform == "measure_local")
581
582
583 # Calculate values if geometry, selection or cursor changed.
584 @persistent
585 def scene_update(context):
586     sce = context
587     mode = bpy.context.mode
588
589     if (mode == 'EDIT_MESH' and not sce.measure_panel_update):
590         return
591
592     if (bpy.data.objects.is_updated
593         or bpy.context.scene.is_updated
594         or sce.measure_panel_update):
595         # TODO: Better way to check selection changes and cursor changes?
596
597         sel_objs = bpy.context.selected_objects
598
599         # EDGE LENGTH
600         if sce.measure_panel_calc_edge_length:
601             if (mode == 'EDIT_MESH'
602                 and sce.measure_panel_update):
603                 sce.measure_panel_update = 0
604                 obj = bpy.context.object
605
606                 #if obj.is_updated:
607                 length_total = objectEdgeLength(obj, True,
608                     measureGlobal(sce))
609                 sce.measure_panel_edge_length = length_total
610
611             elif mode == 'OBJECT':
612                 length_total = -1
613
614                 for o in sel_objs:
615                     if o.type == 'MESH':
616                         length = objectEdgeLength(o, False, measureGlobal(sce))
617
618                         if length >= 0:
619                             if length_total < 0:
620                                 length_total = 0
621
622                             length_total += length
623
624                 sce.measure_panel_edge_length = length_total
625
626         # AREA
627         # Handle mesh surface area calulations
628         if sce.measure_panel_calc_area:
629             if (mode == 'EDIT_MESH'
630                 and sce.measure_panel_update):
631                 sce.measure_panel_update = 0
632                 obj = bpy.context.active_object
633
634                 if obj and obj.type == 'MESH' and obj.data:
635                     # "Note: a Mesh will return the selection state of the mesh
636                     # when EditMode was last exited. A Python script operating
637                     # in EditMode must exit EditMode before getting the current
638                     # selection state of the mesh."
639                     # http://www.blender.org/documentation/249PythonDoc/
640                     # /Mesh.MVert-class.html#sel
641                     # We can only provide this by existing &
642                     # re-entering EditMode.
643                     # @todo: Better way to do this?
644
645                     # Get mesh data from Object.
646                     me = obj.data
647
648                     # Get transformation matrix from object.
649                     ob_mat = obj.matrix_world
650                     # Also make an inversed copy! of the matrix.
651                     ob_mat_inv = ob_mat.copy()
652                     Matrix.invert(ob_mat_inv)
653
654                     # Get the selected vertices.
655                     # @todo: Better (more efficient) way to do this?
656                     verts_selected = [v for v in me.vertices if v.select == 1]
657
658                     if len(verts_selected) >= 3:
659                         # Get selected faces
660                         # @todo: Better (more efficient) way to do this?
661                         polys_selected = [p for p in me.polygons
662                             if p.select == 1]
663
664                         if len(polys_selected) > 0:
665                             area, normal = objectSurfaceArea(obj, True,
666                                 measureGlobal(sce))
667                             if area >= 0.0:
668                                 sce.measure_panel_area1 = area
669                                 sce.measure_panel_normal1 = normal
670
671             elif mode == 'OBJECT':
672                 # We are working in object mode.
673
674                 # Get a single selected object (or nothing).
675                 obj = getSingleObject()
676
677                 if len(sel_objs) > 2:
678                     return
679 # @todo Make this work again.
680 #                    # We have more that 2 objects selected...
681 #
682 #                    mesh_objects = [o for o in context.selected_objects
683 #                       if o.type == 'MESH']
684
685 #                    if len(mesh_objects) > 0:
686 #                        # ... and at least one of them is a mesh.
687 #
688 #                        for o in mesh_objects:
689 #                            area = objectSurfaceArea(o, False,
690 #                                measureGlobal(sce))
691 #                           if area >= 0:
692 #                               #row.label(text=o.name, icon='OBJECT_DATA')
693 #                               #row.label(text=str(round(area, PRECISION))
694 #                               #    + " BU^2")
695
696                 elif len(sel_objs) == 2:
697                     # 2 objects selected.
698
699                     obj1, obj2 = sel_objs
700
701                     # Calculate surface area of the objects.
702                     area1, normal1 = objectSurfaceArea(obj1, False,
703                         measureGlobal(sce))
704                     area2, normal2 = objectSurfaceArea(obj2, False,
705                         measureGlobal(sce))
706                     sce.measure_panel_area1 = area1
707                     sce.measure_panel_area2 = area2
708                     sce.measure_panel_normal1 = normal1
709                     sce.measure_panel_normal2 = normal2
710
711                 elif obj:
712                     # One object selected.
713
714                     # Calculate surface area of the object.
715                     area, normal = objectSurfaceArea(obj, False,
716                         measureGlobal(sce))
717
718                     sce.measure_panel_area1 = area
719                     sce.measure_panel_normal1 = normal
720
721         # VOLUME
722         # Handle mesh volume calulations.
723         if sce.measure_panel_calc_volume:
724             obj = getSingleObject()
725
726             if mode == 'OBJECT':
727                 # We are working in object mode.
728
729                 #if len(sel_objs) > 2:       # TODO
730                 #el
731                 if len(sel_objs) == 2:
732                     # 2 objects selected.
733
734                     obj1, obj2 = sel_objs
735
736                     # Calculate surface area of the objects.
737                     volume1 = objectVolume(obj1, measureGlobal(sce))
738                     volume2 = objectVolume(obj2, measureGlobal(sce))
739
740                     sce.measure_panel_volume1 = volume1
741                     sce.measure_panel_volume2 = volume2
742
743                 elif obj:
744                     # One object selected.
745
746                     # Calculate surface area of the object.
747                     volume1 = objectVolume(obj, measureGlobal(sce))
748
749                     sce.measure_panel_volume1 = volume1
750
751
752 def draw_measurements_callback(self, context):
753     sce = context.scene
754
755     draw = 0
756     if hasattr(sce, "measure_panel_draw"):
757         draw = sce.measure_panel_draw
758
759     # 2D drawing code example
760     #bgl.glBegin(bgl.GL_LINE_STRIP)
761     #bgl.glVertex2i(0, 0)
762     #bgl.glVertex2i(80, 100)
763     #bgl.glEnd()
764
765     # Get measured 3D points and colors.
766     line = getMeasurePoints(context)
767
768     if line:
769         p1, p2, color = line
770
771         dist = (p1 - p2).length
772
773         # Write distance value into the scene property,
774         # so we can display it in the panel & refresh the panel.
775         if hasattr(sce, "measure_panel_dist"):
776             sce.measure_panel_dist = dist
777             context.area.tag_redraw()
778
779         if draw:
780             # Get & convert the Perspective Matrix of the current view/region.
781             view3d = bpy.context
782             region = view3d.region_data
783             perspMatrix = region.perspective_matrix
784             tempMat = [perspMatrix[j][i] for i in range(4) for j in range(4)]
785             perspBuff = bgl.Buffer(bgl.GL_FLOAT, 16, tempMat)
786
787             # ---
788             # Store previous OpenGL settings.
789             # Store MatrixMode
790             MatrixMode_prev = bgl.Buffer(bgl.GL_INT, [1])
791             bgl.glGetIntegerv(bgl.GL_MATRIX_MODE, MatrixMode_prev)
792             MatrixMode_prev = MatrixMode_prev[0]
793
794             # Store projection matrix
795             ProjMatrix_prev = bgl.Buffer(bgl.GL_DOUBLE, [16])
796             bgl.glGetFloatv(bgl.GL_PROJECTION_MATRIX, ProjMatrix_prev)
797
798             # Store Line width
799             lineWidth_prev = bgl.Buffer(bgl.GL_FLOAT, [1])
800             bgl.glGetFloatv(bgl.GL_LINE_WIDTH, lineWidth_prev)
801             lineWidth_prev = lineWidth_prev[0]
802
803             # Store GL_BLEND
804             blend_prev = bgl.Buffer(bgl.GL_BYTE, [1])
805             bgl.glGetFloatv(bgl.GL_BLEND, blend_prev)
806             blend_prev = blend_prev[0]
807
808             line_stipple_prev = bgl.Buffer(bgl.GL_BYTE, [1])
809             bgl.glGetFloatv(bgl.GL_LINE_STIPPLE, line_stipple_prev)
810             line_stipple_prev = line_stipple_prev[0]
811
812             # Store glColor4f
813             color_prev = bgl.Buffer(bgl.GL_FLOAT, [4])
814             bgl.glGetFloatv(bgl.GL_COLOR, color_prev)
815
816             # ---
817             # Prepare for 3D drawing
818             bgl.glLoadIdentity()
819             bgl.glMatrixMode(bgl.GL_PROJECTION)
820             bgl.glLoadMatrixf(perspBuff)
821
822             bgl.glEnable(bgl.GL_BLEND)
823             bgl.glEnable(bgl.GL_LINE_STIPPLE)
824
825             # ---
826             # Draw 3D stuff.
827             bgl.glLineWidth(LINE_WIDTH_XYZ)
828             # X
829             bgl.glColor4f(1, 0, 0, 0.8)
830             bgl.glBegin(bgl.GL_LINE_STRIP)
831             bgl.glVertex3f(p1[0], p1[1], p1[2])
832             bgl.glVertex3f(p2[0], p1[1], p1[2])
833             bgl.glEnd()
834             # Y
835             bgl.glColor4f(0, 1, 0, 0.8)
836             bgl.glBegin(bgl.GL_LINE_STRIP)
837             bgl.glVertex3f(p1[0], p1[1], p1[2])
838             bgl.glVertex3f(p1[0], p2[1], p1[2])
839             bgl.glEnd()
840             # Z
841             bgl.glColor4f(0, 0, 1, 0.8)
842             bgl.glBegin(bgl.GL_LINE_STRIP)
843             bgl.glVertex3f(p1[0], p1[1], p1[2])
844             bgl.glVertex3f(p1[0], p1[1], p2[2])
845             bgl.glEnd()
846
847             # Dist
848             bgl.glLineWidth(LINE_WIDTH_DIST)
849             bgl.glColor4f(color[0], color[1], color[2], color[3])
850             bgl.glBegin(bgl.GL_LINE_STRIP)
851             bgl.glVertex3f(p1[0], p1[1], p1[2])
852             bgl.glVertex3f(p2[0], p2[1], p2[2])
853             bgl.glEnd()
854
855             # ---
856             # Restore previous OpenGL settings
857             bgl.glLoadIdentity()
858             bgl.glMatrixMode(MatrixMode_prev)
859             bgl.glLoadMatrixf(ProjMatrix_prev)
860             bgl.glLineWidth(lineWidth_prev)
861             if not blend_prev:
862                 bgl.glDisable(bgl.GL_BLEND)
863             if not line_stipple_prev:
864                 bgl.glDisable(bgl.GL_LINE_STIPPLE)
865             bgl.glColor4f(
866                 color_prev[0],
867                 color_prev[1],
868                 color_prev[2],
869                 color_prev[3])
870
871             # ---
872             # Draw (2D) text
873             # We do this after drawing the lines so
874             # we can draw it OVER the line.
875             coord_2d = location_3d_to_region_2d(
876                 context.region,
877                 context.space_data.region_3d,
878                 p1.lerp(p2, 0.5))
879
880             texts = [
881                 ("Dist:", dist),
882                 ("X:", abs(p1[0] - p2[0])),
883                 ("Y:", abs(p1[1] - p2[1])),
884                 ("Z:", abs(p1[2] - p2[2]))]
885
886             # Draw all texts
887             # @todo Get user pref for text color in 3D View
888             bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
889             blf.size(0, 12, 72)  # Prevent font size to randomly change.
890
891             uinfo = getUnitsInfo()
892
893             loc_x = coord_2d[0] + OFFSET_LINE
894             loc_y = coord_2d[1]
895
896             for t in texts:
897                 text = t[0]
898
899                 value = convertDistance(t[1], uinfo)
900
901                 blf.position(0, loc_x, loc_y, 0)
902                 blf.draw(0, text)
903                 blf.position(0, loc_x + OFFSET_VALUE, loc_y, 0)
904                 blf.draw(0, value)
905
906                 loc_y -= OFFSET_Y
907
908
909 # Callback code Daniel Ashby 2014-10-30
910 class VIEW3D_OT_display_measurements(bpy.types.Operator):
911     """Display the measurements made in the 'Measure' panel"""
912     bl_idname = "view3d.display_measurements"
913     bl_label = "Display measurements"
914     bl_description = "Display the measurements made in the" \
915         " 'Measure' panel in the 3D View"
916     bl_options = {'REGISTER'}  # TODO: can this be removed?
917     _handle = None
918
919     @staticmethod
920     def handle_add(self, context):
921         VIEW3D_OT_display_measurements._handle \
922         = bpy.types.SpaceView3D.draw_handler_add(
923                 draw_measurements_callback,
924                 (self, context),
925                 'WINDOW', 'POST_PIXEL')
926
927     @staticmethod
928     def handle_remove(context):
929         if VIEW3D_OT_display_measurements._handle is not None:
930             bpy.types.SpaceView3D.draw_handler_remove(
931                 VIEW3D_OT_display_measurements._handle,
932                 'WINDOW')
933         VIEW3D_OT_display_measurements._handle = None
934
935     def modal(self, context, event):
936         if context.area:
937             context.area.tag_redraw
938
939         if not context.window_manager.display_measurements_runstate:
940             #stop script
941             VIEW3D_OT_display_measurements.handle_remove(context)
942             return {'CANCELLED'}
943
944         return {'PASS_THROUGH'}
945
946     def cancel(self, context):
947         if context.window_manager.display_measurements_runstate:
948             display_measurements.handle_remove(context)
949             context.window_manager.display_measurements_runstate = False
950         return {'CANCELLED'}
951
952     def invoke(self, context, event):
953         if context.area.type == 'VIEW_3D':
954             if context.window_manager.display_measurements_runstate is False:
955                 # operator is called for the first time, start everything
956                 context.window_manager.display_measurements_runstate = True
957                 VIEW3D_OT_display_measurements.handle_add(self, context)
958                 context.window_manager.modal_handler_add(self)
959                 return {'RUNNING_MODAL'}
960
961             else:
962                 # operator is called again, stop displaying
963                 context.window_manager.display_measurements_runstate = False
964                 return {'CANCELLED'}
965
966         else:
967             self.report({'WARNING'}, "3D View not found, can't run operator"
968                 " for 'Display measurements'")
969             return {'CANCELLED'}
970
971
972 class VIEW3D_OT_activate_measure_panel(bpy.types.Operator):
973     bl_label = "Activate"
974     bl_idname = "view3d.activate_measure_panel"
975     bl_description = "Activate the callback needed to draw the lines"
976     bl_options = {'REGISTER'}
977
978     def invoke(self, context, event):
979
980         # Execute operator (this adds the callback)
981         # if it wasn't done yet.
982         bpy.ops.view3d.display_measurements()
983         return {'FINISHED'}
984
985
986 class VIEW3D_OT_reenter_editmode(bpy.types.Operator):
987     bl_label = "Re-enter EditMode"
988     bl_idname = "view3d.reenter_editmode"
989     bl_description = "Update mesh data of an active mesh object " \
990         "(this is done by exiting and re-entering mesh edit mode)"
991     bl_options = {'REGISTER'}
992
993     def invoke(self, context, event):
994
995         # Get the active object.
996         obj = context.active_object
997         sce = context.scene
998
999         if obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH':
1000             # Exit and re-enter mesh EditMode.
1001             bpy.ops.object.mode_set(mode='OBJECT')
1002             bpy.ops.object.mode_set(mode='EDIT')
1003             sce.measure_panel_update = 1
1004             return {'FINISHED'}
1005
1006         return {'CANCELLED'}
1007
1008
1009 class VIEW3D_PT_measure(bpy.types.Panel):
1010     bl_space_type = 'VIEW_3D'
1011     bl_region_type = 'UI'
1012     bl_label = "Measure"
1013     bl_options = {'DEFAULT_CLOSED'}
1014
1015     @classmethod
1016     def poll(cls, context):
1017         # Only display this panel in the object and edit mode 3D view.
1018         mode = context.mode
1019         if (context.area.type == 'VIEW_3D' and
1020             (mode == 'EDIT_MESH' or mode == 'OBJECT')):
1021             return 1
1022
1023         return 0
1024
1025     def draw_header(self, context):
1026         layout = self.layout
1027         sce = context.scene
1028
1029         if not context.window_manager.display_measurements_runstate:
1030             layout.operator("view3d.display_measurements", text="Activate",
1031                 icon="PLAY")
1032
1033     def draw(self, context):
1034         layout = self.layout
1035         sce = context.scene
1036         mode = context.mode
1037
1038         # Get a single selected object (or nothing).
1039         obj = getSingleObject()
1040
1041         drawTansformButtons = 1
1042
1043         if mode == 'EDIT_MESH':
1044             obj = context.active_object
1045
1046             row = layout.row()
1047             row.operator("view3d.reenter_editmode",
1048                 text="Update selection")
1049 #               @todo
1050 #                description="The calculated values can" \
1051 #                    " not be updated in mesh edit mode" \
1052 #                    " automatically. Press this button" \
1053 #                    " to do this manually, after you changed" \
1054 #                    " the selection")
1055
1056             if obj and obj.type == 'MESH' and obj.data:
1057                 # "Note: a Mesh will return the selection state of the mesh
1058                 # when EditMode was last exited. A Python script operating
1059                 # in EditMode must exit EditMode before getting the current
1060                 # selection state of the mesh."
1061                 # http://www.blender.org/documentation/249PythonDoc/
1062                 # /Mesh.MVert-class.html#sel
1063                 # We can only provide this by existing & re-entering EditMode.
1064                 # @todo: Better way to do this?
1065
1066                 # Get mesh data from Object.
1067                 mesh = obj.data
1068
1069                 # Get transformation matrix from object.
1070                 ob_mat = obj.matrix_world
1071                 # Also make an inversed copy! of the matrix.
1072                 ob_mat_inv = ob_mat.copy()
1073                 Matrix.invert(ob_mat_inv)
1074
1075                 # Get the selected vertices.
1076                 # @todo: Better (more efficient) way to do this?
1077                 verts_selected = [v for v in mesh.vertices if v.select == 1]
1078
1079                 if len(verts_selected) == 0:
1080                     # Nothing selected.
1081                     # We measure the distance from...
1082                     # local  ... the object center to the 3D cursor.
1083                     # global ... the origin to the 3D cursor.
1084                     layout.label(text="Distance")
1085
1086                     box = layout.box()
1087                     row = box.row()
1088                     row.prop(sce, "measure_panel_dist")
1089
1090                     row = box.row()
1091                     row.label(text="", icon='PIVOT_CURSOR')
1092                     row.label(text="", icon='ARROW_LEFTRIGHT')
1093                     if measureLocal(sce):
1094                         row.label(text="Obj. Center")
1095                     else:
1096                         row.label(text="Origin [0,0,0]")
1097
1098                     layout.prop(sce, "measure_panel_draw")
1099
1100                 elif len(verts_selected) == 1:
1101                     # One vertex selected.
1102                     # We measure the distance from the
1103                     # selected vertex object to the 3D cursor.
1104                     layout.label(text="Distance")
1105
1106                     box = layout.box()
1107                     row = box.row()
1108                     row.prop(sce, "measure_panel_dist")
1109
1110                     row = box.row()
1111                     row.label(text="", icon='PIVOT_CURSOR')
1112                     row.label(text="", icon='ARROW_LEFTRIGHT')
1113                     row.label(text="", icon='VERTEXSEL')
1114
1115                     layout.prop(sce, "measure_panel_draw")
1116
1117                 elif len(verts_selected) == 2:
1118                     # Two vertices selected.
1119                     # We measure the distance between the
1120                     # two selected vertices.
1121                     layout.label(text="Distance")
1122
1123                     box = layout.box()
1124                     row = box.row()
1125                     row.prop(sce, "measure_panel_dist")
1126
1127                     row = box.row()
1128                     row.label(text="", icon='VERTEXSEL')
1129                     row.label(text="", icon='ARROW_LEFTRIGHT')
1130                     row.label(text="", icon='VERTEXSEL')
1131
1132                     layout.prop(sce, "measure_panel_draw")
1133
1134                 edges_selected = [ed for ed in mesh.edges if ed.select == 1]
1135                 if len(edges_selected) >= 1:
1136                     row = layout.row()
1137                     row.prop(sce, "measure_panel_calc_edge_length",
1138                         text="Edge Length (selected edges)")
1139
1140                     if sce.measure_panel_calc_edge_length:
1141                         if sce.measure_panel_edge_length >= 0:
1142                             box = layout.box()
1143                             row = box.row()
1144                             row.label(
1145                                 text=str(len(edges_selected)),
1146                                 icon='EDGESEL')
1147
1148                             row = box.row()
1149                             row.label(text="Length")
1150                             row.prop(sce, "measure_panel_edge_length")
1151
1152                 if len(verts_selected) > 2:
1153                     row = layout.row()
1154                     row.prop(sce, "measure_panel_calc_area",
1155                         text="Surface area (selected faces)")
1156
1157                     if sce.measure_panel_calc_area:
1158                         # Get selected faces
1159                         # @todo: Better (more efficient) way to do this?
1160                         polys_selected = [p for p in mesh.polygons
1161                             if p.select == 1]
1162
1163                         if len(polys_selected) > 0:
1164                             if sce.measure_panel_area1 >= 0:
1165                                 box = layout.box()
1166                                 row = box.row()
1167                                 row.label(
1168                                     text=str(len(polys_selected)),
1169                                     icon='FACESEL')
1170
1171                                 row = box.row()
1172                                 row.label(text="Area")
1173                                 row.prop(sce, "measure_panel_area1")
1174
1175                                 row = box.row()
1176                                 row.label(text="Normal")
1177                                 row = box.row()
1178                                 row.prop(sce, "measure_panel_normal1")
1179
1180                         else:
1181                             row = layout.row()
1182                             row.label(text="Selection not supported",
1183                                 icon='INFO')
1184
1185                 if drawTansformButtons:
1186                     row = layout.row()
1187                     row.prop(sce,
1188                         "measure_panel_transform",
1189                         expand=True)
1190
1191         elif mode == 'OBJECT':
1192             # We are working in object mode.
1193
1194             mesh_objects = [o for o in context.selected_objects
1195                         if o.type == 'MESH']
1196
1197             if len(context.selected_objects) > 2:
1198                 # We have more that 2 objects selected...
1199
1200                 # EDGES
1201                 row = layout.row()
1202                 row.prop(sce, "measure_panel_calc_edge_length",
1203                     text="Edge Length")
1204
1205                 if sce.measure_panel_calc_edge_length:
1206                     if len(mesh_objects) > 0:
1207                         box = layout.box()
1208
1209                         row = box.row()
1210                         row.label(text="Total edge length")
1211                         row.prop(sce, "measure_panel_edge_length")
1212
1213                 # AREA
1214                 row = layout.row()
1215                 row.prop(sce, "measure_panel_calc_area",
1216                         text="Surface area")
1217
1218                 if sce.measure_panel_calc_area:
1219                     if len(mesh_objects) > 0:
1220                         # ... and at least one of them is a mesh.
1221
1222                         # Calculate and display surface area of the objects.
1223                         # @todo: Convert to scene units! We do not have a
1224                         # FloatProperty field here for automatic conversion.
1225
1226                         row = layout.row()
1227                         row.label(text="Multiple objects not yet supported",
1228                             icon='INFO')
1229                         row = layout.row()
1230                         row.label(text="(= More than two meshes)",
1231                             icon='INFO')
1232 # @todo Make this work again.
1233 #                        for o in mesh_objects:
1234 #                            area = objectSurfaceArea(o, False,
1235 #                                measureGlobal(sce))
1236 #                            if area >= 0:
1237 #                                row = layout.row()
1238 #                                row.label(text=o.name, icon='OBJECT_DATA')
1239 #                                row.label(text=str(round(area, PRECISION))
1240 #                                    + " BU^2")
1241
1242             elif len(context.selected_objects) == 2:
1243                 # 2 objects selected.
1244                 # We measure the distance between the 2 selected objects.
1245                 layout.label(text="Distance")
1246
1247                 obj1, obj2 = context.selected_objects
1248
1249                 box = layout.box()
1250                 row = box.row()
1251                 row.prop(sce, "measure_panel_dist")
1252
1253                 row = box.row()
1254                 row.label(text="", icon='OBJECT_DATA')
1255                 row.prop(obj1, "name", text="")
1256
1257                 row.label(text="", icon='ARROW_LEFTRIGHT')
1258
1259                 row.label(text="", icon='OBJECT_DATA')
1260                 row.prop(obj2, "name", text="")
1261
1262                 layout.prop(sce, "measure_panel_draw")
1263
1264                 # EDGES
1265                 row = layout.row()
1266                 row.prop(sce, "measure_panel_calc_edge_length",
1267                     text="Edge Length")
1268
1269                 if sce.measure_panel_calc_edge_length:
1270                     if sce.measure_panel_edge_length >= 0:
1271                         if len(mesh_objects) > 0:
1272                             box = layout.box()
1273
1274                             row = box.row()
1275                             row.label(text="Total edge length")
1276                             row.prop(sce, "measure_panel_edge_length")
1277
1278                 # AREA
1279
1280                 row = layout.row()
1281                 row.prop(sce, "measure_panel_calc_area",
1282                     text="Surface area")
1283
1284                 if sce.measure_panel_calc_area:
1285                     # Display surface area of the objects.
1286                     if (sce.measure_panel_area1 >= 0
1287                     or sce.measure_panel_area2 >= 0):
1288                         if sce.measure_panel_area1 >= 0:
1289                             box = layout.box()
1290                             row = box.row()
1291                             row.label(text=obj1.name, icon='OBJECT_DATA')
1292
1293                             row = box.row()
1294                             row.label(text="Area")
1295                             row.prop(sce, "measure_panel_area1")
1296
1297                             row = box.row()
1298                             row.label(text="Normal")
1299                             row = box.row()
1300                             row.prop(sce, "measure_panel_normal1")
1301
1302                         if sce.measure_panel_area2 >= 0:
1303                             box = layout.box()
1304                             row = box.row()
1305                             row.label(text=obj2.name, icon='OBJECT_DATA')
1306
1307                             row = box.row()
1308                             row.label(text="Area")
1309                             row.prop(sce, "measure_panel_area2")
1310
1311                             row = box.row()
1312                             row.label(text="Normal")
1313                             row = box.row()
1314                             row.prop(sce, "measure_panel_normal2")
1315
1316                 # VOL
1317                 row = layout.row()
1318                 row.prop(sce, "measure_panel_calc_volume",
1319                     text="Volume")
1320
1321                 if sce.measure_panel_calc_volume:
1322                     # Display volume of the objects.
1323                     if sce.measure_panel_volume1 >= -2:
1324                         box = layout.box()
1325                         row = box.row()
1326                         row.label(text=obj1.name, icon='OBJECT_DATA')
1327
1328                         if sce.measure_panel_volume1 >= 0:
1329                             row = box.row()
1330                             row.label(text="Volume")
1331                             row.prop(sce, "measure_panel_volume1")
1332                         elif sce.measure_panel_volume1 >= -1:
1333                             row = box.row()
1334                             row.label(text="Mesh is non-manifold!",
1335                                 icon='INFO')
1336                         else:  # -2
1337                             row = box.row()
1338                             row.label(text="Mesh has n-gons (faces with " \
1339                                 "more than 4 edges)!",
1340                                 icon='INFO')
1341
1342                     if sce.measure_panel_volume2 >= -2:
1343                         box = layout.box()
1344                         row = box.row()
1345                         row.label(text=obj2.name, icon='OBJECT_DATA')
1346
1347                         if sce.measure_panel_volume2 >= 0:
1348                             row = box.row()
1349                             row.label(text="Volume")
1350                             row.prop(sce, "measure_panel_volume2")
1351                         elif sce.measure_panel_volume2 >= -1:
1352                             row = box.row()
1353                             row.label(text="Mesh is non-manifold!",
1354                                 icon='INFO')
1355                         else:  # -2
1356                             row = box.row()
1357                             row.label(text="Mesh has n-gons (faces with " \
1358                                 "more than 4 edges)!",
1359                                 icon='INFO')
1360
1361             elif obj:
1362                 # One object selected.
1363                 # We measure the distance from the object to the 3D cursor.
1364                 layout.label(text="Distance")
1365
1366                 box = layout.box()
1367                 row = box.row()
1368                 row.prop(sce, "measure_panel_dist")
1369
1370                 row = box.row()
1371                 row.label(text="", icon='PIVOT_CURSOR')
1372
1373                 row.label(text="", icon='ARROW_LEFTRIGHT')
1374
1375                 row.label(text="", icon='OBJECT_DATA')
1376                 row.prop(obj, "name", text="")
1377
1378                 layout.prop(sce, "measure_panel_draw")
1379
1380                 # EDGES
1381                 row = layout.row()
1382                 row.prop(sce, "measure_panel_calc_edge_length",
1383                     text="Edge Length")
1384
1385                 if sce.measure_panel_calc_edge_length:
1386                     if sce.measure_panel_edge_length >= 0:
1387                         if len(mesh_objects) > 0:
1388                             box = layout.box()
1389
1390                             row = box.row()
1391                             row.label(text="Total edge length")
1392                             row.prop(sce, "measure_panel_edge_length")
1393
1394                 # AREA
1395                 row = layout.row()
1396                 row.prop(sce, "measure_panel_calc_area",
1397                     text="Surface area")
1398
1399                 if sce.measure_panel_calc_area:
1400                     # Display surface area of the object.
1401
1402                     if sce.measure_panel_area1 >= 0.0:
1403                         box = layout.box()
1404                         row = box.row()
1405                         row.label(text=obj.name, icon='OBJECT_DATA')
1406
1407                         row = box.row()
1408                         row.label(text="Area")
1409                         row.prop(sce, "measure_panel_area1")
1410
1411                         row = box.row()
1412                         row.label(text="Normal")
1413                         row = box.row()
1414                         row.prop(sce, "measure_panel_normal1")
1415
1416                 # VOL
1417                 row = layout.row()
1418                 row.prop(sce, "measure_panel_calc_volume",
1419                     text="Volume")
1420
1421                 if sce.measure_panel_calc_volume:
1422                     # Display volume of the objects.
1423                     if sce.measure_panel_volume1 >= -2:
1424                         box = layout.box()
1425                         row = box.row()
1426                         row.label(text=obj.name, icon='OBJECT_DATA')
1427
1428                         if sce.measure_panel_volume1 >= 0:
1429                             row = box.row()
1430                             row.label(text="Volume")
1431                             row.prop(sce, "measure_panel_volume1")
1432                         elif sce.measure_panel_volume1 >= -1:
1433                             row = box.row()
1434                             row.label(text="Mesh is non-manifold!",
1435                                 icon='INFO')
1436                         else:  # -2
1437                             row = box.row()
1438                             row.label(text="Mesh has n-gons (faces with " \
1439                                 "more than 4 edges)!",
1440                                 icon='INFO')
1441
1442             elif not context.selected_objects:
1443                 # Nothing selected.
1444                 # We measure the distance from the origin to the 3D cursor.
1445                 layout.label(text="Distance")
1446
1447                 box = layout.box()
1448                 row = box.row()
1449                 row.prop(sce, "measure_panel_dist")
1450
1451                 row = box.row()
1452                 row.label(text="", icon='PIVOT_CURSOR')
1453                 row.label(text="", icon='ARROW_LEFTRIGHT')
1454                 row.label(text="Origin [0,0,0]")
1455
1456                 layout.prop(sce, "measure_panel_draw")
1457
1458             else:
1459                 row = layout.row()
1460                 row.label(text="Selection not supported",
1461                     icon='INFO')
1462
1463             if drawTansformButtons:
1464                 row = layout.row()
1465                 row.prop(sce,
1466                     "measure_panel_transform",
1467                     expand=True)
1468
1469 classes = (
1470     VIEW3D_OT_display_measurements,
1471     VIEW3D_OT_reenter_editmode,
1472     VIEW3D_PT_measure)
1473
1474
1475 def register():
1476     bpy.app.handlers.scene_update_post.append(scene_update)
1477
1478     # Define a temporary attribute for the distance value
1479     bpy.types.Scene.measure_panel_dist = bpy.props.FloatProperty(
1480         name="Distance",
1481         precision=PRECISION,
1482         unit="LENGTH")
1483     bpy.types.Scene.measure_panel_edge_length = bpy.props.FloatProperty(
1484         name="",
1485         precision=PRECISION,
1486         unit="LENGTH")
1487     bpy.types.Scene.measure_panel_area1 = bpy.props.FloatProperty(
1488         name="",
1489         precision=PRECISION,
1490         unit="AREA")
1491     bpy.types.Scene.measure_panel_area2 = bpy.props.FloatProperty(
1492         name="",
1493         precision=PRECISION,
1494         unit="AREA")
1495     bpy.types.Scene.measure_panel_normal1 = bpy.props.FloatVectorProperty(
1496         name="",
1497         precision=PRECISION,
1498         subtype="XYZ")
1499     bpy.types.Scene.measure_panel_normal2 = bpy.props.FloatVectorProperty(
1500         name="",
1501         precision=PRECISION,
1502         subtype="XYZ")
1503     bpy.types.Scene.measure_panel_volume1 = bpy.props.FloatProperty(
1504         name="",
1505         precision=PRECISION,
1506         unit="VOLUME")
1507     bpy.types.Scene.measure_panel_volume2 = bpy.props.FloatProperty(
1508         name="",
1509         precision=PRECISION,
1510         unit="VOLUME")
1511
1512     TRANSFORM = [
1513         ("measure_global", "Global",
1514          "Calculate values in global space"),
1515         ("measure_local", "Local",
1516          "Calculate values inside the local object space")]
1517
1518     # Define dropdown for the global/local setting
1519     bpy.types.Scene.measure_panel_transform = bpy.props.EnumProperty(
1520         name="Space",
1521         description="Choose in which space you want to measure",
1522         items=TRANSFORM,
1523         default='measure_global')
1524
1525     # Define property for the draw setting.
1526     bpy.types.Scene.measure_panel_draw = bpy.props.BoolProperty(
1527         name="Draw distance",
1528         description="Draw distances in 3D View",
1529         default=1)
1530
1531     bpy.types.Scene.measure_panel_calc_edge_length = bpy.props.BoolProperty(
1532         description="Calculate total length of (selected) edges",
1533         default=0)
1534
1535     # Define property for the calc-area setting.
1536     # @todo prevent double calculations for each refresh automatically?
1537     bpy.types.Scene.measure_panel_calc_area = bpy.props.BoolProperty(
1538         description="Calculate mesh surface area (heavy CPU "
1539                     "usage on bigger meshes)",
1540         default=0)
1541
1542     # Define property for the calc-volume setting.
1543     bpy.types.Scene.measure_panel_calc_volume = bpy.props.BoolProperty(
1544         description="Calculate mesh volume (heavy CPU "
1545                     "usage on bigger meshes)",
1546         default=0)
1547
1548      # Define dropdown for the global/local setting
1549     bpy.types.Scene.measure_panel_update = bpy.props.BoolProperty(
1550         description="Update CPU heavy calculations",
1551         default=0)
1552
1553     # Callback code Daniel Ashby 2014-10-30
1554     # Runstate initially always set to False
1555     # note: it is not stored in the Scene, but in window manager:
1556     wm = bpy.types.WindowManager
1557     wm.display_measurements_runstate = bpy.props.BoolProperty(default=False)
1558
1559     for c in classes:
1560         bpy.utils.register_class(c)
1561
1562
1563 def unregister():
1564     bpy.app.handlers.scene_update_post.remove(scene_update)
1565
1566     VIEW3D_OT_display_measurements.handle_remove(bpy.context)
1567
1568     for c in classes:
1569         bpy.utils.unregister_class(c)
1570
1571     # Remove properties.
1572     del bpy.types.Scene.measure_panel_dist
1573     del bpy.types.Scene.measure_panel_edge_length
1574     del bpy.types.Scene.measure_panel_area1
1575     del bpy.types.Scene.measure_panel_area2
1576     del bpy.types.Scene.measure_panel_normal1
1577     del bpy.types.Scene.measure_panel_normal2
1578     del bpy.types.Scene.measure_panel_volume1
1579     del bpy.types.Scene.measure_panel_volume2
1580     del bpy.types.Scene.measure_panel_transform
1581     del bpy.types.Scene.measure_panel_draw
1582     del bpy.types.Scene.measure_panel_calc_edge_length
1583     del bpy.types.Scene.measure_panel_calc_area
1584     del bpy.types.Scene.measure_panel_calc_volume
1585     del bpy.types.Scene.measure_panel_update
1586
1587 if __name__ == "__main__":
1588     register()