fix for error reading gzip'd x3d/vrml files
[blender-addons.git] / space_view3d_panel_measure.py
blob6897871a79bca02e0a132dfd32811604fb21ea86
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 "version": (0, 9, 0),
31 "blender": (2, 60, 0),
32 "location": "View3D > Properties > Measure Panel",
33 "description": "Measure distances between objects",
34 "warning": "Script needs repairs",
35 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" \
36 "Scripts/3D_interaction/Panel_Measure",
37 "tracker_url": "https://projects.blender.org/tracker/index.php?" \
38 "func=detail&aid=21445",
39 "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 = context.active_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 if draw:
765 # Get measured 3D points and colors.
766 line = getMeasurePoints(context)
768 if line:
769 p1, p2, color = line
771 # Get & convert the Perspective Matrix of the current view/region.
772 view3d = bpy.context
773 region = view3d.region_data
774 perspMatrix = region.perspective_matrix
775 tempMat = [perspMatrix[j][i] for i in range(4) for j in range(4)]
776 perspBuff = bgl.Buffer(bgl.GL_FLOAT, 16, tempMat)
778 # ---
779 # Store previous OpenGL settings.
780 # Store MatrixMode
781 MatrixMode_prev = bgl.Buffer(bgl.GL_INT, [1])
782 bgl.glGetIntegerv(bgl.GL_MATRIX_MODE, MatrixMode_prev)
783 MatrixMode_prev = MatrixMode_prev[0]
785 # Store projection matrix
786 ProjMatrix_prev = bgl.Buffer(bgl.GL_DOUBLE, [16])
787 bgl.glGetFloatv(bgl.GL_PROJECTION_MATRIX, ProjMatrix_prev)
789 # Store Line width
790 lineWidth_prev = bgl.Buffer(bgl.GL_FLOAT, [1])
791 bgl.glGetFloatv(bgl.GL_LINE_WIDTH, lineWidth_prev)
792 lineWidth_prev = lineWidth_prev[0]
794 # Store GL_BLEND
795 blend_prev = bgl.Buffer(bgl.GL_BYTE, [1])
796 bgl.glGetFloatv(bgl.GL_BLEND, blend_prev)
797 blend_prev = blend_prev[0]
799 line_stipple_prev = bgl.Buffer(bgl.GL_BYTE, [1])
800 bgl.glGetFloatv(bgl.GL_LINE_STIPPLE, line_stipple_prev)
801 line_stipple_prev = line_stipple_prev[0]
803 # Store glColor4f
804 color_prev = bgl.Buffer(bgl.GL_FLOAT, [4])
805 bgl.glGetFloatv(bgl.GL_COLOR, color_prev)
807 # ---
808 # Prepare for 3D drawing
809 bgl.glLoadIdentity()
810 bgl.glMatrixMode(bgl.GL_PROJECTION)
811 bgl.glLoadMatrixf(perspBuff)
813 bgl.glEnable(bgl.GL_BLEND)
814 bgl.glEnable(bgl.GL_LINE_STIPPLE)
816 # ---
817 # Draw 3D stuff.
818 bgl.glLineWidth(LINE_WIDTH_XYZ)
820 bgl.glColor4f(1, 0, 0, 0.8)
821 bgl.glBegin(bgl.GL_LINE_STRIP)
822 bgl.glVertex3f(p1[0], p1[1], p1[2])
823 bgl.glVertex3f(p2[0], p1[1], p1[2])
824 bgl.glEnd()
826 bgl.glColor4f(0, 1, 0, 0.8)
827 bgl.glBegin(bgl.GL_LINE_STRIP)
828 bgl.glVertex3f(p1[0], p1[1], p1[2])
829 bgl.glVertex3f(p1[0], p2[1], p1[2])
830 bgl.glEnd()
832 bgl.glColor4f(0, 0, 1, 0.8)
833 bgl.glBegin(bgl.GL_LINE_STRIP)
834 bgl.glVertex3f(p1[0], p1[1], p1[2])
835 bgl.glVertex3f(p1[0], p1[1], p2[2])
836 bgl.glEnd()
838 # Dist
839 bgl.glLineWidth(LINE_WIDTH_DIST)
840 bgl.glColor4f(color[0], color[1], color[2], color[3])
841 bgl.glBegin(bgl.GL_LINE_STRIP)
842 bgl.glVertex3f(p1[0], p1[1], p1[2])
843 bgl.glVertex3f(p2[0], p2[1], p2[2])
844 bgl.glEnd()
846 # ---
847 # Restore previous OpenGL settings
848 bgl.glLoadIdentity()
849 bgl.glMatrixMode(MatrixMode_prev)
850 bgl.glLoadMatrixf(ProjMatrix_prev)
851 bgl.glLineWidth(lineWidth_prev)
852 if not blend_prev:
853 bgl.glDisable(bgl.GL_BLEND)
854 if not line_stipple_prev:
855 bgl.glDisable(bgl.GL_LINE_STIPPLE)
856 bgl.glColor4f(
857 color_prev[0],
858 color_prev[1],
859 color_prev[2],
860 color_prev[3])
862 # ---
863 # Draw (2D) text
864 # We do this after drawing the lines so
865 # we can draw it OVER the line.
866 coord_2d = location_3d_to_region_2d(
867 context.region,
868 context.space_data.region_3d,
869 p1.lerp(p2, 0.5))
870 dist = (p1 - p2).length
872 # Write distance value into the scene property,
873 # so we can display it in the panel & refresh the panel.
874 if hasattr(sce, "measure_panel_dist"):
875 sce.measure_panel_dist = dist
876 context.area.tag_redraw()
878 texts = [
879 ("Dist:", dist),
880 ("X:", abs(p1[0] - p2[0])),
881 ("Y:", abs(p1[1] - p2[1])),
882 ("Z:", abs(p1[2] - p2[2]))]
884 # Draw all texts
885 # @todo Get user pref for text color in 3D View
886 bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
887 blf.size(0, 12, 72) # Prevent font size to randomly change.
889 uinfo = getUnitsInfo()
891 loc_x = coord_2d[0] + OFFSET_LINE
892 loc_y = coord_2d[1]
894 for t in texts:
895 text = t[0]
897 value = convertDistance(t[1], uinfo)
899 blf.position(0, loc_x, loc_y, 0)
900 blf.draw(0, text)
901 blf.position(0, loc_x + OFFSET_VALUE, loc_y, 0)
902 blf.draw(0, value)
904 loc_y -= OFFSET_Y
907 class VIEW3D_OT_display_measurements(bpy.types.Operator):
908 """Display the measurements made in the 'Measure' panel"""
909 bl_idname = "view3d.display_measurements"
910 bl_label = "Display the measurements made in the" \
911 " 'Measure' panel in the 3D View"
912 bl_options = {'REGISTER'}
914 def modal(self, context, event):
915 context.area.tag_redraw()
916 return {'FINISHED'}
918 def execute(self, context):
919 if context.area.type == 'VIEW_3D':
920 mgr_ops = context.window_manager.operators.values()
921 if not self.bl_idname in [op.bl_idname for op in mgr_ops]:
922 # Add the region OpenGL drawing callback
924 # XXX, this is never removed!, it should be! (at least when disabling the addon)
925 self._handle = bpy.types.SpaceView3D.draw_handler_add(
926 draw_measurements_callback,
927 (self, context),
928 'WINDOW', 'POST_PIXEL')
930 print("Measure panel display callback added")
932 # XXX, never removed!
933 context.window_manager.modal_handler_add(self)
934 return {'RUNNING_MODAL'}
936 return {'CANCELLED'}
938 else:
939 self.report({'WARNING'}, "View3D not found, cannot run operator")
940 return {'CANCELLED'}
943 class VIEW3D_OT_activate_measure_panel(bpy.types.Operator):
944 bl_label = "Activate"
945 bl_idname = "view3d.activate_measure_panel"
946 bl_description = "Activate the callback needed to draw the lines"
947 bl_options = {'REGISTER'}
949 def invoke(self, context, event):
951 # Execute operator (this adds the callback)
952 # if it wasn't done yet.
953 bpy.ops.view3d.display_measurements()
954 return {'FINISHED'}
957 class VIEW3D_OT_reenter_editmode(bpy.types.Operator):
958 bl_label = "Re-enter EditMode"
959 bl_idname = "view3d.reenter_editmode"
960 bl_description = "Update mesh data of an active mesh object " \
961 "(this is done by exiting and re-entering mesh edit mode)"
962 bl_options = {'REGISTER'}
964 def invoke(self, context, event):
966 # Get the active object.
967 obj = context.active_object
968 sce = context.scene
970 if obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH':
971 # Exit and re-enter mesh EditMode.
972 bpy.ops.object.mode_set(mode='OBJECT')
973 bpy.ops.object.mode_set(mode='EDIT')
974 sce.measure_panel_update = 1
975 return {'FINISHED'}
977 return {'CANCELLED'}
980 class VIEW3D_PT_measure(bpy.types.Panel):
981 bl_space_type = 'VIEW_3D'
982 bl_region_type = 'UI'
983 bl_label = "Measure"
984 bl_options = {'DEFAULT_CLOSED'}
986 @classmethod
987 def poll(cls, context):
988 # Only display this panel in the object and edit mode 3D view.
989 mode = context.mode
990 if (context.area.type == 'VIEW_3D' and
991 (mode == 'EDIT_MESH' or mode == 'OBJECT')):
992 return 1
994 return 0
996 def draw_header(self, context):
997 layout = self.layout
998 sce = context.scene
1000 mgr_ops = context.window_manager.operators.values()
1001 if (not "VIEW3D_OT_display_measurements"
1002 in [op.bl_idname for op in mgr_ops]):
1003 layout.operator("view3d.activate_measure_panel",
1004 text="Activate")
1006 def draw(self, context):
1007 layout = self.layout
1008 sce = context.scene
1009 mode = context.mode
1011 # Get a single selected object (or nothing).
1012 obj = getSingleObject()
1014 drawTansformButtons = 1
1016 if mode == 'EDIT_MESH':
1017 obj = context.active_object
1019 row = layout.row()
1020 row.operator("view3d.reenter_editmode",
1021 text="Update selection")
1022 # @todo
1023 # description="The calculated values can" \
1024 # " not be updated in mesh edit mode" \
1025 # " automatically. Press this button" \
1026 # " to do this manually, after you changed" \
1027 # " the selection")
1029 if obj and obj.type == 'MESH' and obj.data:
1030 # "Note: a Mesh will return the selection state of the mesh
1031 # when EditMode was last exited. A Python script operating
1032 # in EditMode must exit EditMode before getting the current
1033 # selection state of the mesh."
1034 # http://www.blender.org/documentation/249PythonDoc/
1035 # /Mesh.MVert-class.html#sel
1036 # We can only provide this by existing & re-entering EditMode.
1037 # @todo: Better way to do this?
1039 # Get mesh data from Object.
1040 mesh = obj.data
1042 # Get transformation matrix from object.
1043 ob_mat = obj.matrix_world
1044 # Also make an inversed copy! of the matrix.
1045 ob_mat_inv = ob_mat.copy()
1046 Matrix.invert(ob_mat_inv)
1048 # Get the selected vertices.
1049 # @todo: Better (more efficient) way to do this?
1050 verts_selected = [v for v in mesh.vertices if v.select == 1]
1052 if len(verts_selected) == 0:
1053 # Nothing selected.
1054 # We measure the distance from...
1055 # local ... the object center to the 3D cursor.
1056 # global ... the origin to the 3D cursor.
1057 layout.label(text="Distance")
1059 box = layout.box()
1060 row = box.row()
1061 row.prop(sce, "measure_panel_dist")
1063 row = box.row()
1064 row.label(text="", icon='CURSOR')
1065 row.label(text="", icon='ARROW_LEFTRIGHT')
1066 if measureLocal(sce):
1067 row.label(text="Obj. Center")
1068 else:
1069 row.label(text="Origin [0,0,0]")
1071 layout.prop(sce, "measure_panel_draw")
1073 elif len(verts_selected) == 1:
1074 # One vertex selected.
1075 # We measure the distance from the
1076 # selected vertex object to the 3D cursor.
1077 layout.label(text="Distance")
1079 box = layout.box()
1080 row = box.row()
1081 row.prop(sce, "measure_panel_dist")
1083 row = box.row()
1084 row.label(text="", icon='CURSOR')
1085 row.label(text="", icon='ARROW_LEFTRIGHT')
1086 row.label(text="", icon='VERTEXSEL')
1088 layout.prop(sce, "measure_panel_draw")
1090 elif len(verts_selected) == 2:
1091 # Two vertices selected.
1092 # We measure the distance between the
1093 # two selected vertices.
1094 layout.label(text="Distance")
1096 box = layout.box()
1097 row = box.row()
1098 row.prop(sce, "measure_panel_dist")
1100 row = box.row()
1101 row.label(text="", icon='VERTEXSEL')
1102 row.label(text="", icon='ARROW_LEFTRIGHT')
1103 row.label(text="", icon='VERTEXSEL')
1105 layout.prop(sce, "measure_panel_draw")
1107 edges_selected = [ed for ed in mesh.edges if ed.select == 1]
1108 if len(edges_selected) >= 1:
1109 row = layout.row()
1110 row.prop(sce, "measure_panel_calc_edge_length",
1111 text="Edge Length (selected edges)")
1113 if sce.measure_panel_calc_edge_length:
1114 if sce.measure_panel_edge_length >= 0:
1115 box = layout.box()
1116 row = box.row()
1117 row.label(
1118 text=str(len(edges_selected)),
1119 icon='EDGESEL')
1121 row = box.row()
1122 row.label(text="Length")
1123 row.prop(sce, "measure_panel_edge_length")
1125 if len(verts_selected) > 2:
1126 row = layout.row()
1127 row.prop(sce, "measure_panel_calc_area",
1128 text="Surface area (selected faces)")
1130 if sce.measure_panel_calc_area:
1131 # Get selected faces
1132 # @todo: Better (more efficient) way to do this?
1133 polys_selected = [p for p in mesh.polygons
1134 if p.select == 1]
1136 if len(polys_selected) > 0:
1137 if sce.measure_panel_area1 >= 0:
1138 box = layout.box()
1139 row = box.row()
1140 row.label(
1141 text=str(len(polys_selected)),
1142 icon='FACESEL')
1144 row = box.row()
1145 row.label(text="Area")
1146 row.prop(sce, "measure_panel_area1")
1148 row = box.row()
1149 row.label(text="Normal")
1150 row = box.row()
1151 row.prop(sce, "measure_panel_normal1")
1153 else:
1154 row = layout.row()
1155 row.label(text="Selection not supported",
1156 icon='INFO')
1158 if drawTansformButtons:
1159 row = layout.row()
1160 row.prop(sce,
1161 "measure_panel_transform",
1162 expand=True)
1164 elif mode == 'OBJECT':
1165 # We are working in object mode.
1167 mesh_objects = [o for o in context.selected_objects
1168 if o.type == 'MESH']
1170 if len(context.selected_objects) > 2:
1171 # We have more that 2 objects selected...
1173 # EDGES
1174 row = layout.row()
1175 row.prop(sce, "measure_panel_calc_edge_length",
1176 text="Edge Length")
1178 if sce.measure_panel_calc_edge_length:
1179 if len(mesh_objects) > 0:
1180 box = layout.box()
1182 row = box.row()
1183 row.label(text="Total edge length")
1184 row.prop(sce, "measure_panel_edge_length")
1186 # AREA
1187 row = layout.row()
1188 row.prop(sce, "measure_panel_calc_area",
1189 text="Surface area")
1191 if sce.measure_panel_calc_area:
1192 if len(mesh_objects) > 0:
1193 # ... and at least one of them is a mesh.
1195 # Calculate and display surface area of the objects.
1196 # @todo: Convert to scene units! We do not have a
1197 # FloatProperty field here for automatic conversion.
1199 row = layout.row()
1200 row.label(text="Multiple objects not yet supported",
1201 icon='INFO')
1202 row = layout.row()
1203 row.label(text="(= More than two meshes)",
1204 icon='INFO')
1205 # @todo Make this work again.
1206 # for o in mesh_objects:
1207 # area = objectSurfaceArea(o, False,
1208 # measureGlobal(sce))
1209 # if area >= 0:
1210 # row = layout.row()
1211 # row.label(text=o.name, icon='OBJECT_DATA')
1212 # row.label(text=str(round(area, PRECISION))
1213 # + " BU^2")
1215 elif len(context.selected_objects) == 2:
1216 # 2 objects selected.
1217 # We measure the distance between the 2 selected objects.
1218 layout.label(text="Distance")
1220 obj1, obj2 = context.selected_objects
1222 box = layout.box()
1223 row = box.row()
1224 row.prop(sce, "measure_panel_dist")
1226 row = box.row()
1227 row.label(text="", icon='OBJECT_DATA')
1228 row.prop(obj1, "name", text="")
1230 row.label(text="", icon='ARROW_LEFTRIGHT')
1232 row.label(text="", icon='OBJECT_DATA')
1233 row.prop(obj2, "name", text="")
1235 layout.prop(sce, "measure_panel_draw")
1237 # EDGES
1238 row = layout.row()
1239 row.prop(sce, "measure_panel_calc_edge_length",
1240 text="Edge Length")
1242 if sce.measure_panel_calc_edge_length:
1243 if sce.measure_panel_edge_length >= 0:
1244 if len(mesh_objects) > 0:
1245 box = layout.box()
1247 row = box.row()
1248 row.label(text="Total edge length")
1249 row.prop(sce, "measure_panel_edge_length")
1251 # AREA
1253 row = layout.row()
1254 row.prop(sce, "measure_panel_calc_area",
1255 text="Surface area")
1257 if sce.measure_panel_calc_area:
1258 # Display surface area of the objects.
1259 if (sce.measure_panel_area1 >= 0
1260 or sce.measure_panel_area2 >= 0):
1261 if sce.measure_panel_area1 >= 0:
1262 box = layout.box()
1263 row = box.row()
1264 row.label(text=obj1.name, icon='OBJECT_DATA')
1266 row = box.row()
1267 row.label(text="Area")
1268 row.prop(sce, "measure_panel_area1")
1270 row = box.row()
1271 row.label(text="Normal")
1272 row = box.row()
1273 row.prop(sce, "measure_panel_normal1")
1275 if sce.measure_panel_area2 >= 0:
1276 box = layout.box()
1277 row = box.row()
1278 row.label(text=obj2.name, icon='OBJECT_DATA')
1280 row = box.row()
1281 row.label(text="Area")
1282 row.prop(sce, "measure_panel_area2")
1284 row = box.row()
1285 row.label(text="Normal")
1286 row = box.row()
1287 row.prop(sce, "measure_panel_normal2")
1289 # VOL
1290 row = layout.row()
1291 row.prop(sce, "measure_panel_calc_volume",
1292 text="Volume")
1294 if sce.measure_panel_calc_volume:
1295 # Display volume of the objects.
1296 if sce.measure_panel_volume1 >= -2:
1297 box = layout.box()
1298 row = box.row()
1299 row.label(text=obj1.name, icon='OBJECT_DATA')
1301 if sce.measure_panel_volume1 >= 0:
1302 row = box.row()
1303 row.label(text="Volume")
1304 row.prop(sce, "measure_panel_volume1")
1305 elif sce.measure_panel_volume1 >= -1:
1306 row = box.row()
1307 row.label(text="Mesh is non-manifold!",
1308 icon='INFO')
1309 else: # -2
1310 row = box.row()
1311 row.label(text="Mesh has n-gons (faces with " \
1312 "more than 4 edges)!",
1313 icon='INFO')
1315 if sce.measure_panel_volume2 >= -2:
1316 box = layout.box()
1317 row = box.row()
1318 row.label(text=obj2.name, icon='OBJECT_DATA')
1320 if sce.measure_panel_volume2 >= 0:
1321 row = box.row()
1322 row.label(text="Volume")
1323 row.prop(sce, "measure_panel_volume2")
1324 elif sce.measure_panel_volume2 >= -1:
1325 row = box.row()
1326 row.label(text="Mesh is non-manifold!",
1327 icon='INFO')
1328 else: # -2
1329 row = box.row()
1330 row.label(text="Mesh has n-gons (faces with " \
1331 "more than 4 edges)!",
1332 icon='INFO')
1334 elif obj:
1335 # One object selected.
1336 # We measure the distance from the object to the 3D cursor.
1337 layout.label(text="Distance")
1339 box = layout.box()
1340 row = box.row()
1341 row.prop(sce, "measure_panel_dist")
1343 row = box.row()
1344 row.label(text="", icon='CURSOR')
1346 row.label(text="", icon='ARROW_LEFTRIGHT')
1348 row.label(text="", icon='OBJECT_DATA')
1349 row.prop(obj, "name", text="")
1351 layout.prop(sce, "measure_panel_draw")
1353 # EDGES
1354 row = layout.row()
1355 row.prop(sce, "measure_panel_calc_edge_length",
1356 text="Edge Length")
1358 if sce.measure_panel_calc_edge_length:
1359 if sce.measure_panel_edge_length >= 0:
1360 if len(mesh_objects) > 0:
1361 box = layout.box()
1363 row = box.row()
1364 row.label(text="Total edge length")
1365 row.prop(sce, "measure_panel_edge_length")
1367 # AREA
1368 row = layout.row()
1369 row.prop(sce, "measure_panel_calc_area",
1370 text="Surface area")
1372 if sce.measure_panel_calc_area:
1373 # Display surface area of the object.
1375 if sce.measure_panel_area1 >= 0.0:
1376 box = layout.box()
1377 row = box.row()
1378 row.label(text=obj.name, icon='OBJECT_DATA')
1380 row = box.row()
1381 row.label(text="Area")
1382 row.prop(sce, "measure_panel_area1")
1384 row = box.row()
1385 row.label(text="Normal")
1386 row = box.row()
1387 row.prop(sce, "measure_panel_normal1")
1389 # VOL
1390 row = layout.row()
1391 row.prop(sce, "measure_panel_calc_volume",
1392 text="Volume")
1394 if sce.measure_panel_calc_volume:
1395 # Display volume of the objects.
1396 if sce.measure_panel_volume1 >= -2:
1397 box = layout.box()
1398 row = box.row()
1399 row.label(text=obj.name, icon='OBJECT_DATA')
1401 if sce.measure_panel_volume1 >= 0:
1402 row = box.row()
1403 row.label(text="Volume")
1404 row.prop(sce, "measure_panel_volume1")
1405 elif sce.measure_panel_volume1 >= -1:
1406 row = box.row()
1407 row.label(text="Mesh is non-manifold!",
1408 icon='INFO')
1409 else: # -2
1410 row = box.row()
1411 row.label(text="Mesh has n-gons (faces with " \
1412 "more than 4 edges)!",
1413 icon='INFO')
1415 elif not context.selected_objects:
1416 # Nothing selected.
1417 # We measure the distance from the origin to the 3D cursor.
1418 layout.label(text="Distance")
1420 box = layout.box()
1421 row = box.row()
1422 row.prop(sce, "measure_panel_dist")
1424 row = box.row()
1425 row.label(text="", icon='CURSOR')
1426 row.label(text="", icon='ARROW_LEFTRIGHT')
1427 row.label(text="Origin [0,0,0]")
1429 layout.prop(sce, "measure_panel_draw")
1431 else:
1432 row = layout.row()
1433 row.label(text="Selection not supported",
1434 icon='INFO')
1436 if drawTansformButtons:
1437 row = layout.row()
1438 row.prop(sce,
1439 "measure_panel_transform",
1440 expand=True)
1443 def register():
1444 bpy.utils.register_module(__name__)
1446 bpy.app.handlers.scene_update_post.append(scene_update)
1448 # Define a temporary attribute for the distance value
1449 bpy.types.Scene.measure_panel_dist = bpy.props.FloatProperty(
1450 name="Distance",
1451 precision=PRECISION,
1452 unit="LENGTH")
1453 bpy.types.Scene.measure_panel_edge_length = bpy.props.FloatProperty(
1454 name="",
1455 precision=PRECISION,
1456 unit="LENGTH")
1457 bpy.types.Scene.measure_panel_area1 = bpy.props.FloatProperty(
1458 name="",
1459 precision=PRECISION,
1460 unit="AREA")
1461 bpy.types.Scene.measure_panel_area2 = bpy.props.FloatProperty(
1462 name="",
1463 precision=PRECISION,
1464 unit="AREA")
1465 bpy.types.Scene.measure_panel_normal1 = bpy.props.FloatVectorProperty(
1466 name="",
1467 precision=PRECISION,
1468 subtype="XYZ")
1469 bpy.types.Scene.measure_panel_normal2 = bpy.props.FloatVectorProperty(
1470 name="",
1471 precision=PRECISION,
1472 subtype="XYZ")
1473 bpy.types.Scene.measure_panel_volume1 = bpy.props.FloatProperty(
1474 name="",
1475 precision=PRECISION,
1476 unit="VOLUME")
1477 bpy.types.Scene.measure_panel_volume2 = bpy.props.FloatProperty(
1478 name="",
1479 precision=PRECISION,
1480 unit="VOLUME")
1482 TRANSFORM = [
1483 ("measure_global", "Global",
1484 "Calculate values in global space"),
1485 ("measure_local", "Local",
1486 "Calculate values inside the local object space")]
1488 # Define dropdown for the global/local setting
1489 bpy.types.Scene.measure_panel_transform = bpy.props.EnumProperty(
1490 name="Space",
1491 description="Choose in which space you want to measure",
1492 items=TRANSFORM,
1493 default='measure_global')
1495 # Define property for the draw setting.
1496 bpy.types.Scene.measure_panel_draw = bpy.props.BoolProperty(
1497 name="Draw distance",
1498 description="Draw distances in 3D View",
1499 default=1)
1501 bpy.types.Scene.measure_panel_calc_edge_length = bpy.props.BoolProperty(
1502 description="Calculate total length of (selected) edges",
1503 default=0)
1505 # Define property for the calc-area setting.
1506 # @todo prevent double calculations for each refresh automatically?
1507 bpy.types.Scene.measure_panel_calc_area = bpy.props.BoolProperty(
1508 description="Calculate mesh surface area (heavy CPU "
1509 "usage on bigger meshes)",
1510 default=0)
1512 # Define property for the calc-volume setting.
1513 bpy.types.Scene.measure_panel_calc_volume = bpy.props.BoolProperty(
1514 description="Calculate mesh volume (heavy CPU "
1515 "usage on bigger meshes)",
1516 default=0)
1518 # Define dropdown for the global/local setting
1519 bpy.types.Scene.measure_panel_update = bpy.props.BoolProperty(
1520 description="Update CPU heavy calculations",
1521 default=0)
1523 pass
1526 def unregister():
1527 bpy.utils.unregister_module(__name__)
1529 bpy.app.handlers.scene_update_post.remove(scene_update)
1531 # Remove properties.
1532 del bpy.types.Scene.measure_panel_dist
1533 del bpy.types.Scene.measure_panel_edge_length
1534 del bpy.types.Scene.measure_panel_area1
1535 del bpy.types.Scene.measure_panel_area2
1536 del bpy.types.Scene.measure_panel_normal1
1537 del bpy.types.Scene.measure_panel_normal2
1538 del bpy.types.Scene.measure_panel_volume1
1539 del bpy.types.Scene.measure_panel_volume2
1540 del bpy.types.Scene.measure_panel_transform
1541 del bpy.types.Scene.measure_panel_draw
1542 del bpy.types.Scene.measure_panel_calc_edge_length
1543 del bpy.types.Scene.measure_panel_calc_area
1544 del bpy.types.Scene.measure_panel_calc_volume
1545 del bpy.types.Scene.measure_panel_update
1547 pass
1549 if __name__ == "__main__":
1550 register()