mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00
1113 lines
40 KiB
Python
1113 lines
40 KiB
Python
# ##### 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 #####
|
|
|
|
bl_info = {
|
|
"name": "Measure Panel",
|
|
"author": "Buerbaum Martin (Pontiac)",
|
|
"version": (0, 7, 12),
|
|
"blender": (2, 5, 5),
|
|
"api": 33931,
|
|
"location": "View3D > Properties > Measure",
|
|
"description": "Measure distances between objects",
|
|
"warning": "",
|
|
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/" \
|
|
"Scripts/3D_interaction/Panel_Measure",
|
|
"tracker_url": "https://projects.blender.org/tracker/index.php?" \
|
|
"func=detail&aid=21445",
|
|
"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.
|
|
|
|
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.
|
|
|
|
Version history:
|
|
v0.7.12 - Moved setting of properties to callback function
|
|
(it is bad practise to set it in the draw code).
|
|
Fixed distance calculation of parented objects.
|
|
API change: add_modal_handler -> modal_handler_add
|
|
Regression: Had to disable area display for selection with
|
|
more than 2 meshes.
|
|
Fixed Local/Global vert-loc calculations in EditMode.
|
|
v0.7.11 - Applied patch by Filiciss Muhgue that fixes the text in quad view.
|
|
v0.7.10 - Applied patch by Filiciss Muhgue that (mostly) fixes the quad view.
|
|
Patch link: https://projects.blender.org/tracker/?func=
|
|
detail&atid=127&aid=24932&group_id=9
|
|
Thanks for that!
|
|
Removed (now) unneeded "attr" setting for properties.
|
|
v0.7.9 - Updated scene properties for changes in property API.
|
|
See http://lists.blender.org/pipermail/bf-committers/
|
|
2010-September/028654.html
|
|
Synced API changes in/from local copy.
|
|
v0.7.8 - Various Py API changes by Campbell ...
|
|
bl_default_closed -> bl_options = {'DEFAULT_CLOSED'}
|
|
x.verts -> x.vertices
|
|
@classmethod def poll(cls, context)
|
|
No "location" in bl_info->name
|
|
bl_info->api
|
|
v0.7.7 - One more change to the callback registration code.
|
|
Now it should finally work as intended.
|
|
v0.7.6 - API changes (r885, r886) - register & unregister function
|
|
v0.7.5.3 - Small fix for bug in v0.7.5.1
|
|
(location was off when object was moved)
|
|
v0.7.5.2 - Changed callback registration back to original code &
|
|
fixed bug in there (use bl_idname instead of bl_label)
|
|
v0.7.5.1 - Global mode is now taking rotation into account properly.
|
|
v0.7.5 - Fixed lagging and drawing issues.
|
|
v0.7.4 - Fixed the modal_handler_add and callback_add code.
|
|
Thanks to jesterKing for pointing that out :-)
|
|
v0.7.3.1 - Fixed bug that made all lines in Blender stippled :-)
|
|
v0.7.3 - Added display of delta x/y/z value in 3d view.
|
|
* Inspired by warpi's patch here:
|
|
http://blenderartists.org/forum/showpost.php?p=1671033&postcount=47
|
|
* Also added display of dx,dy,dz lines
|
|
* Changed the "dist" colors to something not already used
|
|
by x/y/z axes.
|
|
v0.7.2 - Merged changes from trunk (scripts_addons r847):
|
|
* obj.matrix -> obj.matrix_world
|
|
* vert.selected -> vert.select
|
|
* face.selected -> face.select
|
|
* bl_info: warning, wiki_url, tracker_url
|
|
* removed __bpydoc__
|
|
* Use fontid=0 for blf functions. 0 is the default font.
|
|
v0.7.1 - Merged changes by Campbell:
|
|
* Fix for API change: Collections like context.selected_objects
|
|
no longer return None for empty lists.
|
|
* Update for mathutils, also stripped some redundant
|
|
conversions (Mostly "Vector()" stuff)
|
|
v0.7 - Initial support for drawing lines.
|
|
(Thanks to Algorith for applying my perspective_matrix patch.)
|
|
The distance value (in BUs) is also drawn in the 3D view now.
|
|
Also fixed some wrong calculations of global/local distances.
|
|
Now it's really "what you see is what is calculated".
|
|
Use bl_info for Add-On information.
|
|
Use "3D View" in category & name
|
|
Renamed reenter_editmode to view3d.reenter_editmode.
|
|
Renamed panel_measure.py into space_view3d_panel_measure.py
|
|
Active object is only used for edit-mode now. Measurement
|
|
with exactly one sel. (but not necessarily active) object
|
|
now gets the obj via the sel-object array.
|
|
API change Mathutils -> mathutils (r557)
|
|
Deselecting 1 of 2 objects now works correctly (active object is ignored).
|
|
Force a redraw of the area so disabling the "measure_panel_draw"
|
|
checkbox will clear the line/text.
|
|
Only calculate area (CPU heavy) if a "area" checkbox is enabled.
|
|
v0.6.4 - Fixed unneeded meshdata duplication (sometimes crashes Blender).
|
|
The script now correctly calculated the surface area (faceAreaGlobal)
|
|
of scaled meshes.
|
|
http://projects.blender.org/tracker/
|
|
?func=detail&atid=453&aid=21913&group_id=153
|
|
v0.6.3 - Added register & unregister functions.
|
|
v0.6.2 - Fixed precision of second area property.
|
|
Reduced display precision to 5 (instead of 6).
|
|
Added (commented out code) for shortcut [F5] for
|
|
updating EditMode selection & calculation.
|
|
Changed the script so it can be managed from the "Add-Ons" tab
|
|
in the user preferences.
|
|
Corrected FSF address.
|
|
v0.6.1 - Updated reenter_editmode operator description.
|
|
Fixed search for selected mesh objects.
|
|
Added "BU^2" after values that are not yet translated via "unit".
|
|
v0.6
|
|
*) Fix: Removed EditMode/ObjectMode toggle stuff. This causes all the
|
|
crashes and is generally not stable.
|
|
Instead I've added a manual "refresh" button.
|
|
I registered a new operator OBJECT_OT_reenter_editmode for this.
|
|
*) Use "unit" settings (i.e. none/metric/imperial)
|
|
*) Fix: Only display surface area (>=3 objects) if return value is >=0.
|
|
*) Minor: Renamed objectFaceArea to objectSurfaceArea
|
|
*) Updated Vector() and tuple() usage.
|
|
*) Fixed some comments.
|
|
v0.5 - Global surface area (object mode) is now calculated as well.
|
|
Support area calculation for face selection.
|
|
Also made measurement panel closed by default. (Area calculation
|
|
may use up a lot of CPU/RAM in extreme cases)
|
|
v0.4.1 - Various cleanups.
|
|
Using the shorter "scene" instead of "context.scene"
|
|
New functions measureGlobal() and measureLocal() for
|
|
user-friendly access to the "space" setting.
|
|
v0.4 - Calculate & display the surface area of mesh
|
|
objects (local space only right now).
|
|
Expanded global/local switch.
|
|
Made "local" option for 3Dcursor-only in edit mode actually work.
|
|
Fixed local/global calculation for 3Dcursor<->vertex in edit mode.
|
|
v0.3.2 - Fixed calculation & display of local/global coordinates.
|
|
The user can now select via dropdown which space is wanted/needed
|
|
Basically this is a bugfix and new feature at the same time :-)
|
|
v0.3.1 - Fixed bug where "measure_panel_dist" wasn't defined
|
|
before it was used.
|
|
Also added the distance calculation "origin -> 3D cursor" for edit mode.
|
|
v0.3 - Support for mesh edit mode (1 or 2 selected vertices)
|
|
v0.2.1 - Small fix (selecting nothing didn't calculate the distance
|
|
of the cursor from the origin anymore)
|
|
v0.2 - Distance value is now displayed via a FloatProperty widget (and
|
|
therefore saved to file too right now [according to ideasman42].
|
|
The value is save inside the scene right now.)
|
|
Thanks goes to ideasman42 (Campbell Barton) for helping me out on this.
|
|
v0.1 - Initial revision. Seems to work fine for most purposes.
|
|
|
|
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 mathutils import Vector, Matrix
|
|
import bgl
|
|
import blf
|
|
|
|
|
|
# Precicion for display of float values.
|
|
PRECISION = 4
|
|
|
|
# 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)
|
|
|
|
|
|
# Returns a single selected object.
|
|
# Returns None if more than one (or nothing) is selected.
|
|
# Note: Ignores the active object.
|
|
def getSingleObject(context):
|
|
if len(context.selected_objects) == 1:
|
|
return 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
|
|
|
|
# Get a single selected object (or nothing).
|
|
obj = getSingleObject(context)
|
|
|
|
if (context.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.translation_part()
|
|
|
|
# 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 = vert_loc * obj.matrix_world
|
|
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.translation_part()
|
|
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 = vert1_loc * obj.matrix_world
|
|
p2 = vert2_loc * obj.matrix_world
|
|
return (p1, p2, COLOR_GLOBAL)
|
|
|
|
else:
|
|
return None
|
|
|
|
elif (context.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.translation_part()
|
|
obj2_loc = obj2.matrix_world.translation_part()
|
|
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.translation_part()
|
|
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 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_MeshFace_area_get
|
|
# @sa: math_geom.c:area_quad_v3
|
|
# @sa: math_geom.c:area_tri_v3
|
|
def faceAreaGlobal(face, obj):
|
|
area = 0.0
|
|
|
|
mat = obj.matrix_world
|
|
|
|
if len(face.vertices) == 4:
|
|
# Quad
|
|
|
|
# Get vertex indices
|
|
v1, v2, v3, v4 = face.vertices
|
|
|
|
# Get vertex data
|
|
v1 = obj.data.vertices[v1]
|
|
v2 = obj.data.vertices[v2]
|
|
v3 = obj.data.vertices[v3]
|
|
v4 = obj.data.vertices[v4]
|
|
|
|
# Apply transform matrix to vertex coordinates.
|
|
v1 = v1.co * mat
|
|
v2 = v2.co * mat
|
|
v3 = v3.co * mat
|
|
v4 = v4.co * mat
|
|
|
|
vec1 = v2 - v1
|
|
vec2 = v4 - v1
|
|
|
|
n = vec1.cross(vec2)
|
|
|
|
area = n.length / 2.0
|
|
|
|
vec1 = v4 - v3
|
|
vec2 = v2 - v3
|
|
|
|
n = vec1.cross(vec2)
|
|
|
|
area += n.length / 2.0
|
|
|
|
elif len(face.vertices) == 3:
|
|
# Triangle
|
|
|
|
# Get vertex indices
|
|
v1, v2, v3 = face.vertices
|
|
|
|
# Get vertex data
|
|
v1 = obj.data.vertices[v1]
|
|
v2 = obj.data.vertices[v2]
|
|
v3 = obj.data.vertices[v3]
|
|
|
|
# Apply transform matrix to vertex coordinates.
|
|
v1 = v1.co * mat
|
|
v2 = v2.co * mat
|
|
v3 = v3.co * mat
|
|
|
|
vec1 = v3 - v2
|
|
vec2 = v1 - v2
|
|
|
|
n = vec1.cross(vec2)
|
|
|
|
area = n.length / 2.0
|
|
|
|
return area
|
|
|
|
|
|
# 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
|
|
|
|
mesh = obj.data
|
|
|
|
# Count the area of all the faces.
|
|
for face in mesh.faces:
|
|
if not selectedOnly or face.select:
|
|
if globalSpace:
|
|
areaTotal += faceAreaGlobal(face, obj)
|
|
else:
|
|
areaTotal += face.area
|
|
|
|
return areaTotal
|
|
|
|
# We can not calculate an area for this object.
|
|
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")
|
|
|
|
|
|
# Converts 3D coordinates in a 3DRegion
|
|
# into 2D screen coordinates for that region.
|
|
def region3d_get_2d_coordinates(context, loc_3d):
|
|
# Get screen information
|
|
mid_x = context.region.width / 2.0
|
|
mid_y = context.region.height / 2.0
|
|
width = context.region.width
|
|
height = context.region.height
|
|
|
|
# Get matrices
|
|
view_mat = context.region_data.perspective_matrix
|
|
total_mat = view_mat
|
|
|
|
# Order is important
|
|
vec = Vector((loc_3d[0], loc_3d[1], loc_3d[2], 1.0)) * total_mat
|
|
|
|
# dehomogenise
|
|
vec = Vector((
|
|
vec[0] / vec[3],
|
|
vec[1] / vec[3],
|
|
vec[2] / vec[3]))
|
|
|
|
x = int(mid_x + vec[0] * width / 2.0)
|
|
y = int(mid_y + vec[1] * height / 2.0)
|
|
|
|
return Vector((x, y, 0))
|
|
|
|
|
|
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 and draw):
|
|
p1, p2, color = line
|
|
|
|
# Get and convert the Perspective Matrix of the current view/region.
|
|
view3d = bpy.context
|
|
region = view3d.region_data
|
|
perspMatrix = region.perspective_matrix
|
|
tempMat = [perspMatrix[i][j] 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.
|
|
width = 1
|
|
bgl.glLineWidth(width)
|
|
# 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
|
|
width = 2
|
|
bgl.glLineWidth(width)
|
|
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 = region3d_get_2d_coordinates(context, p2 + (p1 - p2) * 0.5)
|
|
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.
|
|
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()
|
|
|
|
texts = [("Dist:", round(dist, PRECISION)),
|
|
("X:", round(abs(p1[0] - p2[0]), PRECISION)),
|
|
("Y:", round(abs(p1[1] - p2[1]), PRECISION)),
|
|
("Z:", round(abs(p1[2] - p2[2]), PRECISION))]
|
|
|
|
# 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.
|
|
|
|
loc_x = coord_2d[0] + OFFSET_LINE
|
|
loc_y = coord_2d[1]
|
|
for t in texts:
|
|
text = t[0]
|
|
value = str(t[1]) + " BU"
|
|
|
|
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
|
|
|
|
# Handle mesh surface area calulations
|
|
if (sce.measure_panel_calc_area):
|
|
# Get a single selected object (or nothing).
|
|
obj = getSingleObject(context)
|
|
|
|
if (context.mode == 'EDIT_MESH'):
|
|
obj = 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.
|
|
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) >= 3:
|
|
# Get selected faces
|
|
# @todo: Better (more efficient) way to do this?
|
|
faces_selected = [f for f in mesh.faces
|
|
if f.select == 1]
|
|
|
|
if len(faces_selected) > 0:
|
|
area = objectSurfaceArea(obj, True,
|
|
measureGlobal(sce))
|
|
if (area >= 0):
|
|
sce.measure_panel_area1 = area
|
|
|
|
elif (context.mode == 'OBJECT'):
|
|
# We are working in object mode.
|
|
|
|
if len(context.selected_objects) > 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(context.selected_objects) == 2:
|
|
# 2 objects selected.
|
|
|
|
obj1, obj2 = context.selected_objects
|
|
|
|
# Calculate surface area of the objects.
|
|
area1 = objectSurfaceArea(obj1, False, measureGlobal(sce))
|
|
area2 = objectSurfaceArea(obj2, False, measureGlobal(sce))
|
|
sce.measure_panel_area1 = area1
|
|
sce.measure_panel_area2 = area2
|
|
|
|
elif (obj):
|
|
# One object selected.
|
|
|
|
# Calculate surface area of the object.
|
|
area = objectSurfaceArea(obj, False, measureGlobal(sce))
|
|
if (area >= 0):
|
|
sce.measure_panel_area1 = area
|
|
|
|
|
|
class VIEW3D_OT_display_measurements(bpy.types.Operator):
|
|
'''Display the measurements made in the 'Measure' panel'''
|
|
# Do not use bl_idname here (class name is used instead),
|
|
# so the callback can be added easily.
|
|
#bl_idname = "view3d.display_measurements"
|
|
bl_label = "Display the measurements made in the" \
|
|
" 'Measure' panel in the 3D View."
|
|
bl_options = {'REGISTER'}
|
|
|
|
def modal(self, context, event):
|
|
context.area.tag_redraw()
|
|
|
|
return {'FINISHED'}
|
|
|
|
def execute(self, context):
|
|
if context.area.type == 'VIEW_3D':
|
|
mgr_ops = context.window_manager.operators.values()
|
|
if not self.bl_idname in [op.bl_idname for op in mgr_ops]:
|
|
# Add the region OpenGL drawing callback
|
|
for WINregion in context.area.regions:
|
|
if WINregion.type == 'WINDOW':
|
|
context.window_manager.modal_handler_add(self)
|
|
self._handle = WINregion.callback_add(
|
|
draw_measurements_callback,
|
|
(self, context),
|
|
'POST_PIXEL')
|
|
|
|
print("Measure panel display callback added")
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
return {'CANCELLED'}
|
|
|
|
else:
|
|
self.report({'WARNING'}, "View3D not found, cannot run operator")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
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
|
|
|
|
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')
|
|
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.
|
|
if (context.area.type == 'VIEW_3D' and
|
|
(context.mode == 'EDIT_MESH'
|
|
or context.mode == 'OBJECT')):
|
|
return 1
|
|
|
|
return 0
|
|
|
|
def draw_header(self, context):
|
|
layout = self.layout
|
|
sce = context.scene
|
|
|
|
# Force a redraw.
|
|
# This prevents the lines still be drawn after
|
|
# disabling the "measure_panel_draw" checkbox.
|
|
# @todo Better solution?
|
|
context.area.tag_redraw()
|
|
|
|
# Execute operator (this adds the callback)
|
|
# if it wasn't done yet.
|
|
bpy.ops.view3d.display_measurements()
|
|
|
|
# Define property for the draw setting.
|
|
bpy.types.Scene.measure_panel_draw = bpy.props.BoolProperty(
|
|
description="Draw distances in 3D View",
|
|
default=1)
|
|
|
|
# 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)
|
|
|
|
layout.prop(sce, "measure_panel_draw")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
sce = context.scene
|
|
|
|
# Get a single selected object (or nothing).
|
|
obj = getSingleObject(context)
|
|
|
|
# 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_area1 = bpy.props.FloatProperty(
|
|
precision=PRECISION,
|
|
unit="AREA")
|
|
bpy.types.Scene.measure_panel_area2 = bpy.props.FloatProperty(
|
|
precision=PRECISION,
|
|
unit="AREA")
|
|
|
|
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')
|
|
|
|
if (context.mode == 'EDIT_MESH'):
|
|
obj = 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.
|
|
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.
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_dist")
|
|
|
|
row = layout.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]")
|
|
|
|
row = layout.row()
|
|
row.operator("view3d.reenter_editmode",
|
|
text="Update selection & distance")
|
|
# @todo
|
|
# description="The surface area value can" \
|
|
# " not be updated in mesh edit mode" \
|
|
# " automatically. Press this button" \
|
|
# " 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
|
|
# selected vertex object to the 3D cursor.
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_dist")
|
|
|
|
row = layout.row()
|
|
row.label(text="", icon='CURSOR')
|
|
row.label(text="", icon='ARROW_LEFTRIGHT')
|
|
row.label(text="", icon='VERTEXSEL')
|
|
|
|
row = layout.row()
|
|
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
|
|
# two selected vertices.
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_dist")
|
|
|
|
row = layout.row()
|
|
row.label(text="", icon='VERTEXSEL')
|
|
row.label(text="", icon='ARROW_LEFTRIGHT')
|
|
row.label(text="", icon='VERTEXSEL')
|
|
|
|
row = layout.row()
|
|
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",
|
|
text="Surface area (selected faces):")
|
|
|
|
if (sce.measure_panel_calc_area):
|
|
# Get selected faces
|
|
# @todo: Better (more efficient) way to do this?
|
|
faces_selected = [f for f in mesh.faces
|
|
if f.select == 1]
|
|
|
|
if len(faces_selected) > 0:
|
|
if (sce.measure_panel_area1 >= 0):
|
|
row = layout.row()
|
|
row.label(
|
|
text=str(len(faces_selected)),
|
|
icon='FACESEL')
|
|
row.prop(sce, "measure_panel_area1")
|
|
|
|
row = layout.row()
|
|
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.",
|
|
icon='INFO')
|
|
|
|
row = layout.row()
|
|
row.operator("view3d.reenter_editmode",
|
|
text="Update selection")
|
|
|
|
else:
|
|
row = layout.row()
|
|
row.operator("view3d.reenter_editmode",
|
|
text="Update selection")
|
|
|
|
elif (context.mode == 'OBJECT'):
|
|
# We are working in object mode.
|
|
|
|
if len(context.selected_objects) > 2:
|
|
# We have more that 2 objects selected...
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_calc_area",
|
|
text="Surface area (selected faces):")
|
|
|
|
if (sce.measure_panel_calc_area):
|
|
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.
|
|
|
|
# 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")
|
|
|
|
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.
|
|
|
|
obj1, obj2 = context.selected_objects
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_dist")
|
|
|
|
row = layout.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="")
|
|
|
|
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):
|
|
row = layout.row()
|
|
row.label(text=obj1.name, icon='OBJECT_DATA')
|
|
row.prop(sce, "measure_panel_area1")
|
|
|
|
if (sce.measure_panel_area2 >= 0):
|
|
row = layout.row()
|
|
row.label(text=obj2.name, icon='OBJECT_DATA')
|
|
row.prop(sce, "measure_panel_area2")
|
|
|
|
row = layout.row()
|
|
row.prop(sce,
|
|
"measure_panel_transform",
|
|
expand=True)
|
|
|
|
elif (obj):
|
|
# One object selected.
|
|
# We measure the distance from the object to the 3D cursor.
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_dist")
|
|
|
|
row = layout.row()
|
|
row.label(text="", icon='CURSOR')
|
|
|
|
row.label(text="", icon='ARROW_LEFTRIGHT')
|
|
|
|
row.label(text="", icon='OBJECT_DATA')
|
|
row.prop(obj, "name", text="")
|
|
|
|
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):
|
|
row = layout.row()
|
|
row.label(text=obj.name, icon='OBJECT_DATA')
|
|
row.prop(sce, "measure_panel_area1")
|
|
|
|
row = layout.row()
|
|
row.prop(sce,
|
|
"measure_panel_transform",
|
|
expand=True)
|
|
|
|
elif not context.selected_objects:
|
|
# Nothing selected.
|
|
# We measure the distance from the origin to the 3D cursor.
|
|
|
|
row = layout.row()
|
|
row.prop(sce, "measure_panel_dist")
|
|
|
|
row = layout.row()
|
|
row.label(text="", icon='CURSOR')
|
|
row.label(text="", icon='ARROW_LEFTRIGHT')
|
|
row.label(text="Origin [0,0,0]")
|
|
|
|
else:
|
|
row = layout.row()
|
|
row.label(text="Selection not supported.",
|
|
icon='INFO')
|
|
|
|
|
|
def register():
|
|
pass
|
|
|
|
|
|
def unregister():
|
|
pass
|
|
|
|
if __name__ == "__main__":
|
|
register()
|