Page MenuHome

object_cross_section.py

# ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
bl_info = {
"name": " ",
"author": "Yorik van Havre, Alejandro Sierra, Howard Trickey, Campbell Barton, Ejnar Rasmussen",
"description": "Creates cross section(s) of the selected object(s) using the active object as cut plane",
"version": (0, 1, 8),
"blender": (2, 6, 5),
"category": "Object",
"location": "Toolshelf > Cross Section",
"warning": '',
"wiki_url": "",
"tracker_url": "",
}
"""Script made by Yorik for 2.4x based upon a code by Cambo found on blenderArtists,
and ported to 2.5x by Ejnaren.
This scripts creates cross-sections of selected objects, at intersection with
active plane object.
Installation:
Via the addons menu, no particular action needed
Usage:
Select objects you want to cut, then select (make active)
the cutting plane, then run the script. The resulting section parts will be
filled and nicely placed inside a group for easy selection.
Options:
You can turn the fill option on or off, if enabled, closed edge loops are turned into faces.
Limitations:
- Only Mesh and Surface (by approximation) objects will be cut
- The cutting object must be a plane (it must have only one face)
- The cutting plane shouldn't have any parents, if it does, the rotation from
the parents will not affect the section's position and rotation"""
import bpy
import time
EPSILON = 0.0001
def dupTest(object):
"""Checks objects for duplicates enabled (any type)
object: Blender Object.
Returns: Boolean - True if object has any kind of duplicates enabled."""
if (object.is_duplicator):
return True
else:
return False
def getObjectsAndDuplis(oblist, MATRICES=False, HACK=False):
"""Return a list of real objects and duplicates and optionally their matrices
oblist: List of Blender Objects
MATRICES: Boolean - Check to also get the objects matrices default=False
HACK: Boolean - See note default=False
Returns: List of objects or
List of tuples of the form:(ob,matrix) if MATRICES is set to True
NOTE: There is an ugly hack here that excludes all objects whose name
starts with "dpl_" to exclude objects that are parented to a duplicating
object, User must name objects properly if hack is used."""
result = []
for o in oblist:
if dupTest(o):
dup_obs = o.children
if len(dup_obs):
for dup_ob in dup_obs:
if MATRICES:
result.append((dup_ob, dup_ob.matrix_world))
else:
result.append(dup_ob)
else:
if HACK:
if o.name[0:4] != "dpl_":
if MATRICES:
result.append((o, o.matrix_world))
else:
result.append(o)
else:
if MATRICES:
result.append((o, o.matrix_world))
else:
result.append(o)
return result
def section(context, m, itM, FILL=True):
"""Finds the section mesh between a mesh and a plane
m: Blender Mesh - the mesh to be cut
itM: Matrix - The matrix of object of the mesh for correct coordinates
FILL: Boolean - Check if you want to fill the resulting mesh, default=True
Returns: Mesh - the resulting mesh of the section if any or
Boolean - False if no section exists"""
verts = []
edges = []
ed_xsect = {}
for ed in m.edges:
# getting a vector from each edge vertices to a point on the plane
# first apply transformation matrix so we get the real section
id0 = ed.vertices[0]
p0 = itM * m.vertices[id0].co
id1 = ed.vertices[1]
p1 = itM * m.vertices[id1].co
d0 = abs(p0.z)
d1 = abs(p1.z)
# Check to see if edge intersects.
if (p0.z > 0) != (p1.z > 0):
t = d0 / (d0 + d1)
co = p0 + (p1 - p0) * t
ed_xsect[ed.key] = len(verts)
verts.append(co)
elif d0 < EPSILON:
ed_xsect[ed.key] = len(verts)
verts.append(p0)
elif d1 < EPSILON:
ed_xsect[ed.key] = len(verts)
verts.append(p1)
for p in m.polygons:
# get the edges that the intersecting points form
# to explain this better:
# If a face has an edge that is proven to be crossed then use the
# mapping we created earlier to connect the edges properly
ps = [ed_xsect[key] for key in p.edge_keys if key in ed_xsect]
s = len(ps)
if s == 2:
edges.append(tuple(ps))
elif s > 2:
# when one vertex is coplanar
# the end of two edges are found
unique = {tuple(verts[id]): id for id in ps}
if len(unique) == 2:
edges.append(tuple(unique.values()))
if edges:
me = bpy.data.meshes.new('Section')
me.from_pydata(verts, edges, [])
# create a temp object and link it to the current scene to be able to
# apply rem Doubles and fill
o = bpy.data.objects.new('Section', me)
scene = context.scene
scene.objects.link(o)
# do a remove doubles to cleanup the mesh, this is needed when there
# is one or more edges coplanar to the plane.
o.select = True
scene.objects.active = o
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="EDGE", action="ENABLE")
bpy.ops.mesh.select_all(action="SELECT")
# remove doubles:
bpy.ops.mesh.remove_doubles()
if FILL:
bpy.ops.mesh.edge_face_add()
# recalculate outside normals:
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.object.mode_set(mode='OBJECT')
return o
else:
return False
# operator definition
class OBJECT_OT_cross_section(bpy.types.Operator):
"""Creates cross section(s) of the selected object(s), using the active object as cut plane"""
bl_idname = "object.cross_section"
bl_label = "Cross Section"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Creates cross section(s) of the selected object(s), using the active object as cut plane"
@classmethod
def poll(cls, context):
if context.active_object:
if len(context.selected_objects) > 1:
return True
return False
def execute(self, context):
act = context.active_object
sel = context.selected_objects[:]
if not act or act.type != 'MESH':
self.report({'WARNING'}, "No meshes or active object selected")
return
if len(sel) >= 2:
# Window.WaitCursor(1) 2.6 equivalent?
t = time.time()
# Get section Plane's transformation matrix
tM = act.matrix_world
itM = tM.inverted()
# filter selection to get back any duplis to cut them as well
oblist = getObjectsAndDuplis(sel, MATRICES=True, HACK=False)
# deselect all selected objects so we can select new ones and put
# them into the group that will contain all new objects
for o in sel:
o.select = False
# create a list to hold all objects that we'll create
parts = []
for o, wM in oblist:
typ = o.type
if o != act and \
(typ == "MESH" or typ == "SURFACE" or typ == "CURVE"):
# Use BpyMesh to get mesh data so that modifiers are applied
# and to be able to cut surfaces and curves (curves behave
# strange though and don't seem to be very usefull
m = o.to_mesh(scene=context.scene, apply_modifiers=True, settings='PREVIEW')
# Run the main function
ob = section(context, m, itM * wM, context.scene.cross_section_fill)
# if there's no intersection just skip the object creation
if ob:
# Reset section part's center and orientation
# Since it's a 2d object it's best to use the objects
# center of mass instead of bounding box center
ob.matrix_world = tM.copy()
parts.append(ob)
else:
self.report({'WARNING'}, "an object does not intersect the plane, continuing happily")
# Put the parts of the section into a new group so that it's easy to
# select them all
sect_grp = bpy.data.groups.new('section')
for part in parts:
sect_grp.objects.link(part)
else:
self.report({'WARNING'}, "the selection is empty, no object to cut!")
print('CrossSection finished in %.2f seconds' % (time.time() - t))
act.select = False
return {'FINISHED'}
# a panel containing 2 buttons
class VIEW3D_PT_tools_cross_section(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_category = "Tools"
bl_context = "objectmode"
bl_label = "Cross Section"
def draw(self, context):
row = self.layout.row(align=True)
row.alignment = 'LEFT'
row.prop(context.scene, "cross_section_fill")
row.operator("object.cross_section", text="Create cross section")
# Registers the operator, the toolshelf panel and the fill property
def register():
# this registers all the panels and operators
bpy.utils.register_module(__name__)
# this stores the fill property in the current scene
bpy.types.Scene.cross_section_fill = bpy.props.BoolProperty(
name="Fill",
description="Fill closed contours with faces",
default=True)
# Removes the operator, the toolshelf and the fill property
def unregister():
bpy.utils.unregister_module(__name__)
del bpy.types.Scene.cross_section_fill
# This lets you import the script without running it
if __name__ == "__main__":
register()

File Metadata

Mime Type
text/x-c++
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
87/0d/f440874dc1cb063221ebdbc87a43

Event Timeline