Patch T41093: Cleanup non-manifold
[blender-addons.git] / space_view3d_panel_measure.py
blob975da1c76de8fb5a6b8005385bd122c8eed7f718
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 "category": "3D View",
40 """
41 Measure panel
43 This script displays in OBJECT MODE:
44 * The distance of the 3D cursor to the origin of the
45 3D space (if NOTHING is selected).
46 * The distance of the 3D cursor to the center of an object
47 (if exactly ONE object is selected).
48 * The distance between 2 object centers
49 (if exactly TWO objects are selected).
50 * The surface area of any selected mesh object.
51 * The average normal of the mesh surface of any selected mesh object.
52 * The volume of any selected mesh object.
54 Display in EDIT MODE (Local and Global space supported):
55 * The distance of the 3D cursor to the origin
56 (in Local space it is the object center instead).
57 * The distance of the 3D cursor to a selected vertex.
58 * The distance between 2 selected vertices.
60 Usage:
62 This functionality can be accessed via the
63 "Properties" panel in 3D View ([N] key).
65 It's very helpful to use one or two "Empty" objects with
66 "Snap during transform" enabled for fast measurement.
68 More links:
69 http://gitorious.org/blender-scripts/blender-measure-panel-script
70 http://blenderartists.org/forum/showthread.php?t=177800
71 """
73 import bpy
74 from bpy.props import *
75 from bpy.app.handlers import persistent
76 from mathutils import Vector, Matrix
77 import bgl
78 import blf
79 from bpy_extras.view3d_utils import location_3d_to_region_2d
80 from bpy_extras.mesh_utils import ngon_tessellate
83 # Precicion for display of float values.
84 PRECISION = 5
86 # Name of the custom properties as stored in the scene.
87 COLOR_LOCAL = (1.0, 0.5, 0.0, 0.8)
88 COLOR_GLOBAL = (0.5, 0.0, 1.0, 0.8)
90 # 3D View - text offset
91 OFFSET_LINE = 10 # Offset the text a bit to the right.
92 OFFSET_Y = 15 # Offset of the lines.
93 OFFSET_VALUE = 30 # Offset of value(s) from the text.
95 # 3D View - line width
96 LINE_WIDTH_XYZ = 1
97 LINE_WIDTH_DIST = 2
100 # Returns a tuple describing the current measuring system
101 # and formatting options.
102 # Returned data is meant to be passed to formatDistance().
103 # Original by Alessandro Sala (Feb, 12th 2012)
104 # Update by Alessandro Sala (Dec, 18th 2012)
105 def getUnitsInfo():
106 scale = bpy.context.scene.unit_settings.scale_length
107 unit_system = bpy.context.scene.unit_settings.system
108 separate_units = bpy.context.scene.unit_settings.use_separate
109 if unit_system == 'METRIC':
110 scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
111 (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
112 elif unit_system == 'IMPERIAL':
113 scale_steps = ((5280, 'mi'), (1, '\''),
114 (1 / 12, '"'), (1 / 12000, 'thou'))
115 scale /= 0.3048 # BU to feet
116 else:
117 scale_steps = ((1, ' BU'),)
118 separate_units = False
120 return (scale, scale_steps, separate_units)
123 # Converts a distance from BU into the measuring system
124 # described by units_info.
125 # Original by Alessandro Sala (Feb, 12th 2012)
126 # Update by Alessandro Sala (Dec, 18th 2012)
127 def convertDistance(val, units_info):
128 scale, scale_steps, separate_units = units_info
129 sval = val * scale
130 idx = 0
131 while idx < len(scale_steps) - 1:
132 if sval >= scale_steps[idx][0]:
133 break
134 idx += 1
135 factor, suffix = scale_steps[idx]
136 sval /= factor
137 if not separate_units or idx == len(scale_steps) - 1:
138 dval = str(round(sval, PRECISION)) + suffix
139 else:
140 ival = int(sval)
141 dval = str(round(ival, PRECISION)) + suffix
142 fval = sval - ival
143 idx += 1
144 while idx < len(scale_steps):
145 fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
146 if fval >= 1:
147 dval += ' ' \
148 + ("%.1f" % fval) \
149 + scale_steps[idx][1]
150 break
151 idx += 1
153 return dval
156 # Returns a single selected object.
157 # Returns None if more than one (or nothing) is selected.
158 # Note: Ignores the active object.
159 def getSingleObject():
160 if len(bpy.context.selected_objects) == 1:
161 return bpy.context.selected_objects[0]
163 return None
166 # Returns a list with 2 3D points (Vector) and a color (RGBA)
167 # depending on the current view mode and the selection.
168 def getMeasurePoints(context):
169 sce = context.scene
170 mode = context.mode
172 # Get a single selected object (or nothing).
173 obj = getSingleObject()
175 if mode == 'EDIT_MESH':
176 obj = context.active_object
178 if obj and obj.type == 'MESH' and obj.data:
179 # Get mesh data from Object.
180 mesh = obj.data
182 # Get the selected vertices.
183 # @todo: Better (more efficient) way to do this?
184 verts_selected = [v for v in mesh.vertices if v.select == 1]
186 if len(verts_selected) == 0:
187 # Nothing selected.
188 # We measure the distance from...
189 # local ... the object center to the 3D cursor.
190 # global ... the origin to the 3D cursor.
191 cur_loc = sce.cursor_location
192 obj_loc = obj.matrix_world.to_translation()
194 # Convert to local space, if needed.
195 if measureLocal(sce):
196 p1 = cur_loc
197 p2 = obj_loc
198 return (p1, p2, COLOR_GLOBAL)
200 else:
201 p1 = Vector((0.0, 0.0, 0.0))
202 p2 = cur_loc
203 return (p1, p2, COLOR_GLOBAL)
205 elif len(verts_selected) == 1:
206 # One vertex selected.
207 # We measure the distance from the
208 # selected vertex object to the 3D cursor.
209 cur_loc = sce.cursor_location
210 vert_loc = verts_selected[0].co.copy()
212 # Convert to local or global space.
213 if measureLocal(sce):
214 p1 = vert_loc
215 p2 = cur_loc
216 return (p1, p2, COLOR_LOCAL)
218 else:
219 p1 = obj.matrix_world * vert_loc
220 p2 = cur_loc
221 return (p1, p2, COLOR_GLOBAL)
223 elif len(verts_selected) == 2:
224 # Two vertices selected.
225 # We measure the distance between the
226 # two selected vertices.
227 obj_loc = obj.matrix_world.to_translation()
228 vert1_loc = verts_selected[0].co.copy()
229 vert2_loc = verts_selected[1].co.copy()
231 # Convert to local or global space.
232 if measureLocal(sce):
233 p1 = vert1_loc
234 p2 = vert2_loc
235 return (p1, p2, COLOR_LOCAL)
237 else:
238 p1 = obj.matrix_world * vert1_loc
239 p2 = obj.matrix_world * vert2_loc
240 return (p1, p2, COLOR_GLOBAL)
242 else:
243 return None
245 elif mode == 'OBJECT':
246 # We are working in object mode.
248 if len(context.selected_objects) > 2:
249 return None
250 elif len(context.selected_objects) == 2:
251 # 2 objects selected.
252 # We measure the distance between the 2 selected objects.
253 obj1, obj2 = context.selected_objects
254 obj1_loc = obj1.matrix_world.to_translation()
255 obj2_loc = obj2.matrix_world.to_translation()
256 return (obj1_loc, obj2_loc, COLOR_GLOBAL)
258 elif obj:
259 # One object selected.
260 # We measure the distance from the object to the 3D cursor.
261 cur_loc = sce.cursor_location
262 obj_loc = obj.matrix_world.to_translation()
263 return (obj_loc, cur_loc, COLOR_GLOBAL)
265 elif not context.selected_objects:
266 # Nothing selected.
267 # We measure the distance from the origin to the 3D cursor.
268 p1 = Vector((0.0, 0.0, 0.0))
269 p2 = sce.cursor_location
270 return (p1, p2, COLOR_GLOBAL)
272 else:
273 return None
276 # Return the length of an edge (in global space if "obj" is set).
277 # Respects the scaling (via the "obj.matrix_world" parameter).
278 def edgeLengthGlobal(edge, obj, globalSpace):
279 v1, v2 = edge.vertices
281 # Get vertex data
282 v1 = obj.data.vertices[v1]
283 v2 = obj.data.vertices[v2]
285 if globalSpace:
286 mat = obj.matrix_world
287 # Apply transform matrix to vertex coordinates.
288 v1 = mat * v1.co
289 v2 = mat * v2.co
290 else:
291 v1 = v1.co
292 v2 = v2.co
294 return (v1 - v2).length
297 # Calculate the edge length of a mesh object.
298 # *) Set selectedOnly=1 if you only want to count selected edges.
299 # *) Set globalSpace=1 if you want to calculate
300 # the global edge length (object mode).
301 # Note: Be sure you have updated the mesh data before
302 # running this with selectedOnly=1!
303 # @todo Support other object types (surfaces, etc...)?
304 def objectEdgeLength(obj, selectedOnly, globalSpace):
305 if obj and obj.type == 'MESH' and obj.data:
306 edgeTotal = 0
308 mesh = obj.data
310 # Count the length of all edges.
311 for ed in mesh.edges:
312 if not selectedOnly or ed.select:
313 edgeTotal += edgeLengthGlobal(ed, obj, globalSpace)
315 return edgeTotal
317 # We can not calculate a length for this object.
318 return -1
321 # Return the area of a face (in global space).
322 # @note Copies the functionality of the following functions,
323 # but also respects the scaling (via the "obj.matrix_world" parameter):
324 # @sa: rna_mesh.c:rna_MeshTessFace_area_get
325 # @sa: math_geom.c:area_quad_v3
326 # @sa: math_geom.c:area_tri_v3
327 # @sa: math_geom.c:area_poly_v3
328 # @todo Fix calculation of "n" for n-gons?
329 def polyAreaGlobal(poly, obj):
330 mesh = obj.data
331 mat = obj.matrix_world.copy()
332 norm = poly.normal
334 area = 0.0
336 if len(poly.vertices) > 3:
337 # Tesselate the polygon into multiple tris
338 tris = ngon_tessellate(mesh, poly.vertices)
340 for tri in tris:
341 # Get vertex data
342 v1, v2, v3 = tri
344 # Get indices from original poly
345 v1 = poly.vertices[v1]
346 v2 = poly.vertices[v2]
347 v3 = poly.vertices[v3]
349 # Get vertex information from indices
350 v1 = mesh.vertices[v1]
351 v2 = mesh.vertices[v2]
352 v3 = mesh.vertices[v3]
354 # Apply transform matrix to vertex coordinates.
355 v1 = mat * v1.co
356 v2 = mat * v2.co
357 v3 = mat * v3.co
359 # Calculate area for the new tri
360 vec1 = v3 - v2
361 vec2 = v1 - v2
363 n = vec1.cross(vec2)
365 area += n.length / 2.0
367 elif len(poly.vertices) == 3:
368 # Triangle
370 # Get vertex indices
371 v1, v2, v3 = poly.vertices
373 # Get vertex data
374 v1 = mesh.vertices[v1]
375 v2 = mesh.vertices[v2]
376 v3 = mesh.vertices[v3]
378 # Apply transform matrix to vertex coordinates.
379 v1 = mat * v1.co
380 v2 = mat * v2.co
381 v3 = mat * v3.co
383 vec1 = v3 - v2
384 vec2 = v1 - v2
386 n = vec1.cross(vec2)
388 area = n.length / 2.0
390 # Apply rotation and scale to the normal as well.
391 rot_mat = obj.matrix_world.to_quaternion()
392 scale = obj.matrix_world.to_scale()
393 norm = rot_mat * norm
394 norm = Vector((
395 norm.x * scale.x,
396 norm.y * scale.y,
397 norm.z * scale.z)).normalized()
399 return area, norm
402 # Calculate the surface area of a mesh object.
403 # *) Set selectedOnly=1 if you only want to count selected faces.
404 # *) Set globalSpace=1 if you want to calculate
405 # the global surface area (object mode).
406 # Note: Be sure you have updated the mesh data before
407 # running this with selectedOnly=1!
408 # @todo Support other object types (surfaces, etc...)?
409 def objectSurfaceArea(obj, selectedOnly, globalSpace):
410 if obj and obj.type == 'MESH' and obj.data:
411 areaTotal = 0
412 normTotal = Vector((0.0, 0.0, 0.0))
414 mesh = obj.data
416 # Count the area of all the faces.
417 for poly in mesh.polygons:
418 if not selectedOnly or poly.select:
419 if globalSpace:
420 a, n = polyAreaGlobal(poly, obj)
421 areaTotal += a
422 normTotal += n
423 else:
424 areaTotal += poly.area
425 normTotal += poly.normal
427 return areaTotal, normTotal
429 # We can not calculate an area for this object.
430 return -1, Vector((0.0, 0.0, 0.0))
433 # Calculate the volume of a mesh object.
434 # Copyright Loonsbury (loonsbury@yahoo.com)
435 def objectVolume(obj, globalSpace):
436 if obj and obj.type == 'MESH' and obj.data:
438 # Check if mesh is non-manifold
439 if not checkManifold(obj):
440 return -1
442 # Check if mesh has n-gons
443 if checkNgon(obj):
444 return -2
446 mesh = obj.data
448 volTot = 0
450 for poly in mesh.polygons:
451 fzn = poly.normal.z
453 if len(poly.vertices) == 4:
454 v1, v2, v3, v4 = poly.vertices
455 else:
456 v1, v2, v3 = poly.vertices
458 v1 = mesh.vertices[v1]
459 v2 = mesh.vertices[v2]
460 v3 = mesh.vertices[v3]
462 # Scaled vert coordinates with object XYZ offsets for
463 # selection extremes/sizing.
464 if globalSpace:
465 x1 = v1.co[0] * obj.scale[0] + obj.location[0]
466 y1 = v1.co[1] * obj.scale[1] + obj.location[1]
467 z1 = v1.co[2] * obj.scale[2] + obj.location[2]
469 x2 = v2.co[0] * obj.scale[0] + obj.location[0]
470 y2 = v2.co[1] * obj.scale[1] + obj.location[1]
471 z2 = v2.co[2] * obj.scale[2] + obj.location[2]
473 x3 = v3.co[0] * obj.scale[0] + obj.location[0]
474 y3 = v3.co[1] * obj.scale[1] + obj.location[1]
475 z3 = v3.co[2] * obj.scale[2] + obj.location[2]
477 else:
478 x1, y1, z1 = v1.co
479 x2, y2, z2 = v2.co
480 x3, y3, z3 = v3.co
482 pa = 0.5 * abs(
483 (x1 * (y3 - y2))
484 + (x2 * (y1 - y3))
485 + (x3 * (y2 - y1)))
486 volume = ((z1 + z2 + z3) / 3.0) * pa
488 # Allowing for quads
489 if len(poly.vertices) == 4:
490 # Get vertex data
491 v4 = mesh.vertices[v4]
493 if globalSpace:
494 x4 = v4.co[0] * obj.scale[0] + obj.location[0]
495 y4 = v4.co[1] * obj.scale[1] + obj.location[1]
496 z4 = v4.co[2] * obj.scale[2] + obj.location[2]
498 else:
499 x4, y4, z4 = v4.co
501 pa = 0.5 * abs(
502 (x1 * (y4 - y3))
503 + (x3 * (y1 - y4))
504 + (x4 * (y3 - y1)))
506 volume += ((z1 + z3 + z4) / 3.0) * pa
508 if fzn < 0:
509 fzn = -1
511 elif fzn > 0:
512 fzn = 1
514 else:
515 fzn = 0
517 volTot += fzn * volume
519 return volTot
521 # else:
522 # print obj.name, ': Object must be a mesh!' # TODO
524 return -3
527 # Manifold Checks
528 # Copyright Loonsbury (loonsbury@yahoo.com)
529 def checkManifold(obj):
530 if obj and obj.type == 'MESH' and obj.data:
531 mesh = obj.data
533 mc = dict([(ed.key, 0) for ed in mesh.edges]) # TODO
535 for p in mesh.polygons:
536 for ek in p.edge_keys:
537 mc[ek] += 1
538 if mc[ek] > 2:
539 return 0
541 mt = [e[1] for e in mc.items()]
542 mt.sort()
544 if mt[0] < 2:
545 return 0
547 if mt[len(mt) - 1] > 2:
548 return 0
550 return 1
552 else:
553 return -1
556 # Check if a mesh has n-gons (polygon with more than 4 edges).
557 def checkNgon(obj):
558 if obj and obj.type == 'MESH' and obj.data:
559 mesh = obj.data
561 for p in mesh.polygons:
562 if len(p.vertices) > 4:
563 return 1
565 return 0
567 else:
568 return -1
571 # User friendly access to the "space" setting.
572 def measureGlobal(sce):
573 return (sce.measure_panel_transform == "measure_global")
576 # User friendly access to the "space" setting.
577 def measureLocal(sce):
578 return (sce.measure_panel_transform == "measure_local")
581 # Calculate values if geometry, selection or cursor changed.
582 @persistent
583 def scene_update(context):
584 sce = context
585 mode = bpy.context.mode
587 if (mode == 'EDIT_MESH' and not sce.measure_panel_update):
588 return
590 if (bpy.data.objects.is_updated
591 or bpy.context.scene.is_updated
592 or sce.measure_panel_update):
593 # TODO: Better way to check selection changes and cursor changes?
595 sel_objs = bpy.context.selected_objects
597 # EDGE LENGTH
598 if sce.measure_panel_calc_edge_length:
599 if (mode == 'EDIT_MESH'
600 and sce.measure_panel_update):
601 sce.measure_panel_update = 0
602 obj = context.active_object
604 #if obj.is_updated:
605 length_total = objectEdgeLength(obj, True,
606 measureGlobal(sce))
607 sce.measure_panel_edge_length = length_total
609 elif mode == 'OBJECT':
610 length_total = -1
612 for o in sel_objs:
613 if o.type == 'MESH':
614 length = objectEdgeLength(o, False, measureGlobal(sce))
616 if length >= 0:
617 if length_total < 0:
618 length_total = 0
620 length_total += length
622 sce.measure_panel_edge_length = length_total
624 # AREA
625 # Handle mesh surface area calulations
626 if sce.measure_panel_calc_area:
627 if (mode == 'EDIT_MESH'
628 and sce.measure_panel_update):
629 sce.measure_panel_update = 0
630 obj = bpy.context.active_object
632 if obj and obj.type == 'MESH' and obj.data:
633 # "Note: a Mesh will return the selection state of the mesh
634 # when EditMode was last exited. A Python script operating
635 # in EditMode must exit EditMode before getting the current
636 # selection state of the mesh."
637 # http://www.blender.org/documentation/249PythonDoc/
638 # /Mesh.MVert-class.html#sel
639 # We can only provide this by existing &
640 # re-entering EditMode.
641 # @todo: Better way to do this?
643 # Get mesh data from Object.
644 me = obj.data
646 # Get transformation matrix from object.
647 ob_mat = obj.matrix_world
648 # Also make an inversed copy! of the matrix.
649 ob_mat_inv = ob_mat.copy()
650 Matrix.invert(ob_mat_inv)
652 # Get the selected vertices.
653 # @todo: Better (more efficient) way to do this?
654 verts_selected = [v for v in me.vertices if v.select == 1]
656 if len(verts_selected) >= 3:
657 # Get selected faces
658 # @todo: Better (more efficient) way to do this?
659 polys_selected = [p for p in me.polygons
660 if p.select == 1]
662 if len(polys_selected) > 0:
663 area, normal = objectSurfaceArea(obj, True,
664 measureGlobal(sce))
665 if area >= 0.0:
666 sce.measure_panel_area1 = area
667 sce.measure_panel_normal1 = normal
669 elif mode == 'OBJECT':
670 # We are working in object mode.
672 # Get a single selected object (or nothing).
673 obj = getSingleObject()
675 if len(sel_objs) > 2:
676 return
677 # @todo Make this work again.
678 # # We have more that 2 objects selected...
680 # mesh_objects = [o for o in context.selected_objects
681 # if o.type == 'MESH']
683 # if len(mesh_objects) > 0:
684 # # ... and at least one of them is a mesh.
686 # for o in mesh_objects:
687 # area = objectSurfaceArea(o, False,
688 # measureGlobal(sce))
689 # if area >= 0:
690 # #row.label(text=o.name, icon='OBJECT_DATA')
691 # #row.label(text=str(round(area, PRECISION))
692 # # + " BU^2")
694 elif len(sel_objs) == 2:
695 # 2 objects selected.
697 obj1, obj2 = sel_objs
699 # Calculate surface area of the objects.
700 area1, normal1 = objectSurfaceArea(obj1, False,
701 measureGlobal(sce))
702 area2, normal2 = objectSurfaceArea(obj2, False,
703 measureGlobal(sce))
704 sce.measure_panel_area1 = area1
705 sce.measure_panel_area2 = area2
706 sce.measure_panel_normal1 = normal1
707 sce.measure_panel_normal2 = normal2
709 elif obj:
710 # One object selected.
712 # Calculate surface area of the object.
713 area, normal = objectSurfaceArea(obj, False,
714 measureGlobal(sce))
716 sce.measure_panel_area1 = area
717 sce.measure_panel_normal1 = normal
719 # VOLUME
720 # Handle mesh volume calulations.
721 if sce.measure_panel_calc_volume:
722 obj = getSingleObject()
724 if mode == 'OBJECT':
725 # We are working in object mode.
727 #if len(sel_objs) > 2: # TODO
729 if len(sel_objs) == 2:
730 # 2 objects selected.
732 obj1, obj2 = sel_objs
734 # Calculate surface area of the objects.
735 volume1 = objectVolume(obj1, measureGlobal(sce))
736 volume2 = objectVolume(obj2, measureGlobal(sce))
738 sce.measure_panel_volume1 = volume1
739 sce.measure_panel_volume2 = volume2
741 elif obj:
742 # One object selected.
744 # Calculate surface area of the object.
745 volume1 = objectVolume(obj, measureGlobal(sce))
747 sce.measure_panel_volume1 = volume1
750 def draw_measurements_callback(self, context):
751 sce = context.scene
753 draw = 0
754 if hasattr(sce, "measure_panel_draw"):
755 draw = sce.measure_panel_draw
757 # 2D drawing code example
758 #bgl.glBegin(bgl.GL_LINE_STRIP)
759 #bgl.glVertex2i(0, 0)
760 #bgl.glVertex2i(80, 100)
761 #bgl.glEnd()
763 if draw:
764 # Get measured 3D points and colors.
765 line = getMeasurePoints(context)
767 if line:
768 p1, p2, color = line
770 # Get & convert the Perspective Matrix of the current view/region.
771 view3d = bpy.context
772 region = view3d.region_data
773 perspMatrix = region.perspective_matrix
774 tempMat = [perspMatrix[j][i] for i in range(4) for j in range(4)]
775 perspBuff = bgl.Buffer(bgl.GL_FLOAT, 16, tempMat)
777 # ---
778 # Store previous OpenGL settings.
779 # Store MatrixMode
780 MatrixMode_prev = bgl.Buffer(bgl.GL_INT, [1])
781 bgl.glGetIntegerv(bgl.GL_MATRIX_MODE, MatrixMode_prev)
782 MatrixMode_prev = MatrixMode_prev[0]
784 # Store projection matrix
785 ProjMatrix_prev = bgl.Buffer(bgl.GL_DOUBLE, [16])
786 bgl.glGetFloatv(bgl.GL_PROJECTION_MATRIX, ProjMatrix_prev)
788 # Store Line width
789 lineWidth_prev = bgl.Buffer(bgl.GL_FLOAT, [1])
790 bgl.glGetFloatv(bgl.GL_LINE_WIDTH, lineWidth_prev)
791 lineWidth_prev = lineWidth_prev[0]
793 # Store GL_BLEND
794 blend_prev = bgl.Buffer(bgl.GL_BYTE, [1])
795 bgl.glGetFloatv(bgl.GL_BLEND, blend_prev)
796 blend_prev = blend_prev[0]
798 line_stipple_prev = bgl.Buffer(bgl.GL_BYTE, [1])
799 bgl.glGetFloatv(bgl.GL_LINE_STIPPLE, line_stipple_prev)
800 line_stipple_prev = line_stipple_prev[0]
802 # Store glColor4f
803 color_prev = bgl.Buffer(bgl.GL_FLOAT, [4])
804 bgl.glGetFloatv(bgl.GL_COLOR, color_prev)
806 # ---
807 # Prepare for 3D drawing
808 bgl.glLoadIdentity()
809 bgl.glMatrixMode(bgl.GL_PROJECTION)
810 bgl.glLoadMatrixf(perspBuff)
812 bgl.glEnable(bgl.GL_BLEND)
813 bgl.glEnable(bgl.GL_LINE_STIPPLE)
815 # ---
816 # Draw 3D stuff.
817 bgl.glLineWidth(LINE_WIDTH_XYZ)
819 bgl.glColor4f(1, 0, 0, 0.8)
820 bgl.glBegin(bgl.GL_LINE_STRIP)
821 bgl.glVertex3f(p1[0], p1[1], p1[2])
822 bgl.glVertex3f(p2[0], p1[1], p1[2])
823 bgl.glEnd()
825 bgl.glColor4f(0, 1, 0, 0.8)
826 bgl.glBegin(bgl.GL_LINE_STRIP)
827 bgl.glVertex3f(p1[0], p1[1], p1[2])
828 bgl.glVertex3f(p1[0], p2[1], p1[2])
829 bgl.glEnd()
831 bgl.glColor4f(0, 0, 1, 0.8)
832 bgl.glBegin(bgl.GL_LINE_STRIP)
833 bgl.glVertex3f(p1[0], p1[1], p1[2])
834 bgl.glVertex3f(p1[0], p1[1], p2[2])
835 bgl.glEnd()
837 # Dist
838 bgl.glLineWidth(LINE_WIDTH_DIST)
839 bgl.glColor4f(color[0], color[1], color[2], color[3])
840 bgl.glBegin(bgl.GL_LINE_STRIP)
841 bgl.glVertex3f(p1[0], p1[1], p1[2])
842 bgl.glVertex3f(p2[0], p2[1], p2[2])
843 bgl.glEnd()
845 # ---
846 # Restore previous OpenGL settings
847 bgl.glLoadIdentity()
848 bgl.glMatrixMode(MatrixMode_prev)
849 bgl.glLoadMatrixf(ProjMatrix_prev)
850 bgl.glLineWidth(lineWidth_prev)
851 if not blend_prev:
852 bgl.glDisable(bgl.GL_BLEND)
853 if not line_stipple_prev:
854 bgl.glDisable(bgl.GL_LINE_STIPPLE)
855 bgl.glColor4f(
856 color_prev[0],
857 color_prev[1],
858 color_prev[2],
859 color_prev[3])
861 # ---
862 # Draw (2D) text
863 # We do this after drawing the lines so
864 # we can draw it OVER the line.
865 coord_2d = location_3d_to_region_2d(
866 context.region,
867 context.space_data.region_3d,
868 p1.lerp(p2, 0.5))
869 dist = (p1 - p2).length
871 # Write distance value into the scene property,
872 # so we can display it in the panel & refresh the panel.
873 if hasattr(sce, "measure_panel_dist"):
874 sce.measure_panel_dist = dist
875 context.area.tag_redraw()
877 texts = [
878 ("Dist:", dist),
879 ("X:", abs(p1[0] - p2[0])),
880 ("Y:", abs(p1[1] - p2[1])),
881 ("Z:", abs(p1[2] - p2[2]))]
883 # Draw all texts
884 # @todo Get user pref for text color in 3D View
885 bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
886 blf.size(0, 12, 72) # Prevent font size to randomly change.
888 uinfo = getUnitsInfo()
890 loc_x = coord_2d[0] + OFFSET_LINE
891 loc_y = coord_2d[1]
893 for t in texts:
894 text = t[0]
896 value = convertDistance(t[1], uinfo)
898 blf.position(0, loc_x, loc_y, 0)
899 blf.draw(0, text)
900 blf.position(0, loc_x + OFFSET_VALUE, loc_y, 0)
901 blf.draw(0, value)
903 loc_y -= OFFSET_Y
906 class VIEW3D_OT_display_measurements(bpy.types.Operator):
907 """Display the measurements made in the 'Measure' panel"""
908 bl_idname = "view3d.display_measurements"
909 bl_label = "Display the measurements made in the" \
910 " 'Measure' panel in the 3D View"
911 bl_options = {'REGISTER'}
913 def modal(self, context, event):
914 context.area.tag_redraw()
915 return {'FINISHED'}
917 def execute(self, context):
918 if context.area.type == 'VIEW_3D':
919 mgr_ops = context.window_manager.operators.values()
920 if not self.bl_idname in [op.bl_idname for op in mgr_ops]:
921 # Add the region OpenGL drawing callback
923 # XXX, this is never removed!, it should be! (at least when disabling the addon)
924 self._handle = bpy.types.SpaceView3D.draw_handler_add(
925 draw_measurements_callback,
926 (self, context),
927 'WINDOW', 'POST_PIXEL')
929 print("Measure panel display callback added")
931 # XXX, never removed!
932 context.window_manager.modal_handler_add(self)
933 return {'RUNNING_MODAL'}
935 return {'CANCELLED'}
937 else:
938 self.report({'WARNING'}, "View3D not found, cannot run operator")
939 return {'CANCELLED'}
942 class VIEW3D_OT_activate_measure_panel(bpy.types.Operator):
943 bl_label = "Activate"
944 bl_idname = "view3d.activate_measure_panel"
945 bl_description = "Activate the callback needed to draw the lines"
946 bl_options = {'REGISTER'}
948 def invoke(self, context, event):
950 # Execute operator (this adds the callback)
951 # if it wasn't done yet.
952 bpy.ops.view3d.display_measurements()
953 return {'FINISHED'}
956 class VIEW3D_OT_reenter_editmode(bpy.types.Operator):
957 bl_label = "Re-enter EditMode"
958 bl_idname = "view3d.reenter_editmode"
959 bl_description = "Update mesh data of an active mesh object " \
960 "(this is done by exiting and re-entering mesh edit mode)"
961 bl_options = {'REGISTER'}
963 def invoke(self, context, event):
965 # Get the active object.
966 obj = context.active_object
967 sce = context.scene
969 if obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH':
970 # Exit and re-enter mesh EditMode.
971 bpy.ops.object.mode_set(mode='OBJECT')
972 bpy.ops.object.mode_set(mode='EDIT')
973 sce.measure_panel_update = 1
974 return {'FINISHED'}
976 return {'CANCELLED'}
979 class VIEW3D_PT_measure(bpy.types.Panel):
980 bl_space_type = 'VIEW_3D'
981 bl_region_type = 'UI'
982 bl_label = "Measure"
983 bl_options = {'DEFAULT_CLOSED'}
985 @classmethod
986 def poll(cls, context):
987 # Only display this panel in the object and edit mode 3D view.
988 mode = context.mode
989 if (context.area.type == 'VIEW_3D' and
990 (mode == 'EDIT_MESH' or mode == 'OBJECT')):
991 return 1
993 return 0
995 def draw_header(self, context):
996 layout = self.layout
997 sce = context.scene
999 mgr_ops = context.window_manager.operators.values()
1000 if (not "VIEW3D_OT_display_measurements"
1001 in [op.bl_idname for op in mgr_ops]):
1002 layout.operator("view3d.activate_measure_panel",
1003 text="Activate")
1005 def draw(self, context):
1006 layout = self.layout
1007 sce = context.scene
1008 mode = context.mode
1010 # Get a single selected object (or nothing).
1011 obj = getSingleObject()
1013 drawTansformButtons = 1
1015 if mode == 'EDIT_MESH':
1016 obj = context.active_object
1018 row = layout.row()
1019 row.operator("view3d.reenter_editmode",
1020 text="Update selection")
1021 # @todo
1022 # description="The calculated values can" \
1023 # " not be updated in mesh edit mode" \
1024 # " automatically. Press this button" \
1025 # " to do this manually, after you changed" \
1026 # " the selection")
1028 if obj and obj.type == 'MESH' and obj.data:
1029 # "Note: a Mesh will return the selection state of the mesh
1030 # when EditMode was last exited. A Python script operating
1031 # in EditMode must exit EditMode before getting the current
1032 # selection state of the mesh."
1033 # http://www.blender.org/documentation/249PythonDoc/
1034 # /Mesh.MVert-class.html#sel
1035 # We can only provide this by existing & re-entering EditMode.
1036 # @todo: Better way to do this?
1038 # Get mesh data from Object.
1039 mesh = obj.data
1041 # Get transformation matrix from object.
1042 ob_mat = obj.matrix_world
1043 # Also make an inversed copy! of the matrix.
1044 ob_mat_inv = ob_mat.copy()
1045 Matrix.invert(ob_mat_inv)
1047 # Get the selected vertices.
1048 # @todo: Better (more efficient) way to do this?
1049 verts_selected = [v for v in mesh.vertices if v.select == 1]
1051 if len(verts_selected) == 0:
1052 # Nothing selected.
1053 # We measure the distance from...
1054 # local ... the object center to the 3D cursor.
1055 # global ... the origin to the 3D cursor.
1056 layout.label(text="Distance")
1058 box = layout.box()
1059 row = box.row()
1060 row.prop(sce, "measure_panel_dist")
1062 row = box.row()
1063 row.label(text="", icon='CURSOR')
1064 row.label(text="", icon='ARROW_LEFTRIGHT')
1065 if measureLocal(sce):
1066 row.label(text="Obj. Center")
1067 else:
1068 row.label(text="Origin [0,0,0]")
1070 layout.prop(sce, "measure_panel_draw")
1072 elif len(verts_selected) == 1:
1073 # One vertex selected.
1074 # We measure the distance from the
1075 # selected vertex object to the 3D cursor.
1076 layout.label(text="Distance")
1078 box = layout.box()
1079 row = box.row()
1080 row.prop(sce, "measure_panel_dist")
1082 row = box.row()
1083 row.label(text="", icon='CURSOR')
1084 row.label(text="", icon='ARROW_LEFTRIGHT')
1085 row.label(text="", icon='VERTEXSEL')
1087 layout.prop(sce, "measure_panel_draw")
1089 elif len(verts_selected) == 2:
1090 # Two vertices selected.
1091 # We measure the distance between the
1092 # two selected vertices.
1093 layout.label(text="Distance")
1095 box = layout.box()
1096 row = box.row()
1097 row.prop(sce, "measure_panel_dist")
1099 row = box.row()
1100 row.label(text="", icon='VERTEXSEL')
1101 row.label(text="", icon='ARROW_LEFTRIGHT')
1102 row.label(text="", icon='VERTEXSEL')
1104 layout.prop(sce, "measure_panel_draw")
1106 edges_selected = [ed for ed in mesh.edges if ed.select == 1]
1107 if len(edges_selected) >= 1:
1108 row = layout.row()
1109 row.prop(sce, "measure_panel_calc_edge_length",
1110 text="Edge Length (selected edges)")
1112 if sce.measure_panel_calc_edge_length:
1113 if sce.measure_panel_edge_length >= 0:
1114 box = layout.box()
1115 row = box.row()
1116 row.label(
1117 text=str(len(edges_selected)),
1118 icon='EDGESEL')
1120 row = box.row()
1121 row.label(text="Length")
1122 row.prop(sce, "measure_panel_edge_length")
1124 if len(verts_selected) > 2:
1125 row = layout.row()
1126 row.prop(sce, "measure_panel_calc_area",
1127 text="Surface area (selected faces)")
1129 if sce.measure_panel_calc_area:
1130 # Get selected faces
1131 # @todo: Better (more efficient) way to do this?
1132 polys_selected = [p for p in mesh.polygons
1133 if p.select == 1]
1135 if len(polys_selected) > 0:
1136 if sce.measure_panel_area1 >= 0:
1137 box = layout.box()
1138 row = box.row()
1139 row.label(
1140 text=str(len(polys_selected)),
1141 icon='FACESEL')
1143 row = box.row()
1144 row.label(text="Area")
1145 row.prop(sce, "measure_panel_area1")
1147 row = box.row()
1148 row.label(text="Normal")
1149 row = box.row()
1150 row.prop(sce, "measure_panel_normal1")
1152 else:
1153 row = layout.row()
1154 row.label(text="Selection not supported",
1155 icon='INFO')
1157 if drawTansformButtons:
1158 row = layout.row()
1159 row.prop(sce,
1160 "measure_panel_transform",
1161 expand=True)
1163 elif mode == 'OBJECT':
1164 # We are working in object mode.
1166 mesh_objects = [o for o in context.selected_objects
1167 if o.type == 'MESH']
1169 if len(context.selected_objects) > 2:
1170 # We have more that 2 objects selected...
1172 # EDGES
1173 row = layout.row()
1174 row.prop(sce, "measure_panel_calc_edge_length",
1175 text="Edge Length")
1177 if sce.measure_panel_calc_edge_length:
1178 if len(mesh_objects) > 0:
1179 box = layout.box()
1181 row = box.row()
1182 row.label(text="Total edge length")
1183 row.prop(sce, "measure_panel_edge_length")
1185 # AREA
1186 row = layout.row()
1187 row.prop(sce, "measure_panel_calc_area",
1188 text="Surface area")
1190 if sce.measure_panel_calc_area:
1191 if len(mesh_objects) > 0:
1192 # ... and at least one of them is a mesh.
1194 # Calculate and display surface area of the objects.
1195 # @todo: Convert to scene units! We do not have a
1196 # FloatProperty field here for automatic conversion.
1198 row = layout.row()
1199 row.label(text="Multiple objects not yet supported",
1200 icon='INFO')
1201 row = layout.row()
1202 row.label(text="(= More than two meshes)",
1203 icon='INFO')
1204 # @todo Make this work again.
1205 # for o in mesh_objects:
1206 # area = objectSurfaceArea(o, False,
1207 # measureGlobal(sce))
1208 # if area >= 0:
1209 # row = layout.row()
1210 # row.label(text=o.name, icon='OBJECT_DATA')
1211 # row.label(text=str(round(area, PRECISION))
1212 # + " BU^2")
1214 elif len(context.selected_objects) == 2:
1215 # 2 objects selected.
1216 # We measure the distance between the 2 selected objects.
1217 layout.label(text="Distance")
1219 obj1, obj2 = context.selected_objects
1221 box = layout.box()
1222 row = box.row()
1223 row.prop(sce, "measure_panel_dist")
1225 row = box.row()
1226 row.label(text="", icon='OBJECT_DATA')
1227 row.prop(obj1, "name", text="")
1229 row.label(text="", icon='ARROW_LEFTRIGHT')
1231 row.label(text="", icon='OBJECT_DATA')
1232 row.prop(obj2, "name", text="")
1234 layout.prop(sce, "measure_panel_draw")
1236 # EDGES
1237 row = layout.row()
1238 row.prop(sce, "measure_panel_calc_edge_length",
1239 text="Edge Length")
1241 if sce.measure_panel_calc_edge_length:
1242 if sce.measure_panel_edge_length >= 0:
1243 if len(mesh_objects) > 0:
1244 box = layout.box()
1246 row = box.row()
1247 row.label(text="Total edge length")
1248 row.prop(sce, "measure_panel_edge_length")
1250 # AREA
1252 row = layout.row()
1253 row.prop(sce, "measure_panel_calc_area",
1254 text="Surface area")
1256 if sce.measure_panel_calc_area:
1257 # Display surface area of the objects.
1258 if (sce.measure_panel_area1 >= 0
1259 or sce.measure_panel_area2 >= 0):
1260 if sce.measure_panel_area1 >= 0:
1261 box = layout.box()
1262 row = box.row()
1263 row.label(text=obj1.name, icon='OBJECT_DATA')
1265 row = box.row()
1266 row.label(text="Area")
1267 row.prop(sce, "measure_panel_area1")
1269 row = box.row()
1270 row.label(text="Normal")
1271 row = box.row()
1272 row.prop(sce, "measure_panel_normal1")
1274 if sce.measure_panel_area2 >= 0:
1275 box = layout.box()
1276 row = box.row()
1277 row.label(text=obj2.name, icon='OBJECT_DATA')
1279 row = box.row()
1280 row.label(text="Area")
1281 row.prop(sce, "measure_panel_area2")
1283 row = box.row()
1284 row.label(text="Normal")
1285 row = box.row()
1286 row.prop(sce, "measure_panel_normal2")
1288 # VOL
1289 row = layout.row()
1290 row.prop(sce, "measure_panel_calc_volume",
1291 text="Volume")
1293 if sce.measure_panel_calc_volume:
1294 # Display volume of the objects.
1295 if sce.measure_panel_volume1 >= -2:
1296 box = layout.box()
1297 row = box.row()
1298 row.label(text=obj1.name, icon='OBJECT_DATA')
1300 if sce.measure_panel_volume1 >= 0:
1301 row = box.row()
1302 row.label(text="Volume")
1303 row.prop(sce, "measure_panel_volume1")
1304 elif sce.measure_panel_volume1 >= -1:
1305 row = box.row()
1306 row.label(text="Mesh is non-manifold!",
1307 icon='INFO')
1308 else: # -2
1309 row = box.row()
1310 row.label(text="Mesh has n-gons (faces with " \
1311 "more than 4 edges)!",
1312 icon='INFO')
1314 if sce.measure_panel_volume2 >= -2:
1315 box = layout.box()
1316 row = box.row()
1317 row.label(text=obj2.name, icon='OBJECT_DATA')
1319 if sce.measure_panel_volume2 >= 0:
1320 row = box.row()
1321 row.label(text="Volume")
1322 row.prop(sce, "measure_panel_volume2")
1323 elif sce.measure_panel_volume2 >= -1:
1324 row = box.row()
1325 row.label(text="Mesh is non-manifold!",
1326 icon='INFO')
1327 else: # -2
1328 row = box.row()
1329 row.label(text="Mesh has n-gons (faces with " \
1330 "more than 4 edges)!",
1331 icon='INFO')
1333 elif obj:
1334 # One object selected.
1335 # We measure the distance from the object to the 3D cursor.
1336 layout.label(text="Distance")
1338 box = layout.box()
1339 row = box.row()
1340 row.prop(sce, "measure_panel_dist")
1342 row = box.row()
1343 row.label(text="", icon='CURSOR')
1345 row.label(text="", icon='ARROW_LEFTRIGHT')
1347 row.label(text="", icon='OBJECT_DATA')
1348 row.prop(obj, "name", text="")
1350 layout.prop(sce, "measure_panel_draw")
1352 # EDGES
1353 row = layout.row()
1354 row.prop(sce, "measure_panel_calc_edge_length",
1355 text="Edge Length")
1357 if sce.measure_panel_calc_edge_length:
1358 if sce.measure_panel_edge_length >= 0:
1359 if len(mesh_objects) > 0:
1360 box = layout.box()
1362 row = box.row()
1363 row.label(text="Total edge length")
1364 row.prop(sce, "measure_panel_edge_length")
1366 # AREA
1367 row = layout.row()
1368 row.prop(sce, "measure_panel_calc_area",
1369 text="Surface area")
1371 if sce.measure_panel_calc_area:
1372 # Display surface area of the object.
1374 if sce.measure_panel_area1 >= 0.0:
1375 box = layout.box()
1376 row = box.row()
1377 row.label(text=obj.name, icon='OBJECT_DATA')
1379 row = box.row()
1380 row.label(text="Area")
1381 row.prop(sce, "measure_panel_area1")
1383 row = box.row()
1384 row.label(text="Normal")
1385 row = box.row()
1386 row.prop(sce, "measure_panel_normal1")
1388 # VOL
1389 row = layout.row()
1390 row.prop(sce, "measure_panel_calc_volume",
1391 text="Volume")
1393 if sce.measure_panel_calc_volume:
1394 # Display volume of the objects.
1395 if sce.measure_panel_volume1 >= -2:
1396 box = layout.box()
1397 row = box.row()
1398 row.label(text=obj.name, icon='OBJECT_DATA')
1400 if sce.measure_panel_volume1 >= 0:
1401 row = box.row()
1402 row.label(text="Volume")
1403 row.prop(sce, "measure_panel_volume1")
1404 elif sce.measure_panel_volume1 >= -1:
1405 row = box.row()
1406 row.label(text="Mesh is non-manifold!",
1407 icon='INFO')
1408 else: # -2
1409 row = box.row()
1410 row.label(text="Mesh has n-gons (faces with " \
1411 "more than 4 edges)!",
1412 icon='INFO')
1414 elif not context.selected_objects:
1415 # Nothing selected.
1416 # We measure the distance from the origin to the 3D cursor.
1417 layout.label(text="Distance")
1419 box = layout.box()
1420 row = box.row()
1421 row.prop(sce, "measure_panel_dist")
1423 row = box.row()
1424 row.label(text="", icon='CURSOR')
1425 row.label(text="", icon='ARROW_LEFTRIGHT')
1426 row.label(text="Origin [0,0,0]")
1428 layout.prop(sce, "measure_panel_draw")
1430 else:
1431 row = layout.row()
1432 row.label(text="Selection not supported",
1433 icon='INFO')
1435 if drawTansformButtons:
1436 row = layout.row()
1437 row.prop(sce,
1438 "measure_panel_transform",
1439 expand=True)
1442 def register():
1443 bpy.utils.register_module(__name__)
1445 bpy.app.handlers.scene_update_post.append(scene_update)
1447 # Define a temporary attribute for the distance value
1448 bpy.types.Scene.measure_panel_dist = bpy.props.FloatProperty(
1449 name="Distance",
1450 precision=PRECISION,
1451 unit="LENGTH")
1452 bpy.types.Scene.measure_panel_edge_length = bpy.props.FloatProperty(
1453 name="",
1454 precision=PRECISION,
1455 unit="LENGTH")
1456 bpy.types.Scene.measure_panel_area1 = bpy.props.FloatProperty(
1457 name="",
1458 precision=PRECISION,
1459 unit="AREA")
1460 bpy.types.Scene.measure_panel_area2 = bpy.props.FloatProperty(
1461 name="",
1462 precision=PRECISION,
1463 unit="AREA")
1464 bpy.types.Scene.measure_panel_normal1 = bpy.props.FloatVectorProperty(
1465 name="",
1466 precision=PRECISION,
1467 subtype="XYZ")
1468 bpy.types.Scene.measure_panel_normal2 = bpy.props.FloatVectorProperty(
1469 name="",
1470 precision=PRECISION,
1471 subtype="XYZ")
1472 bpy.types.Scene.measure_panel_volume1 = bpy.props.FloatProperty(
1473 name="",
1474 precision=PRECISION,
1475 unit="VOLUME")
1476 bpy.types.Scene.measure_panel_volume2 = bpy.props.FloatProperty(
1477 name="",
1478 precision=PRECISION,
1479 unit="VOLUME")
1481 TRANSFORM = [
1482 ("measure_global", "Global",
1483 "Calculate values in global space"),
1484 ("measure_local", "Local",
1485 "Calculate values inside the local object space")]
1487 # Define dropdown for the global/local setting
1488 bpy.types.Scene.measure_panel_transform = bpy.props.EnumProperty(
1489 name="Space",
1490 description="Choose in which space you want to measure",
1491 items=TRANSFORM,
1492 default='measure_global')
1494 # Define property for the draw setting.
1495 bpy.types.Scene.measure_panel_draw = bpy.props.BoolProperty(
1496 name="Draw distance",
1497 description="Draw distances in 3D View",
1498 default=1)
1500 bpy.types.Scene.measure_panel_calc_edge_length = bpy.props.BoolProperty(
1501 description="Calculate total length of (selected) edges",
1502 default=0)
1504 # Define property for the calc-area setting.
1505 # @todo prevent double calculations for each refresh automatically?
1506 bpy.types.Scene.measure_panel_calc_area = bpy.props.BoolProperty(
1507 description="Calculate mesh surface area (heavy CPU "
1508 "usage on bigger meshes)",
1509 default=0)
1511 # Define property for the calc-volume setting.
1512 bpy.types.Scene.measure_panel_calc_volume = bpy.props.BoolProperty(
1513 description="Calculate mesh volume (heavy CPU "
1514 "usage on bigger meshes)",
1515 default=0)
1517 # Define dropdown for the global/local setting
1518 bpy.types.Scene.measure_panel_update = bpy.props.BoolProperty(
1519 description="Update CPU heavy calculations",
1520 default=0)
1522 pass
1525 def unregister():
1526 bpy.utils.unregister_module(__name__)
1528 bpy.app.handlers.scene_update_post.remove(scene_update)
1530 # Remove properties.
1531 del bpy.types.Scene.measure_panel_dist
1532 del bpy.types.Scene.measure_panel_edge_length
1533 del bpy.types.Scene.measure_panel_area1
1534 del bpy.types.Scene.measure_panel_area2
1535 del bpy.types.Scene.measure_panel_normal1
1536 del bpy.types.Scene.measure_panel_normal2
1537 del bpy.types.Scene.measure_panel_volume1
1538 del bpy.types.Scene.measure_panel_volume2
1539 del bpy.types.Scene.measure_panel_transform
1540 del bpy.types.Scene.measure_panel_draw
1541 del bpy.types.Scene.measure_panel_calc_edge_length
1542 del bpy.types.Scene.measure_panel_calc_area
1543 del bpy.types.Scene.measure_panel_calc_volume
1544 del bpy.types.Scene.measure_panel_update
1546 pass
1548 if __name__ == "__main__":
1549 register()