# 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 #####
# Copyright 2012 Tom SF Haines
# thaines@gmail.com
#
# Provides 4 operators, that may be accessed via the 'space' menu.
# *Volume:
# Calculates the volume of a well defined mesh - the output appears momentarily on the top bar, but is also written to the custom property of the selected object, in the variable 'volume'
# A well defined mesh is one with no holes, and no stray vertices/edges where the normals all point outwards and the geometry does not intersect with itself. Multiple separate chunks are acceptable as long as they do not intersect.
# Most likely users of this are people who use Blender to create meshes for 3D printers, to estimate the material required.
# *Record Volume:
# Does exactly what volume does, but calculates it for every frame in the animation, and creates an animated custom property on the object with the name 'volume' - you can then see how the property changes as the animation is run, or use it to drive something.
# Added this because I could - not really sure what it could be used for. Possibly useful to drive a gauge on some complex machine? Might have a debugging use when animating an object that should not change volume.
# *Split Volume:
# You select two objects - first a well defined mesh, then an arbitrary object that represents a plane. The plane can be any actual object - it assumes a plane in the x-y axes, which happens to match up with the plane provided by Blender. Note however that this is a true plane, i.e. infinite.
# The plane can then split the mesh in half, defining a volume above and a volume below the mesh (above is the positive z direction, below the negative z direction.). You define one of 3 custom properties on the plane object:
## below - Contains your desired volume below the plane.
## above - Contains your desired volume above the plane.
## ratio - Defines how much of the volume should be below the plane in terms of the total volume, i.e. below = ratio * total_volume.
# This operator then moves the plane the shortest distance to satisfy the constraint.
# In the event it is not possible to satisfy it the plane will end up at the extremity of the object that is closest to satisfying it.
# Main use is in testing values to find good ones to use for the below operator on the current frame.
# *Record Split Volume:
# Does the exact same thing as split volume, except it makes the adjustment for every frame of the animation range and records the objects location in each position.
# Note that if you animate the custom properties of the plane (below, above or ratio.) it will satisfy the constraint for the correct animated term.
# Basically good if you want to model something related to volume, typically where the volume is held constant as something deforms. Most obvious example is water in a deforming container (Which can simply be a rotating container.).
# Every possible deformation is supported. The animation means you could fill an object with water at a constant rate accounting for the strange shape of the object - not the same as a real water simulation, but potentially useful in cases when that would be overkill.
'description':'Provides an operator to provide the volume of a mesh. Also provides an operator to position a plane to satisfy a volume-based invariant relative to a mesh, i.e. the volume of the mesh above/below the plane or the ratio between the two. Animation is supported, so you can get the volume during an animation or animate a plane to maintain an invariant. This is typically used to position water planes in containers, for when a full simulation is overkill/unwanted (e.g. saline bag, which deforms when being picked up.).',
'author':'Tom SF Haines',
'version':(1,0),
'blender':(2,63,0),
'location':'Operators: Volume, Record Volume, Split Volume, Record Split Volume',
'category':'Mesh'}
defdir_range(normal,mesh,mesh_to_world):
"""Given an object and a normal defining a plane at the origin returns the lowest and highest distances from the plane, e.g. the range of values for which dist in volumeSplitPlane will not have 0 in one of its return values."""
"""Given a plane and a (clean, triangulated) mesh returns a tuple (below, above) of the volume of the plane above and below the mesh. Plane is defined in world space with a normal and distance from the origin."""
below=0.0
above=0.0
defheight_to_vol(h1,h2,h3,area,sign):
# Given 3 heights, all positive or negative, updates below/above accordingly, making use of the projected triangle area...
nonlocalbelow,above
change=sign*area*(h1+h2+h3)/3.0
if(h1+h2+h3)>0.0:above+=change
else:below+=change
defdo_face(v1,v2,v3,norm):
# Process a triangle - start by getting its vertices...
v1=mesh_to_world*mesh.vertices[v1].co
v2=mesh_to_world*mesh.vertices[v2].co
v3=mesh_to_world*mesh.vertices[v3].co
# Calculate distances to the plane...
h1=normal.dot(v1)-dist
h2=normal.dot(v2)-dist
h3=normal.dot(v3)-dist
# Project them back to the plane...
p1=v1-normal*h1
p2=v2-normal*h2
p3=v3-normal*h3
# Check if the face is all on one side of the plane - the simple case...
ph1=h1>0.0
ph2=h2>0.0
ph3=h3>0.0
sign=1.0ifnormal.dot(norm)>0.0else-1.0
ifph1==ph2andph1==ph3:
# Calculate the area of the projected triangle...
area=area_tri(p1,p2,p3)
# Make the update...
height_to_vol(h1,h2,h3,area,sign)
else:
# The complex scenario - need to chop the triangle into 3, where each is entirly on one side of the plane, and handle each in turn...
## Switch around the vertices so that both v2 and v3 are on the same side...
ifph2!=ph3:
ifph1==ph2:
v1,v3=v3,v1
h1,h3=h3,h1
p1,p3=p3,p1
else:# phi1==phi3
v1,v2=v2,v1
h1,h2=h2,h1
p1,p2=p2,p1
## Intercept the lines v1 - v2 and v1 - v3 with the plane...
"""Finds and returns the distance from the origin required by a plane to acheive a certain goal. There are 3 possible goals, set by the task parameter - 'below', 'above' and 'ratio'. If 'below' then the plane is set so the volume below it is equal to goal, if above then for the volume above it. If set to ratio then goal is the ratio of the total volume that should go below the plane. Uses recursive subdivision, so can be quite slow."""
assert(taskin['below','above','ratio'])
# Get the search range...
low,high=dir_range(normal,mesh,mesh_to_world)
# Do a partial first split - some special casing is required...