diff --git a/space_view3d_panel_measure.py b/space_view3d_panel_measure.py index 6f024a0bab5c1f11e7dba78540ed816a4e6fd8c0..30809809fd92cabfd5b694d9164928d9f59bf430 100644 --- a/space_view3d_panel_measure.py +++ b/space_view3d_panel_measure.py @@ -15,13 +15,20 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### +# +# Uses volume calculation and manifold check code (GPL2+) from: +# http://www.shapeways.com/forum/index.php?t=msg&goto=3639 +# Shapeways Volume Calculator by Benjamin Lauritzen (Loonsbury) +# +# ################################# bl_info = { "name": "Measure Panel", - "author": "Buerbaum Martin (Pontiac), TNae (Normal patch)", - "version": (0, 7, 15), + "author": "Buerbaum Martin (Pontiac), TNae (Normal patch)," \ + " Benjamin Lauritzen (Loonsbury; Volume code)", + "version": (0, 8, 0), "blender": (2, 5, 7), - "api": 35864, + "api": 40343, "location": "View3D > Properties > Measure Panel", "description": "Measure distances between objects", "warning": "Script needs repairs", @@ -58,6 +65,7 @@ It's very helpful to use one or two "Empty" objects with "Snap during transform" enabled for fast measurement. Version history: +v0.8.0 - Volume calculation v0.7.15 - Measurement of face normals by TNae + extended to cover all cases. Grouped measured values inside boxes. v0.7.14 @@ -432,6 +440,125 @@ def objectSurfaceArea(obj, selectedOnly, globalSpace): return -1, Vector((0.0, 0.0, 0.0)) +# Calculate the volume of a mesh object. +# Copyright Loonsbury (loonsbury@yahoo.com) +def objectVolume(obj, globalSpace): + if (obj and obj.type == 'MESH' and obj.data): + + # Check if mesh is non-manifold + if not checkManifold(obj): + return -1 + + mesh = obj.data + + volTot = 0 + + for face in mesh.faces: + fzn = face.normal.z + + if len(face.vertices) == 4: + v1, v2, v3, v4 = face.vertices + else: + v1, v2, v3 = face.vertices + + v1 = obj.data.vertices[v1] + v2 = obj.data.vertices[v2] + v3 = obj.data.vertices[v3] + + # Scaled vert coordinates with object XYZ offsets for + # selection extremes/sizing. + if globalSpace: + x1 = v1.co[0] * obj.scale[0] + obj.location[0] + y1 = v1.co[1] * obj.scale[1] + obj.location[1] + z1 = v1.co[2] * obj.scale[2] + obj.location[2] + + x2 = v2.co[0] * obj.scale[0] + obj.location[0] + y2 = v2.co[1] * obj.scale[1] + obj.location[1] + z2 = v2.co[2] * obj.scale[2] + obj.location[2] + + x3 = v3.co[0] * obj.scale[0] + obj.location[0] + y3 = v3.co[1] * obj.scale[1] + obj.location[1] + z3 = v3.co[2] * obj.scale[2] + obj.location[2] + + else: + x1, y1, z1 = v1.co + x2, y2, z2 = v2.co + x3, y3, z3 = v3.co + + pa = 0.5 * abs( + (x1 * (y3 - y2)) + + (x2 * (y1 - y3)) + + (x3 * (y2 - y1))) + volume = ((z1 + z2 + z3) / 3.0) * pa + + # Allowing for quads + if len(face.vertices) == 4: + # Get vertex data + v4 = obj.data.vertices[v4] + + if globalSpace: + x4 = v4.co[0] * obj.scale[0] + obj.location[0] + y4 = v4.co[1] * obj.scale[1] + obj.location[1] + z4 = v4.co[2] * obj.scale[2] + obj.location[2] + + else: + x4, y4, z4 = v4.co + + pa = 0.5 * abs( + (x1 * (y4 - y3)) + + (x3 * (y1 - y4)) + + (x4 * (y3 - y1))) + + volume += ((z1 + z3 + z4) / 3.0) * pa + + if fzn < 0: + fzn = -1 + + elif fzn > 0: + fzn = 1 + + else: + fzn = 0 + + volTot += fzn * volume + + return volTot + +# else: +# print obj.name, ': Object must be a mesh!' # TODO + + return -2 + + +# Manifold Checks +# Copyright Loonsbury (loonsbury@yahoo.com) +def checkManifold(obj): + if (obj and obj.type == 'MESH' and obj.data): + mesh = obj.data + + mc = dict([(ed.key, 0) for ed in mesh.edges]) # TODO + + for f in mesh.faces: + for ek in f.edge_keys: + mc[ek] += 1 + if mc[ek] > 2: + return 0 + + mt = [e[1] for e in mc.items()] + mt.sort() + + if mt[0] < 2: + return 0 + + if mt[len(mt) - 1] > 2: + return 0 + + return 1 + + else: + return -1 + + # User friendly access to the "space" setting. def measureGlobal(sce): return (sce.measure_panel_transform == "measure_global") @@ -681,10 +808,40 @@ def draw_measurements_callback(self, context): # Calculate surface area of the object. area, normal = objectSurfaceArea(obj, False, measureGlobal(sce)) + if (area >= 0): sce.measure_panel_area1 = area sce.measure_panel_normal1 = normal + if (sce.measure_panel_calc_volume): + obj = getSingleObject(context) + + if (context.mode == 'OBJECT'): + # We are working in object mode. + + #if len(context.selected_objects) > 2: # TODO + + #el + if len(context.selected_objects) == 2: + # 2 objects selected. + + obj1, obj2 = context.selected_objects + + # Calculate surface area of the objects. + volume1 = objectVolume(obj1, measureGlobal(sce)) + volume2 = objectVolume(obj2, measureGlobal(sce)) + + sce.measure_panel_volume1 = volume1 + sce.measure_panel_volume2 = volume2 + + elif (obj): + # One object selected. + + # Calculate surface area of the object. + volume1 = objectVolume(obj, measureGlobal(sce)) + + sce.measure_panel_volume1 = volume1 + class VIEW3D_OT_display_measurements(bpy.types.Operator): '''Display the measurements made in the 'Measure' panel''' @@ -800,6 +957,8 @@ class VIEW3D_PT_measure(bpy.types.Panel): # Get a single selected object (or nothing). obj = getSingleObject(context) + drawTansformButtons = 1 + if (context.mode == 'EDIT_MESH'): obj = context.active_object @@ -854,11 +1013,6 @@ class VIEW3D_PT_measure(bpy.types.Panel): # " to do this manually, after you changed" \ # " the selection.") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) - elif len(verts_selected) == 1: # One vertex selected. # We measure the distance from the @@ -877,11 +1031,6 @@ class VIEW3D_PT_measure(bpy.types.Panel): row.operator("view3d.reenter_editmode", text="Update selection & distance") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) - elif len(verts_selected) == 2: # Two vertices selected. # We measure the distance between the @@ -900,11 +1049,6 @@ class VIEW3D_PT_measure(bpy.types.Panel): row.operator("view3d.reenter_editmode", text="Update selection & distance") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) - else: row = layout.row() row.prop(sce, "measure_panel_calc_area", @@ -937,11 +1081,6 @@ class VIEW3D_PT_measure(bpy.types.Panel): row.operator("view3d.reenter_editmode", text="Update selection & area") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) - else: row = layout.row() row.label(text="Selection not supported.", @@ -993,11 +1132,6 @@ class VIEW3D_PT_measure(bpy.types.Panel): # row.label(text=str(round(area, PRECISION)) # + " BU^2") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) - elif len(context.selected_objects) == 2: # 2 objects selected. # We measure the distance between the 2 selected objects. @@ -1019,7 +1153,7 @@ class VIEW3D_PT_measure(bpy.types.Panel): row = layout.row() row.prop(sce, "measure_panel_calc_area", - text="Surface area:") + text="Surface area") if (sce.measure_panel_calc_area): # Display surface area of the objects. @@ -1053,10 +1187,39 @@ class VIEW3D_PT_measure(bpy.types.Panel): row = box.row() row.prop(sce, "measure_panel_normal2") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) + row = layout.row() + row.prop(sce, "measure_panel_calc_volume", + text="Volume:") + + if (sce.measure_panel_calc_volume): + # Display volume of the objects. + if (sce.measure_panel_volume1 >= -1): + box = layout.box() + row = box.row() + row.label(text=obj1.name, icon='OBJECT_DATA') + + if (sce.measure_panel_volume1 >= 0): + row = box.row() + row.label(text="Volume") + row.prop(sce, "measure_panel_volume1") + else: # -1 + row = box.row() + row.label(text="Mesh is non-manifold!", + icon='INFO') + + if (sce.measure_panel_volume2 >= -1): + box = layout.box() + row = box.row() + row.label(text=obj2.name, icon='OBJECT_DATA') + + if (sce.measure_panel_volume2 >= 0): + row = box.row() + row.label(text="Volume") + row.prop(sce, "measure_panel_volume2") + else: # -1 + row = box.row() + row.label(text="Mesh is non-manifold!", + icon='INFO') elif (obj): # One object selected. @@ -1076,7 +1239,7 @@ class VIEW3D_PT_measure(bpy.types.Panel): row = layout.row() row.prop(sce, "measure_panel_calc_area", - text="Surface area:") + text="Surface area") if (sce.measure_panel_calc_area): # Display surface area of the object. @@ -1095,10 +1258,25 @@ class VIEW3D_PT_measure(bpy.types.Panel): row = box.row() row.prop(sce, "measure_panel_normal1") - row = layout.row() - row.prop(sce, - "measure_panel_transform", - expand=True) + row = layout.row() + row.prop(sce, "measure_panel_calc_volume", + text="Volume:") + + if (sce.measure_panel_calc_volume): + # Display volume of the objects. + if (sce.measure_panel_volume1 >= -1): + box = layout.box() + row = box.row() + row.label(text=obj.name, icon='OBJECT_DATA') + + if (sce.measure_panel_volume1 >= 0): + row = box.row() + row.label(text="Volume") + row.prop(sce, "measure_panel_volume1") + else: # -1 + row = box.row() + row.label(text="Mesh is non-manifold!", + icon='INFO') elif not context.selected_objects: # Nothing selected. @@ -1118,6 +1296,12 @@ class VIEW3D_PT_measure(bpy.types.Panel): row.label(text="Selection not supported.", icon='INFO') + if drawTansformButtons: + row = layout.row() + row.prop(sce, + "measure_panel_transform", + expand=True) + def register(): bpy.utils.register_module(__name__) @@ -1139,6 +1323,12 @@ def register(): bpy.types.Scene.measure_panel_normal2 = bpy.props.FloatVectorProperty( precision=PRECISION, subtype="XYZ") + bpy.types.Scene.measure_panel_volume1 = bpy.props.FloatProperty( + precision=PRECISION, + unit="VOLUME") + bpy.types.Scene.measure_panel_volume2 = bpy.props.FloatProperty( + precision=PRECISION, + unit="VOLUME") TRANSFORM = [ ("measure_global", "Global", @@ -1165,6 +1355,12 @@ def register(): " usage on bigger meshes)", default=0) + # Define property for the calc-volume setting. + bpy.types.Scene.measure_panel_calc_volume = bpy.props.BoolProperty( + description="Calculate mesh volume (heavy CPU" \ + " usage on bigger meshes)", + default=0) + pass