-
-
Save natchiketa/312da17fa3739a80f2d0 to your computer and use it in GitHub Desktop.
preliminary_edge_length addon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bl_info = { | |
"name": "Edge sum", | |
"author": "zeffii", | |
"version": (0, 1, 0), | |
"blender": (2, 6, 1), | |
"location": "3d view, N panel", | |
"description": "Adds edge sum box to Mesh Display.", | |
"wiki_url": "", | |
"tracker_url": "", | |
"category": "3D View"} | |
# http://blender.stackexchange.com/a/1071/47 | |
# where Adhi suggests the space_view3d_panel_measure import. | |
import bpy | |
import bmesh | |
import space_view3d_panel_measure as pm | |
def print_details(num_edges, edge_length): | |
print("number of edges: {0}".format(num_edges)) | |
print("combined length: {0:6f}".format(edge_length)) | |
def get_combined_length(object_reference): | |
# Get a BMesh representation | |
bm = bmesh.from_edit_mesh(object_reference.data) | |
selected_edges = [edge for edge in bm.edges if edge.select] | |
num_edges = len(selected_edges) | |
edge_length = 0 | |
for edge in selected_edges: | |
edge_length += edge.calc_length() | |
print_details(num_edges, edge_length) | |
return round(edge_length, 6) | |
class SumButton(bpy.types.Operator): | |
bl_idname = "scene.calculate_length" | |
bl_label = "Sometype of operator" | |
bpy.types.Scene.sum_addon_length = bpy.props.StringProperty(name = "") | |
def execute(self, context): | |
obj_reference = context.active_object | |
length = get_combined_length(obj_reference) | |
uinfo = pm.getUnitsInfo() | |
length = pm.convertDistance(length, uinfo) | |
context.scene.sum_addon_length = length | |
return{'FINISHED'} | |
class CopyLength(bpy.types.Operator): | |
bl_idname = "scene.copy_length" | |
bl_label = "copy to clipboard" | |
def execute(self, context): | |
context.window_manager.clipboard = context.scene.sum_addon_length | |
return{'FINISHED'} | |
def draw_item(self, context): | |
layout = self.layout | |
obj = context.object | |
row = layout.row() | |
scn = context.scene | |
# display label and button | |
if obj: | |
row.label(text="summed edge length:") | |
row = layout.row() | |
split = row.split(percentage=0.50) | |
col = split.column() | |
col.prop(scn, 'sum_addon_length') | |
split = split.split() | |
col = split.column() | |
col.operator("scene.calculate_length", text='Sum') | |
split = split.split() | |
col = split.column() | |
col.operator("scene.copy_length", text='Copy') | |
def register(): | |
bpy.utils.register_module(__name__) | |
bpy.types.VIEW3D_PT_view3d_meshdisplay.append(draw_item) | |
def unregister(): | |
bpy.utils.unregister_module(__name__) | |
bpy.types.VIEW3D_PT_view3d_meshdisplay.remove(draw_item) | |
if __name__ == "__main__": | |
register() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ##### BEGIN GPL LICENSE BLOCK ##### | |
# | |
# This program is free software; you can redistribute it and/or | |
# modify it under the terms of the GNU General Public License | |
# as published by the Free Software Foundation; either version 2 | |
# of the License, or (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program; if not, write to the Free Software Foundation, | |
# 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), " | |
"Benjamin Lauritzen (Loonsbury; Volume code), " | |
"Alessandro Sala (patch: Units in 3D View), " | |
"Daniel Ashby (callback removal code) ", | |
"version": (0, 9, 1), | |
"blender": (2, 60, 0), | |
"location": "View3D > Properties > Measure Panel", | |
"description": "Measure distances between objects", | |
"warning": "Script needs repairs", | |
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" | |
"Scripts/3D_interaction/Panel_Measure", | |
"category": "3D View", | |
} | |
""" | |
Measure panel | |
This script displays in OBJECT MODE: | |
* The distance of the 3D cursor to the origin of the | |
3D space (if NOTHING is selected). | |
* The distance of the 3D cursor to the center of an object | |
(if exactly ONE object is selected). | |
* The distance between 2 object centers | |
(if exactly TWO objects are selected). | |
* The surface area of any selected mesh object. | |
* The average normal of the mesh surface of any selected mesh object. | |
* The volume of any selected mesh object. | |
Display in EDIT MODE (Local and Global space supported): | |
* The distance of the 3D cursor to the origin | |
(in Local space it is the object center instead). | |
* The distance of the 3D cursor to a selected vertex. | |
* The distance between 2 selected vertices. | |
Usage: | |
This functionality can be accessed via the | |
"Properties" panel in 3D View ([N] key). | |
It's very helpful to use one or two "Empty" objects with | |
"Snap during transform" enabled for fast measurement. | |
More links: | |
http://gitorious.org/blender-scripts/blender-measure-panel-script | |
http://blenderartists.org/forum/showthread.php?t=177800 | |
""" | |
import bpy | |
from bpy.props import * | |
from bpy.app.handlers import persistent | |
from mathutils import Vector, Matrix | |
import bgl | |
import blf | |
from bpy_extras.view3d_utils import location_3d_to_region_2d | |
from bpy_extras.mesh_utils import ngon_tessellate | |
# Precicion for display of float values. | |
PRECISION = 5 | |
# Name of the custom properties as stored in the scene. | |
COLOR_LOCAL = (1.0, 0.5, 0.0, 0.8) | |
COLOR_GLOBAL = (0.5, 0.0, 1.0, 0.8) | |
# 3D View - text offset | |
OFFSET_LINE = 10 # Offset the text a bit to the right. | |
OFFSET_Y = 15 # Offset of the lines. | |
OFFSET_VALUE = 30 # Offset of value(s) from the text. | |
# 3D View - line width | |
LINE_WIDTH_XYZ = 1 | |
LINE_WIDTH_DIST = 2 | |
# Returns a tuple describing the current measuring system | |
# and formatting options. | |
# Returned data is meant to be passed to formatDistance(). | |
# Original by Alessandro Sala (Feb, 12th 2012) | |
# Update by Alessandro Sala (Dec, 18th 2012) | |
def getUnitsInfo(): | |
scale = bpy.context.scene.unit_settings.scale_length | |
unit_system = bpy.context.scene.unit_settings.system | |
separate_units = bpy.context.scene.unit_settings.use_separate | |
if unit_system == 'METRIC': | |
scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'), | |
(1 / 1000, 'mm'), (1 / 1000000, '\u00b5m')) | |
elif unit_system == 'IMPERIAL': | |
scale_steps = ((5280, 'mi'), (1, '\''), | |
(1 / 12, '"'), (1 / 12000, 'thou')) | |
scale /= 0.3048 # BU to feet | |
else: | |
scale_steps = ((1, ' BU'),) | |
separate_units = False | |
return (scale, scale_steps, separate_units) | |
# Converts a distance from BU into the measuring system | |
# described by units_info. | |
# Original by Alessandro Sala (Feb, 12th 2012) | |
# Update by Alessandro Sala (Dec, 18th 2012) | |
def convertDistance(val, units_info): | |
scale, scale_steps, separate_units = units_info | |
sval = val * scale | |
idx = 0 | |
while idx < len(scale_steps) - 1: | |
if sval >= scale_steps[idx][0]: | |
break | |
idx += 1 | |
factor, suffix = scale_steps[idx] | |
sval /= factor | |
if not separate_units or idx == len(scale_steps) - 1: | |
dval = str(round(sval, PRECISION)) + suffix | |
else: | |
ival = int(sval) | |
dval = str(round(ival, PRECISION)) + suffix | |
fval = sval - ival | |
idx += 1 | |
while idx < len(scale_steps): | |
fval *= scale_steps[idx - 1][0] / scale_steps[idx][0] | |
if fval >= 1: | |
dval += ' ' \ | |
+ ("%.1f" % fval) \ | |
+ scale_steps[idx][1] | |
break | |
idx += 1 | |
return dval | |
# Returns a single selected object. | |
# Returns None if more than one (or nothing) is selected. | |
# Note: Ignores the active object. | |
def getSingleObject(): | |
if len(bpy.context.selected_objects) == 1: | |
return bpy.context.selected_objects[0] | |
return None | |
# Returns a list with 2 3D points (Vector) and a color (RGBA) | |
# depending on the current view mode and the selection. | |
def getMeasurePoints(context): | |
sce = context.scene | |
mode = context.mode | |
# Get a single selected object (or nothing). | |
obj = getSingleObject() | |
if mode == 'EDIT_MESH': | |
obj = context.active_object | |
if obj and obj.type == 'MESH' and obj.data: | |
# Get mesh data from Object. | |
mesh = obj.data | |
# Get the selected vertices. | |
# @todo: Better (more efficient) way to do this? | |
verts_selected = [v for v in mesh.vertices if v.select == 1] | |
if len(verts_selected) == 0: | |
# Nothing selected. | |
# We measure the distance from... | |
# local ... the object center to the 3D cursor. | |
# global ... the origin to the 3D cursor. | |
cur_loc = sce.cursor_location | |
obj_loc = obj.matrix_world.to_translation() | |
# Convert to local space, if needed. | |
if measureLocal(sce): | |
p1 = cur_loc | |
p2 = obj_loc | |
return (p1, p2, COLOR_GLOBAL) | |
else: | |
p1 = Vector((0.0, 0.0, 0.0)) | |
p2 = cur_loc | |
return (p1, p2, COLOR_GLOBAL) | |
elif len(verts_selected) == 1: | |
# One vertex selected. | |
# We measure the distance from the | |
# selected vertex object to the 3D cursor. | |
cur_loc = sce.cursor_location | |
vert_loc = verts_selected[0].co.copy() | |
# Convert to local or global space. | |
if measureLocal(sce): | |
p1 = vert_loc | |
p2 = cur_loc | |
return (p1, p2, COLOR_LOCAL) | |
else: | |
p1 = obj.matrix_world * vert_loc | |
p2 = cur_loc | |
return (p1, p2, COLOR_GLOBAL) | |
elif len(verts_selected) == 2: | |
# Two vertices selected. | |
# We measure the distance between the | |
# two selected vertices. | |
obj_loc = obj.matrix_world.to_translation() | |
vert1_loc = verts_selected[0].co.copy() | |
vert2_loc = verts_selected[1].co.copy() | |
# Convert to local or global space. | |
if measureLocal(sce): | |
p1 = vert1_loc | |
p2 = vert2_loc | |
return (p1, p2, COLOR_LOCAL) | |
else: | |
p1 = obj.matrix_world * vert1_loc | |
p2 = obj.matrix_world * vert2_loc | |
return (p1, p2, COLOR_GLOBAL) | |
else: | |
return None | |
elif mode == 'OBJECT': | |
# We are working in object mode. | |
if len(context.selected_objects) > 2: | |
return None | |
elif len(context.selected_objects) == 2: | |
# 2 objects selected. | |
# We measure the distance between the 2 selected objects. | |
obj1, obj2 = context.selected_objects | |
obj1_loc = obj1.matrix_world.to_translation() | |
obj2_loc = obj2.matrix_world.to_translation() | |
return (obj1_loc, obj2_loc, COLOR_GLOBAL) | |
elif obj: | |
# One object selected. | |
# We measure the distance from the object to the 3D cursor. | |
cur_loc = sce.cursor_location | |
obj_loc = obj.matrix_world.to_translation() | |
return (obj_loc, cur_loc, COLOR_GLOBAL) | |
elif not context.selected_objects: | |
# Nothing selected. | |
# We measure the distance from the origin to the 3D cursor. | |
p1 = Vector((0.0, 0.0, 0.0)) | |
p2 = sce.cursor_location | |
return (p1, p2, COLOR_GLOBAL) | |
else: | |
return None | |
# Return the length of an edge (in global space if "obj" is set). | |
# Respects the scaling (via the "obj.matrix_world" parameter). | |
def edgeLengthGlobal(edge, obj, globalSpace): | |
v1, v2 = edge.vertices | |
# Get vertex data | |
v1 = obj.data.vertices[v1] | |
v2 = obj.data.vertices[v2] | |
if globalSpace: | |
mat = obj.matrix_world | |
# Apply transform matrix to vertex coordinates. | |
v1 = mat * v1.co | |
v2 = mat * v2.co | |
else: | |
v1 = v1.co | |
v2 = v2.co | |
return (v1 - v2).length | |
# Calculate the edge length of a mesh object. | |
# *) Set selectedOnly=1 if you only want to count selected edges. | |
# *) Set globalSpace=1 if you want to calculate | |
# the global edge length (object mode). | |
# Note: Be sure you have updated the mesh data before | |
# running this with selectedOnly=1! | |
# @todo Support other object types (surfaces, etc...)? | |
def objectEdgeLength(obj, selectedOnly, globalSpace): | |
if obj and obj.type == 'MESH' and obj.data: | |
edgeTotal = 0 | |
mesh = obj.data | |
# Count the length of all edges. | |
for ed in mesh.edges: | |
if not selectedOnly or ed.select: | |
edgeTotal += edgeLengthGlobal(ed, obj, globalSpace) | |
return edgeTotal | |
# We can not calculate a length for this object. | |
return -1 | |
# Return the area of a face (in global space). | |
# @note Copies the functionality of the following functions, | |
# but also respects the scaling (via the "obj.matrix_world" parameter): | |
# @sa: rna_mesh.c:rna_MeshTessFace_area_get | |
# @sa: math_geom.c:area_quad_v3 | |
# @sa: math_geom.c:area_tri_v3 | |
# @sa: math_geom.c:area_poly_v3 | |
# @todo Fix calculation of "n" for n-gons? | |
def polyAreaGlobal(poly, obj): | |
mesh = obj.data | |
mat = obj.matrix_world.copy() | |
norm = poly.normal | |
area = 0.0 | |
if len(poly.vertices) > 3: | |
# Tesselate the polygon into multiple tris | |
tris = ngon_tessellate(mesh, poly.vertices) | |
for tri in tris: | |
# Get vertex data | |
v1, v2, v3 = tri | |
# Get indices from original poly | |
v1 = poly.vertices[v1] | |
v2 = poly.vertices[v2] | |
v3 = poly.vertices[v3] | |
# Get vertex information from indices | |
v1 = mesh.vertices[v1] | |
v2 = mesh.vertices[v2] | |
v3 = mesh.vertices[v3] | |
# Apply transform matrix to vertex coordinates. | |
v1 = mat * v1.co | |
v2 = mat * v2.co | |
v3 = mat * v3.co | |
# Calculate area for the new tri | |
vec1 = v3 - v2 | |
vec2 = v1 - v2 | |
n = vec1.cross(vec2) | |
area += n.length / 2.0 | |
elif len(poly.vertices) == 3: | |
# Triangle | |
# Get vertex indices | |
v1, v2, v3 = poly.vertices | |
# Get vertex data | |
v1 = mesh.vertices[v1] | |
v2 = mesh.vertices[v2] | |
v3 = mesh.vertices[v3] | |
# Apply transform matrix to vertex coordinates. | |
v1 = mat * v1.co | |
v2 = mat * v2.co | |
v3 = mat * v3.co | |
vec1 = v3 - v2 | |
vec2 = v1 - v2 | |
n = vec1.cross(vec2) | |
area = n.length / 2.0 | |
# Apply rotation and scale to the normal as well. | |
rot_mat = obj.matrix_world.to_quaternion() | |
scale = obj.matrix_world.to_scale() | |
norm = rot_mat * norm | |
norm = Vector(( | |
norm.x * scale.x, | |
norm.y * scale.y, | |
norm.z * scale.z)).normalized() | |
return area, norm | |
# Calculate the surface area of a mesh object. | |
# *) Set selectedOnly=1 if you only want to count selected faces. | |
# *) Set globalSpace=1 if you want to calculate | |
# the global surface area (object mode). | |
# Note: Be sure you have updated the mesh data before | |
# running this with selectedOnly=1! | |
# @todo Support other object types (surfaces, etc...)? | |
def objectSurfaceArea(obj, selectedOnly, globalSpace): | |
if obj and obj.type == 'MESH' and obj.data: | |
areaTotal = 0 | |
normTotal = Vector((0.0, 0.0, 0.0)) | |
mesh = obj.data | |
# Count the area of all the faces. | |
for poly in mesh.polygons: | |
if not selectedOnly or poly.select: | |
if globalSpace: | |
a, n = polyAreaGlobal(poly, obj) | |
areaTotal += a | |
normTotal += n | |
else: | |
areaTotal += poly.area | |
normTotal += poly.normal | |
return areaTotal, normTotal | |
# We can not calculate an area for this object. | |
return -1, Vector((0.0, 0.0, 0.0)) | |
# Calculate the volume of a mesh object. | |
# Copyright Loonsbury ([email protected]) | |
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 | |
# Check if mesh has n-gons | |
if checkNgon(obj): | |
return -2 | |
mesh = obj.data | |
volTot = 0 | |
for poly in mesh.polygons: | |
fzn = poly.normal.z | |
if len(poly.vertices) == 4: | |
v1, v2, v3, v4 = poly.vertices | |
else: | |
v1, v2, v3 = poly.vertices | |
v1 = mesh.vertices[v1] | |
v2 = mesh.vertices[v2] | |
v3 = mesh.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(poly.vertices) == 4: | |
# Get vertex data | |
v4 = mesh.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 -3 | |
# Manifold Checks | |
# Copyright Loonsbury ([email protected]) | |
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 p in mesh.polygons: | |
for ek in p.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 | |
# Check if a mesh has n-gons (polygon with more than 4 edges). | |
def checkNgon(obj): | |
if obj and obj.type == 'MESH' and obj.data: | |
mesh = obj.data | |
for p in mesh.polygons: | |
if len(p.vertices) > 4: | |
return 1 | |
return 0 | |
else: | |
return -1 | |
# User friendly access to the "space" setting. | |
def measureGlobal(sce): | |
return (sce.measure_panel_transform == "measure_global") | |
# User friendly access to the "space" setting. | |
def measureLocal(sce): | |
return (sce.measure_panel_transform == "measure_local") | |
# Calculate values if geometry, selection or cursor changed. | |
@persistent | |
def scene_update(context): | |
sce = context | |
mode = bpy.context.mode | |
if (mode == 'EDIT_MESH' and not sce.measure_panel_update): | |
return | |
if (bpy.data.objects.is_updated | |
or bpy.context.scene.is_updated | |
or sce.measure_panel_update): | |
# TODO: Better way to check selection changes and cursor changes? | |
sel_objs = bpy.context.selected_objects | |
# EDGE LENGTH | |
if sce.measure_panel_calc_edge_length: | |
if (mode == 'EDIT_MESH' | |
and sce.measure_panel_update): | |
sce.measure_panel_update = 0 | |
obj = bpy.context.object | |
#if obj.is_updated: | |
length_total = objectEdgeLength(obj, True, | |
measureGlobal(sce)) | |
sce.measure_panel_edge_length = length_total | |
elif mode == 'OBJECT': | |
length_total = -1 | |
for o in sel_objs: | |
if o.type == 'MESH': | |
length = objectEdgeLength(o, False, measureGlobal(sce)) | |
if length >= 0: | |
if length_total < 0: | |
length_total = 0 | |
length_total += length | |
sce.measure_panel_edge_length = length_total | |
# AREA | |
# Handle mesh surface area calulations | |
if sce.measure_panel_calc_area: | |
if (mode == 'EDIT_MESH' | |
and sce.measure_panel_update): | |
sce.measure_panel_update = 0 | |
obj = bpy.context.active_object | |
if obj and obj.type == 'MESH' and obj.data: | |
# "Note: a Mesh will return the selection state of the mesh | |
# when EditMode was last exited. A Python script operating | |
# in EditMode must exit EditMode before getting the current | |
# selection state of the mesh." | |
# http://www.blender.org/documentation/249PythonDoc/ | |
# /Mesh.MVert-class.html#sel | |
# We can only provide this by existing & | |
# re-entering EditMode. | |
# @todo: Better way to do this? | |
# Get mesh data from Object. | |
me = obj.data | |
# Get transformation matrix from object. | |
ob_mat = obj.matrix_world | |
# Also make an inversed copy! of the matrix. | |
ob_mat_inv = ob_mat.copy() | |
Matrix.invert(ob_mat_inv) | |
# Get the selected vertices. | |
# @todo: Better (more efficient) way to do this? | |
verts_selected = [v for v in me.vertices if v.select == 1] | |
if len(verts_selected) >= 3: | |
# Get selected faces | |
# @todo: Better (more efficient) way to do this? | |
polys_selected = [p for p in me.polygons | |
if p.select == 1] | |
if len(polys_selected) > 0: | |
area, normal = objectSurfaceArea(obj, True, | |
measureGlobal(sce)) | |
if area >= 0.0: | |
sce.measure_panel_area1 = area | |
sce.measure_panel_normal1 = normal | |
elif mode == 'OBJECT': | |
# We are working in object mode. | |
# Get a single selected object (or nothing). | |
obj = getSingleObject() | |
if len(sel_objs) > 2: | |
return | |
# @todo Make this work again. | |
# # We have more that 2 objects selected... | |
# | |
# mesh_objects = [o for o in context.selected_objects | |
# if o.type == 'MESH'] | |
# if len(mesh_objects) > 0: | |
# # ... and at least one of them is a mesh. | |
# | |
# for o in mesh_objects: | |
# area = objectSurfaceArea(o, False, | |
# measureGlobal(sce)) | |
# if area >= 0: | |
# #row.label(text=o.name, icon='OBJECT_DATA') | |
# #row.label(text=str(round(area, PRECISION)) | |
# # + " BU^2") | |
elif len(sel_objs) == 2: | |
# 2 objects selected. | |
obj1, obj2 = sel_objs | |
# Calculate surface area of the objects. | |
area1, normal1 = objectSurfaceArea(obj1, False, | |
measureGlobal(sce)) | |
area2, normal2 = objectSurfaceArea(obj2, False, | |
measureGlobal(sce)) | |
sce.measure_panel_area1 = area1 | |
sce.measure_panel_area2 = area2 | |
sce.measure_panel_normal1 = normal1 | |
sce.measure_panel_normal2 = normal2 | |
elif obj: | |
# One object selected. | |
# Calculate surface area of the object. | |
area, normal = objectSurfaceArea(obj, False, | |
measureGlobal(sce)) | |
sce.measure_panel_area1 = area | |
sce.measure_panel_normal1 = normal | |
# VOLUME | |
# Handle mesh volume calulations. | |
if sce.measure_panel_calc_volume: | |
obj = getSingleObject() | |
if mode == 'OBJECT': | |
# We are working in object mode. | |
#if len(sel_objs) > 2: # TODO | |
#el | |
if len(sel_objs) == 2: | |
# 2 objects selected. | |
obj1, obj2 = sel_objs | |
# 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 | |
def draw_measurements_callback(self, context): | |
sce = context.scene | |
draw = 0 | |
if hasattr(sce, "measure_panel_draw"): | |
draw = sce.measure_panel_draw | |
# 2D drawing code example | |
#bgl.glBegin(bgl.GL_LINE_STRIP) | |
#bgl.glVertex2i(0, 0) | |
#bgl.glVertex2i(80, 100) | |
#bgl.glEnd() | |
# Get measured 3D points and colors. | |
line = getMeasurePoints(context) | |
if line: | |
p1, p2, color = line | |
dist = (p1 - p2).length | |
# Write distance value into the scene property, | |
# so we can display it in the panel & refresh the panel. | |
if hasattr(sce, "measure_panel_dist"): | |
sce.measure_panel_dist = dist | |
context.area.tag_redraw() | |
if draw: | |
# Get & convert the Perspective Matrix of the current view/region. | |
view3d = bpy.context | |
region = view3d.region_data | |
perspMatrix = region.perspective_matrix | |
tempMat = [perspMatrix[j][i] for i in range(4) for j in range(4)] | |
perspBuff = bgl.Buffer(bgl.GL_FLOAT, 16, tempMat) | |
# --- | |
# Store previous OpenGL settings. | |
# Store MatrixMode | |
MatrixMode_prev = bgl.Buffer(bgl.GL_INT, [1]) | |
bgl.glGetIntegerv(bgl.GL_MATRIX_MODE, MatrixMode_prev) | |
MatrixMode_prev = MatrixMode_prev[0] | |
# Store projection matrix | |
ProjMatrix_prev = bgl.Buffer(bgl.GL_DOUBLE, [16]) | |
bgl.glGetFloatv(bgl.GL_PROJECTION_MATRIX, ProjMatrix_prev) | |
# Store Line width | |
lineWidth_prev = bgl.Buffer(bgl.GL_FLOAT, [1]) | |
bgl.glGetFloatv(bgl.GL_LINE_WIDTH, lineWidth_prev) | |
lineWidth_prev = lineWidth_prev[0] | |
# Store GL_BLEND | |
blend_prev = bgl.Buffer(bgl.GL_BYTE, [1]) | |
bgl.glGetFloatv(bgl.GL_BLEND, blend_prev) | |
blend_prev = blend_prev[0] | |
line_stipple_prev = bgl.Buffer(bgl.GL_BYTE, [1]) | |
bgl.glGetFloatv(bgl.GL_LINE_STIPPLE, line_stipple_prev) | |
line_stipple_prev = line_stipple_prev[0] | |
# Store glColor4f | |
color_prev = bgl.Buffer(bgl.GL_FLOAT, [4]) | |
bgl.glGetFloatv(bgl.GL_COLOR, color_prev) | |
# --- | |
# Prepare for 3D drawing | |
bgl.glLoadIdentity() | |
bgl.glMatrixMode(bgl.GL_PROJECTION) | |
bgl.glLoadMatrixf(perspBuff) | |
bgl.glEnable(bgl.GL_BLEND) | |
bgl.glEnable(bgl.GL_LINE_STIPPLE) | |
# --- | |
# Draw 3D stuff. | |
bgl.glLineWidth(LINE_WIDTH_XYZ) | |
# X | |
bgl.glColor4f(1, 0, 0, 0.8) | |
bgl.glBegin(bgl.GL_LINE_STRIP) | |
bgl.glVertex3f(p1[0], p1[1], p1[2]) | |
bgl.glVertex3f(p2[0], p1[1], p1[2]) | |
bgl.glEnd() | |
# Y | |
bgl.glColor4f(0, 1, 0, 0.8) | |
bgl.glBegin(bgl.GL_LINE_STRIP) | |
bgl.glVertex3f(p1[0], p1[1], p1[2]) | |
bgl.glVertex3f(p1[0], p2[1], p1[2]) | |
bgl.glEnd() | |
# Z | |
bgl.glColor4f(0, 0, 1, 0.8) | |
bgl.glBegin(bgl.GL_LINE_STRIP) | |
bgl.glVertex3f(p1[0], p1[1], p1[2]) | |
bgl.glVertex3f(p1[0], p1[1], p2[2]) | |
bgl.glEnd() | |
# Dist | |
bgl.glLineWidth(LINE_WIDTH_DIST) | |
bgl.glColor4f(color[0], color[1], color[2], color[3]) | |
bgl.glBegin(bgl.GL_LINE_STRIP) | |
bgl.glVertex3f(p1[0], p1[1], p1[2]) | |
bgl.glVertex3f(p2[0], p2[1], p2[2]) | |
bgl.glEnd() | |
# --- | |
# Restore previous OpenGL settings | |
bgl.glLoadIdentity() | |
bgl.glMatrixMode(MatrixMode_prev) | |
bgl.glLoadMatrixf(ProjMatrix_prev) | |
bgl.glLineWidth(lineWidth_prev) | |
if not blend_prev: | |
bgl.glDisable(bgl.GL_BLEND) | |
if not line_stipple_prev: | |
bgl.glDisable(bgl.GL_LINE_STIPPLE) | |
bgl.glColor4f( | |
color_prev[0], | |
color_prev[1], | |
color_prev[2], | |
color_prev[3]) | |
# --- | |
# Draw (2D) text | |
# We do this after drawing the lines so | |
# we can draw it OVER the line. | |
coord_2d = location_3d_to_region_2d( | |
context.region, | |
context.space_data.region_3d, | |
p1.lerp(p2, 0.5)) | |
texts = [ | |
("Dist:", dist), | |
("X:", abs(p1[0] - p2[0])), | |
("Y:", abs(p1[1] - p2[1])), | |
("Z:", abs(p1[2] - p2[2]))] | |
# Draw all texts | |
# @todo Get user pref for text color in 3D View | |
bgl.glColor4f(1.0, 1.0, 1.0, 1.0) | |
blf.size(0, 12, 72) # Prevent font size to randomly change. | |
uinfo = getUnitsInfo() | |
loc_x = coord_2d[0] + OFFSET_LINE | |
loc_y = coord_2d[1] | |
for t in texts: | |
text = t[0] | |
value = convertDistance(t[1], uinfo) | |
blf.position(0, loc_x, loc_y, 0) | |
blf.draw(0, text) | |
blf.position(0, loc_x + OFFSET_VALUE, loc_y, 0) | |
blf.draw(0, value) | |
loc_y -= OFFSET_Y | |
# Callback code Daniel Ashby 2014-10-30 | |
class VIEW3D_OT_display_measurements(bpy.types.Operator): | |
"""Display the measurements made in the 'Measure' panel""" | |
bl_idname = "view3d.display_measurements" | |
bl_label = "Display measurements" | |
bl_description = "Display the measurements made in the" \ | |
" 'Measure' panel in the 3D View" | |
bl_options = {'REGISTER'} # TODO: can this be removed? | |
_handle = None | |
@staticmethod | |
def handle_add(self, context): | |
VIEW3D_OT_display_measurements._handle \ | |
= bpy.types.SpaceView3D.draw_handler_add( | |
draw_measurements_callback, | |
(self, context), | |
'WINDOW', 'POST_PIXEL') | |
@staticmethod | |
def handle_remove(context): | |
if VIEW3D_OT_display_measurements._handle is not None: | |
bpy.types.SpaceView3D.draw_handler_remove( | |
VIEW3D_OT_display_measurements._handle, | |
'WINDOW') | |
VIEW3D_OT_display_measurements._handle = None | |
def modal(self, context, event): | |
if context.area: | |
context.area.tag_redraw | |
if not context.window_manager.display_measurements_runstate: | |
#stop script | |
VIEW3D_OT_display_measurements.handle_remove(context) | |
return {'CANCELLED'} | |
return {'PASS_THROUGH'} | |
def cancel(self, context): | |
if context.window_manager.display_measurements_runstate: | |
display_measurements.handle_remove(context) | |
context.window_manager.display_measurements_runstate = False | |
return {'CANCELLED'} | |
def invoke(self, context, event): | |
if context.area.type == 'VIEW_3D': | |
if context.window_manager.display_measurements_runstate is False: | |
# operator is called for the first time, start everything | |
context.window_manager.display_measurements_runstate = True | |
VIEW3D_OT_display_measurements.handle_add(self, context) | |
context.window_manager.modal_handler_add(self) | |
return {'RUNNING_MODAL'} | |
else: | |
# operator is called again, stop displaying | |
context.window_manager.display_measurements_runstate = False | |
return {'CANCELLED'} | |
else: | |
self.report({'WARNING'}, "3D View not found, can't run operator" | |
" for 'Display measurements'") | |
return {'CANCELLED'} | |
class VIEW3D_OT_activate_measure_panel(bpy.types.Operator): | |
bl_label = "Activate" | |
bl_idname = "view3d.activate_measure_panel" | |
bl_description = "Activate the callback needed to draw the lines" | |
bl_options = {'REGISTER'} | |
def invoke(self, context, event): | |
# Execute operator (this adds the callback) | |
# if it wasn't done yet. | |
bpy.ops.view3d.display_measurements() | |
return {'FINISHED'} | |
class VIEW3D_OT_reenter_editmode(bpy.types.Operator): | |
bl_label = "Re-enter EditMode" | |
bl_idname = "view3d.reenter_editmode" | |
bl_description = "Update mesh data of an active mesh object " \ | |
"(this is done by exiting and re-entering mesh edit mode)" | |
bl_options = {'REGISTER'} | |
def invoke(self, context, event): | |
# Get the active object. | |
obj = context.active_object | |
sce = context.scene | |
if obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH': | |
# Exit and re-enter mesh EditMode. | |
bpy.ops.object.mode_set(mode='OBJECT') | |
bpy.ops.object.mode_set(mode='EDIT') | |
sce.measure_panel_update = 1 | |
return {'FINISHED'} | |
return {'CANCELLED'} | |
class VIEW3D_PT_measure(bpy.types.Panel): | |
bl_space_type = 'VIEW_3D' | |
bl_region_type = 'UI' | |
bl_label = "Measure" | |
bl_options = {'DEFAULT_CLOSED'} | |
@classmethod | |
def poll(cls, context): | |
# Only display this panel in the object and edit mode 3D view. | |
mode = context.mode | |
if (context.area.type == 'VIEW_3D' and | |
(mode == 'EDIT_MESH' or mode == 'OBJECT')): | |
return 1 | |
return 0 | |
def draw_header(self, context): | |
layout = self.layout | |
sce = context.scene | |
if not context.window_manager.display_measurements_runstate: | |
layout.operator("view3d.display_measurements", text="Activate", | |
icon="PLAY") | |
def draw(self, context): | |
layout = self.layout | |
sce = context.scene | |
mode = context.mode | |
# Get a single selected object (or nothing). | |
obj = getSingleObject() | |
drawTansformButtons = 1 | |
if mode == 'EDIT_MESH': | |
obj = context.active_object | |
row = layout.row() | |
row.operator("view3d.reenter_editmode", | |
text="Update selection") | |
# @todo | |
# description="The calculated values can" \ | |
# " not be updated in mesh edit mode" \ | |
# " automatically. Press this button" \ | |
# " to do this manually, after you changed" \ | |
# " the selection") | |
if obj and obj.type == 'MESH' and obj.data: | |
# "Note: a Mesh will return the selection state of the mesh | |
# when EditMode was last exited. A Python script operating | |
# in EditMode must exit EditMode before getting the current | |
# selection state of the mesh." | |
# http://www.blender.org/documentation/249PythonDoc/ | |
# /Mesh.MVert-class.html#sel | |
# We can only provide this by existing & re-entering EditMode. | |
# @todo: Better way to do this? | |
# Get mesh data from Object. | |
mesh = obj.data | |
# Get transformation matrix from object. | |
ob_mat = obj.matrix_world | |
# Also make an inversed copy! of the matrix. | |
ob_mat_inv = ob_mat.copy() | |
Matrix.invert(ob_mat_inv) | |
# Get the selected vertices. | |
# @todo: Better (more efficient) way to do this? | |
verts_selected = [v for v in mesh.vertices if v.select == 1] | |
if len(verts_selected) == 0: | |
# Nothing selected. | |
# We measure the distance from... | |
# local ... the object center to the 3D cursor. | |
# global ... the origin to the 3D cursor. | |
layout.label(text="Distance") | |
box = layout.box() | |
row = box.row() | |
row.prop(sce, "measure_panel_dist") | |
row = box.row() | |
row.label(text="", icon='CURSOR') | |
row.label(text="", icon='ARROW_LEFTRIGHT') | |
if measureLocal(sce): | |
row.label(text="Obj. Center") | |
else: | |
row.label(text="Origin [0,0,0]") | |
layout.prop(sce, "measure_panel_draw") | |
elif len(verts_selected) == 1: | |
# One vertex selected. | |
# We measure the distance from the | |
# selected vertex object to the 3D cursor. | |
layout.label(text="Distance") | |
box = layout.box() | |
row = box.row() | |
row.prop(sce, "measure_panel_dist") | |
row = box.row() | |
row.label(text="", icon='CURSOR') | |
row.label(text="", icon='ARROW_LEFTRIGHT') | |
row.label(text="", icon='VERTEXSEL') | |
layout.prop(sce, "measure_panel_draw") | |
elif len(verts_selected) == 2: | |
# Two vertices selected. | |
# We measure the distance between the | |
# two selected vertices. | |
layout.label(text="Distance") | |
box = layout.box() | |
row = box.row() | |
row.prop(sce, "measure_panel_dist") | |
row = box.row() | |
row.label(text="", icon='VERTEXSEL') | |
row.label(text="", icon='ARROW_LEFTRIGHT') | |
row.label(text="", icon='VERTEXSEL') | |
layout.prop(sce, "measure_panel_draw") | |
edges_selected = [ed for ed in mesh.edges if ed.select == 1] | |
if len(edges_selected) >= 1: | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_edge_length", | |
text="Edge Length (selected edges)") | |
if sce.measure_panel_calc_edge_length: | |
if sce.measure_panel_edge_length >= 0: | |
box = layout.box() | |
row = box.row() | |
row.label( | |
text=str(len(edges_selected)), | |
icon='EDGESEL') | |
row = box.row() | |
row.label(text="Length") | |
row.prop(sce, "measure_panel_edge_length") | |
if len(verts_selected) > 2: | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_area", | |
text="Surface area (selected faces)") | |
if sce.measure_panel_calc_area: | |
# Get selected faces | |
# @todo: Better (more efficient) way to do this? | |
polys_selected = [p for p in mesh.polygons | |
if p.select == 1] | |
if len(polys_selected) > 0: | |
if sce.measure_panel_area1 >= 0: | |
box = layout.box() | |
row = box.row() | |
row.label( | |
text=str(len(polys_selected)), | |
icon='FACESEL') | |
row = box.row() | |
row.label(text="Area") | |
row.prop(sce, "measure_panel_area1") | |
row = box.row() | |
row.label(text="Normal") | |
row = box.row() | |
row.prop(sce, "measure_panel_normal1") | |
else: | |
row = layout.row() | |
row.label(text="Selection not supported", | |
icon='INFO') | |
if drawTansformButtons: | |
row = layout.row() | |
row.prop(sce, | |
"measure_panel_transform", | |
expand=True) | |
elif mode == 'OBJECT': | |
# We are working in object mode. | |
mesh_objects = [o for o in context.selected_objects | |
if o.type == 'MESH'] | |
if len(context.selected_objects) > 2: | |
# We have more that 2 objects selected... | |
# EDGES | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_edge_length", | |
text="Edge Length") | |
if sce.measure_panel_calc_edge_length: | |
if len(mesh_objects) > 0: | |
box = layout.box() | |
row = box.row() | |
row.label(text="Total edge length") | |
row.prop(sce, "measure_panel_edge_length") | |
# AREA | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_area", | |
text="Surface area") | |
if sce.measure_panel_calc_area: | |
if len(mesh_objects) > 0: | |
# ... and at least one of them is a mesh. | |
# Calculate and display surface area of the objects. | |
# @todo: Convert to scene units! We do not have a | |
# FloatProperty field here for automatic conversion. | |
row = layout.row() | |
row.label(text="Multiple objects not yet supported", | |
icon='INFO') | |
row = layout.row() | |
row.label(text="(= More than two meshes)", | |
icon='INFO') | |
# @todo Make this work again. | |
# for o in mesh_objects: | |
# area = objectSurfaceArea(o, False, | |
# measureGlobal(sce)) | |
# if area >= 0: | |
# row = layout.row() | |
# row.label(text=o.name, icon='OBJECT_DATA') | |
# row.label(text=str(round(area, PRECISION)) | |
# + " BU^2") | |
elif len(context.selected_objects) == 2: | |
# 2 objects selected. | |
# We measure the distance between the 2 selected objects. | |
layout.label(text="Distance") | |
obj1, obj2 = context.selected_objects | |
box = layout.box() | |
row = box.row() | |
row.prop(sce, "measure_panel_dist") | |
row = box.row() | |
row.label(text="", icon='OBJECT_DATA') | |
row.prop(obj1, "name", text="") | |
row.label(text="", icon='ARROW_LEFTRIGHT') | |
row.label(text="", icon='OBJECT_DATA') | |
row.prop(obj2, "name", text="") | |
layout.prop(sce, "measure_panel_draw") | |
# EDGES | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_edge_length", | |
text="Edge Length") | |
if sce.measure_panel_calc_edge_length: | |
if sce.measure_panel_edge_length >= 0: | |
if len(mesh_objects) > 0: | |
box = layout.box() | |
row = box.row() | |
row.label(text="Total edge length") | |
row.prop(sce, "measure_panel_edge_length") | |
# AREA | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_area", | |
text="Surface area") | |
if sce.measure_panel_calc_area: | |
# Display surface area of the objects. | |
if (sce.measure_panel_area1 >= 0 | |
or sce.measure_panel_area2 >= 0): | |
if sce.measure_panel_area1 >= 0: | |
box = layout.box() | |
row = box.row() | |
row.label(text=obj1.name, icon='OBJECT_DATA') | |
row = box.row() | |
row.label(text="Area") | |
row.prop(sce, "measure_panel_area1") | |
row = box.row() | |
row.label(text="Normal") | |
row = box.row() | |
row.prop(sce, "measure_panel_normal1") | |
if sce.measure_panel_area2 >= 0: | |
box = layout.box() | |
row = box.row() | |
row.label(text=obj2.name, icon='OBJECT_DATA') | |
row = box.row() | |
row.label(text="Area") | |
row.prop(sce, "measure_panel_area2") | |
row = box.row() | |
row.label(text="Normal") | |
row = box.row() | |
row.prop(sce, "measure_panel_normal2") | |
# VOL | |
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 >= -2: | |
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") | |
elif sce.measure_panel_volume1 >= -1: | |
row = box.row() | |
row.label(text="Mesh is non-manifold!", | |
icon='INFO') | |
else: # -2 | |
row = box.row() | |
row.label(text="Mesh has n-gons (faces with " \ | |
"more than 4 edges)!", | |
icon='INFO') | |
if sce.measure_panel_volume2 >= -2: | |
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") | |
elif sce.measure_panel_volume2 >= -1: | |
row = box.row() | |
row.label(text="Mesh is non-manifold!", | |
icon='INFO') | |
else: # -2 | |
row = box.row() | |
row.label(text="Mesh has n-gons (faces with " \ | |
"more than 4 edges)!", | |
icon='INFO') | |
elif obj: | |
# One object selected. | |
# We measure the distance from the object to the 3D cursor. | |
layout.label(text="Distance") | |
box = layout.box() | |
row = box.row() | |
row.prop(sce, "measure_panel_dist") | |
row = box.row() | |
row.label(text="", icon='CURSOR') | |
row.label(text="", icon='ARROW_LEFTRIGHT') | |
row.label(text="", icon='OBJECT_DATA') | |
row.prop(obj, "name", text="") | |
layout.prop(sce, "measure_panel_draw") | |
# EDGES | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_edge_length", | |
text="Edge Length") | |
if sce.measure_panel_calc_edge_length: | |
if sce.measure_panel_edge_length >= 0: | |
if len(mesh_objects) > 0: | |
box = layout.box() | |
row = box.row() | |
row.label(text="Total edge length") | |
row.prop(sce, "measure_panel_edge_length") | |
# AREA | |
row = layout.row() | |
row.prop(sce, "measure_panel_calc_area", | |
text="Surface area") | |
if sce.measure_panel_calc_area: | |
# Display surface area of the object. | |
if sce.measure_panel_area1 >= 0.0: | |
box = layout.box() | |
row = box.row() | |
row.label(text=obj.name, icon='OBJECT_DATA') | |
row = box.row() | |
row.label(text="Area") | |
row.prop(sce, "measure_panel_area1") | |
row = box.row() | |
row.label(text="Normal") | |
row = box.row() | |
row.prop(sce, "measure_panel_normal1") | |
# VOL | |
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 >= -2: | |
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") | |
elif sce.measure_panel_volume1 >= -1: | |
row = box.row() | |
row.label(text="Mesh is non-manifold!", | |
icon='INFO') | |
else: # -2 | |
row = box.row() | |
row.label(text="Mesh has n-gons (faces with " \ | |
"more than 4 edges)!", | |
icon='INFO') | |
elif not context.selected_objects: | |
# Nothing selected. | |
# We measure the distance from the origin to the 3D cursor. | |
layout.label(text="Distance") | |
box = layout.box() | |
row = box.row() | |
row.prop(sce, "measure_panel_dist") | |
row = box.row() | |
row.label(text="", icon='CURSOR') | |
row.label(text="", icon='ARROW_LEFTRIGHT') | |
row.label(text="Origin [0,0,0]") | |
layout.prop(sce, "measure_panel_draw") | |
else: | |
row = layout.row() | |
row.label(text="Selection not supported", | |
icon='INFO') | |
if drawTansformButtons: | |
row = layout.row() | |
row.prop(sce, | |
"measure_panel_transform", | |
expand=True) | |
classes = ( | |
VIEW3D_OT_display_measurements, | |
VIEW3D_OT_reenter_editmode, | |
VIEW3D_PT_measure) | |
def register(): | |
bpy.app.handlers.scene_update_post.append(scene_update) | |
# Define a temporary attribute for the distance value | |
bpy.types.Scene.measure_panel_dist = bpy.props.FloatProperty( | |
name="Distance", | |
precision=PRECISION, | |
unit="LENGTH") | |
bpy.types.Scene.measure_panel_edge_length = bpy.props.FloatProperty( | |
name="", | |
precision=PRECISION, | |
unit="LENGTH") | |
bpy.types.Scene.measure_panel_area1 = bpy.props.FloatProperty( | |
name="", | |
precision=PRECISION, | |
unit="AREA") | |
bpy.types.Scene.measure_panel_area2 = bpy.props.FloatProperty( | |
name="", | |
precision=PRECISION, | |
unit="AREA") | |
bpy.types.Scene.measure_panel_normal1 = bpy.props.FloatVectorProperty( | |
name="", | |
precision=PRECISION, | |
subtype="XYZ") | |
bpy.types.Scene.measure_panel_normal2 = bpy.props.FloatVectorProperty( | |
name="", | |
precision=PRECISION, | |
subtype="XYZ") | |
bpy.types.Scene.measure_panel_volume1 = bpy.props.FloatProperty( | |
name="", | |
precision=PRECISION, | |
unit="VOLUME") | |
bpy.types.Scene.measure_panel_volume2 = bpy.props.FloatProperty( | |
name="", | |
precision=PRECISION, | |
unit="VOLUME") | |
TRANSFORM = [ | |
("measure_global", "Global", | |
"Calculate values in global space"), | |
("measure_local", "Local", | |
"Calculate values inside the local object space")] | |
# Define dropdown for the global/local setting | |
bpy.types.Scene.measure_panel_transform = bpy.props.EnumProperty( | |
name="Space", | |
description="Choose in which space you want to measure", | |
items=TRANSFORM, | |
default='measure_global') | |
# Define property for the draw setting. | |
bpy.types.Scene.measure_panel_draw = bpy.props.BoolProperty( | |
name="Draw distance", | |
description="Draw distances in 3D View", | |
default=1) | |
bpy.types.Scene.measure_panel_calc_edge_length = bpy.props.BoolProperty( | |
description="Calculate total length of (selected) edges", | |
default=0) | |
# Define property for the calc-area setting. | |
# @todo prevent double calculations for each refresh automatically? | |
bpy.types.Scene.measure_panel_calc_area = bpy.props.BoolProperty( | |
description="Calculate mesh surface area (heavy CPU " | |
"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) | |
# Define dropdown for the global/local setting | |
bpy.types.Scene.measure_panel_update = bpy.props.BoolProperty( | |
description="Update CPU heavy calculations", | |
default=0) | |
# Callback code Daniel Ashby 2014-10-30 | |
# Runstate initially always set to False | |
# note: it is not stored in the Scene, but in window manager: | |
wm = bpy.types.WindowManager | |
wm.display_measurements_runstate = bpy.props.BoolProperty(default=False) | |
for c in classes: | |
bpy.utils.register_class(c) | |
def unregister(): | |
bpy.app.handlers.scene_update_post.remove(scene_update) | |
VIEW3D_OT_display_measurements.handle_remove(bpy.context) | |
for c in classes: | |
bpy.utils.unregister_class(c) | |
# Remove properties. | |
del bpy.types.Scene.measure_panel_dist | |
del bpy.types.Scene.measure_panel_edge_length | |
del bpy.types.Scene.measure_panel_area1 | |
del bpy.types.Scene.measure_panel_area2 | |
del bpy.types.Scene.measure_panel_normal1 | |
del bpy.types.Scene.measure_panel_normal2 | |
del bpy.types.Scene.measure_panel_volume1 | |
del bpy.types.Scene.measure_panel_volume2 | |
del bpy.types.Scene.measure_panel_transform | |
del bpy.types.Scene.measure_panel_draw | |
del bpy.types.Scene.measure_panel_calc_edge_length | |
del bpy.types.Scene.measure_panel_calc_area | |
del bpy.types.Scene.measure_panel_calc_volume | |
del bpy.types.Scene.measure_panel_update | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment