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