I am attempting to visualize a structural frame, with a hollow cylindrical cross-section (later on I would like to generalize the cross-section, but for now this will do), using VTK version 8.1.0 and Python. Disclaimer, I am quite new to both VTK and Python programming.
The problem is I am getting overlapping actors, which is obvious, to be honest. As illustrated in Figure 1.
I would like to get rid of this, and actually "connect" the adjacent actors. I am thinking that first of all, I need some form of collision detection. Then I have considered extrapolating one of the actors inside the other, and then "cut" the remaining by use of a plane which I hopefully can create from the intersection between the actors, as explained in this example: intersection between actors.
I also thought that it might be possible to actually "extrude to a surface", but I do not know if this is something that is doable in VTK, but I am looking through as many examples as I can find.
So first of all, are any of the ideas I thought of realistic? If the extrude thing is doable, can anyone give me a hint of how to get started?
If none of the ideas are usable, does anyone of you have another idea of how to accomplish this?
I have created a minimal code snippet which shows the problem I would like to fix:
import vtk
def main():
colors = vtk.vtkNamedColors()
disk = vtk.vtkDiskSource()
disk.SetCircumferentialResolution(128)
disk.SetRadialResolution(1)
disk.SetOuterRadius(2)
disk.SetInnerRadius(2 - 0.1)
extrude_disk = vtk.vtkLinearExtrusionFilter()
extrude_disk.SetInputConnection(disk.GetOutputPort())
extrude_disk.SetExtrusionTypeToNormalExtrusion()
extrude_disk.SetVector(0, 0, 1)
extrude_disk.SetScaleFactor(1)
extrude_disk.Update()
disk2 = vtk.vtkDiskSource()
disk2.SetCircumferentialResolution(128)
disk2.SetRadialResolution(1)
disk2.SetOuterRadius(1.5)
disk2.SetInnerRadius(1.5 - 0.1)
extrude_disk2 = vtk.vtkLinearExtrusionFilter()
extrude_disk2.SetInputConnection(disk2.GetOutputPort())
extrude_disk2.SetExtrusionTypeToNormalExtrusion()
extrude_disk2.SetVector(0, 0, 1)
extrude_disk2.SetScaleFactor(1)
extrude_disk2.Update()
start_point = [0] * 3
start_point2 = [-10, 0, 0]
end_point = [0, 0, 10]
end_point2 = [0, 0, 7]
# Computing a basis
normalized_x = [0] * 3
normalized_x2 = [0] * 3
normalized_y = [0] * 3
normalized_y2 = [0] * 3
normalized_z = [0] * 3
normalized_z2 = [0] * 3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(end_point, start_point, normalized_x)
vtk.vtkMath.Subtract(end_point2, start_point2, normalized_x2)
length = vtk.vtkMath.Norm(normalized_x)
length2 = vtk.vtkMath.Norm(normalized_x2)
vtk.vtkMath.Normalize(normalized_x)
vtk.vtkMath.Normalize(normalized_x2)
# The Z axis is an arbitrary vector cross X
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.
arbitrary = [0] * 3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
vtk.vtkMath.Cross(normalized_x2, arbitrary, normalized_z2)
vtk.vtkMath.Normalize(normalized_z)
vtk.vtkMath.Normalize(normalized_z2)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
vtk.vtkMath.Cross(normalized_z2, normalized_x2, normalized_y2)
matrix = vtk.vtkMatrix4x4()
matrix2 = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
matrix2.Identity()
for i in range(3):
matrix.SetElement(i, 0, normalized_x[i])
matrix2.SetElement(i, 0, normalized_x2[i])
matrix.SetElement(i, 1, normalized_y[i])
matrix2.SetElement(i, 1, normalized_y2[i])
matrix.SetElement(i, 2, normalized_z[i])
matrix2.SetElement(i, 2, normalized_z2[i])
# Apply the transforms
transform = vtk.vtkTransform()
transform.Translate(start_point) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateY(90.0) # align cylinder
transform.Scale(1.0, 1.0, length) # scale along the height vector
transform2 = vtk.vtkTransform()
transform2.Translate(start_point2) # translate to starting point
transform2.Concatenate(matrix2) # apply direction cosines
transform2.RotateY(90.0) # align cylinder
transform2.Scale(1.0, 1.0, length2) # scale along the height vector
# Create a mapper and actor for the disks
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(extrude_disk.GetOutputPort())
actor = vtk.vtkActor()
actor.SetUserMatrix(transform.GetMatrix())
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d("yellow"))
mapper2 = vtk.vtkPolyDataMapper()
mapper2.SetInputConnection(extrude_disk2.GetOutputPort())
actor2 = vtk.vtkActor()
actor2.SetUserMatrix(transform2.GetMatrix())
actor2.SetMapper(mapper2)
actor2.GetProperty().SetColor(colors.GetColor3d("yellow"))
# Create a renderer, render window, and interactor
renderer = vtk.vtkRenderer()
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName("Overlapping cylinders example")
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Add the actors to the scene
renderer.AddActor(actor)
renderer.AddActor(actor2)
renderer.SetBackground(colors.GetColor3d("BkgColor"))
# Render and interact
render_window.Render()
render_window_interactor.Start()
if __name__ == '__main__':
main()
This is the end result I am looking for! just without the actors intersecting/crossing/colliding, I do not know the correct term for the behavior i apologise, as they are doing in the example I have here.
Let me start with a small clarification. Actors in VTK are simple container objects that combine geometric data and visualization properties for rendering. VTK does not offer much functionality on the level of actors. If you want to detect colliding objects, you need to solve this for the geometries (vtkPolyData
).
There is no generic collision-detection- or body-intersection-engine in VTK. What you can do in VTK is to apply boolean operations. However, this is not easy to achieve robustly. There are two main approaches:
Use [vtkBooleanOperationPolyDataFilter][1]
to apply boolean operations directly on the mesh. See here for an example. Unfortunately, this will fail in your case because of the mesh properties of your surfaces (hit key W
when looking at the surface in the RenderWindow to inspect the wireframe of your mesh). vtkBooleanOperationPolyDataFilter
will perform best if the mesh's triangles are small and have decent condition numbers, that is, if the triangles are not too spiky. (See the manual of the Verdict toolbox for some triangle metrics.) The mesh of your extruded disks, however, consists of very long, spiky triangles. What you would need to do is to first remesh the surface. VTK does not offer remeshing facilities out of the box, but related toolboxes such as VMTK do.
Getting approach (A) right for generic geometries is tricky, because you may end up with non-manifold or leaky surfaces. Also, vtkBooleanOperationPolyDataFilter
is known to have some bugs (see here or here). Let's hope that these problems will be fixed some day.
A second approach is to work with implicit functions. For instance, you can represent your tubes as implicit cylinders, and intersect these using [vtkImplicitBoolean][7]
. See here for an example. The problem with this approach is that you need to convert the implicit representation of the objects to yield the resulting surface mesh. The marching cubes algorithm in VTK is rather slow, so you need to wait a long time for high resolutions. And it is impossible to preserve sharp edges. On the other hand, it is more robust and easier to deal with.
The code below demonstrates both cases. The remeshing feature I cannot share with you here, therefore only the implicit boolean is functional. The screenshot shows, how the result looks like. (Yellow: the input surfaces, Red: the result)
For more details on terminology and alternative problem formulations, refer to "Collision detection between geometric models: a survey" by Ming and Gottschalk, 1999.
# This code has been written by normanius under the CC BY-SA 4.0 license.
# License: https://creativecommons.org/licenses/by-sa/4.0/
# Author: https://stackoverflow.com/users/3388962/normanius
# Date: July 2018
import vtk
import numpy as np
def compute_transform(start, end):
# Better compute the matrix in numpy!
normalized_x = [0]*3
normalized_y = [0]*3
normalized_z = [0]*3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(end, start, normalized_x)
length = vtk.vtkMath.Norm(normalized_x)
vtk.vtkMath.Normalize(normalized_x)
# The Z axis is an arbitrary vector cross X
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.
arbitrary = [0]*3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
vtk.vtkMath.Normalize(normalized_z)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
matrix = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
for i in range(3):
matrix.SetElement(i, 0, normalized_x[i])
matrix.SetElement(i, 1, normalized_y[i])
matrix.SetElement(i, 2, normalized_z[i])
transform = vtk.vtkTransform()
transform.Translate(start) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateY(90.0) # align cylinder
# Don't scale! This changes mesh properties (e.g. aspect ratio)
#transform.Scale(1.0, 1.0, length) # scale along the height vector
return transform
def transform_item(item, transform):
transformed = vtk.vtkTransformPolyDataFilter()
transformed.SetInputConnection(item.GetOutputPort())
transformed.SetTransform(transform)
transformed.Update()
return transformed
def create_pipe(radius, thickness, height):
# This type of pipe is not suited for remeshing, because remeshing does not
# preserve (feature-) edges. See create_pipe2
assert(radius>thickness)
disk = vtk.vtkDiskSource()
disk.SetCircumferentialResolution(128)
disk.SetRadialResolution(1)
disk.SetOuterRadius(radius)
disk.SetInnerRadius(radius - thickness)
pipe = vtk.vtkLinearExtrusionFilter()
pipe.SetInputConnection(disk.GetOutputPort())
pipe.SetExtrusionTypeToNormalExtrusion()
pipe.SetVector(0, 0, 1)
pipe.SetScaleFactor(height)
pipe.Update()
return pipe
def create_pipe_implicit(radius, thickness, height):
center = np.array([0,0,0])
axis = np.array([0,0,1])
centerTop = center + height*axis
centerBottom = center
# Outer cylinder.
outer = vtk.vtkCylinder()
outer.SetCenter(center)
outer.SetAxis(axis)
outer.SetRadius(radius)
# Inner cylinder.
inner = vtk.vtkCylinder()
inner.SetCenter(center)
inner.SetAxis(axis)
inner.SetRadius(radius-thickness)
# Top face.
plane1 = vtk.vtkPlane()
plane1.SetOrigin(centerTop)
plane1.SetNormal(np.array(outer.GetAxis()))
# Bottom face.
plane2 = vtk.vtkPlane()
plane2.SetOrigin(centerBottom)
plane2.SetNormal(-np.array(outer.GetAxis()))
# Put things together.
difference = vtk.vtkImplicitBoolean()
difference.AddFunction(outer)
difference.AddFunction(inner)
difference.SetOperationTypeToDifference()
intersection = vtk.vtkImplicitBoolean()
intersection.AddFunction(difference)
intersection.AddFunction(plane1)
intersection.AddFunction(plane2)
intersection.SetOperationTypeToIntersection()
pipe = intersection
# Also return inner and outer cylinder.
intersection = vtk.vtkImplicitBoolean()
intersection.AddFunction(inner)
intersection.AddFunction(plane1)
intersection.AddFunction(plane2)
intersection.SetOperationTypeToIntersection()
inner = intersection
intersection = vtk.vtkImplicitBoolean()
intersection.AddFunction(outer)
intersection.AddFunction(plane1)
intersection.AddFunction(plane2)
intersection.SetOperationTypeToIntersection()
outer = intersection
return pipe, inner, outer
def add_to_renderer(renderer, item, color, opacity=1., translate=None):
colors = vtk.vtkNamedColors()
mapper = vtk.vtkPolyDataMapper()
mapper.SetScalarVisibility(False)
mapper.SetInputConnection(item.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(color))
actor.GetProperty().SetOpacity(opacity)
if translate:
trafo = vtk.vtkTransform()
trafo.Translate(translate)
actor.SetUserTransform(trafo)
renderer.AddActor(actor)
return mapper, actor
def evaluate_implicit(implicit_function, resolution, bounds):
sampled = vtk.vtkSampleFunction()
sampled.SetSampleDimensions(resolution, resolution, resolution)
sampled.SetModelBounds(bounds)
sampled.SetImplicitFunction(implicit_function)
iso = vtk.vtkMarchingCubes()
iso.SetValue(0,0.)
iso.SetInputConnection(sampled.GetOutputPort())
iso.Update()
return iso
def main():
colors = vtk.vtkNamedColors()
# Params.
radius = 2.
thickness = 0.5
start_point = np.array([0] * 3)
end_point = np.array([0, 0, 10])
length = np.linalg.norm(start_point-end_point)
radius2 = 2.
thickness2 = 0.5
start_point2 = np.array([-10, 0, 0])
end_point2 = np.array([0, 0, 7])
length2 = np.linalg.norm(start_point2-end_point2)
# Compute transforms.
transform = compute_transform(start_point, end_point)
transform2 = compute_transform(start_point2, end_point2)
############################################################################
# BOOLEAN OPERATIONS ON MESHES
############################################################################
if False:
pipe, inner, outer = create_pipe2(radius=radius, thickness=thickness, height=length)
pipe2, inner2, outer2 = create_pipe2(radius=radius2, thickness=thickness2, height=length2)
# Apply the transforms.
pipe = transform_item(pipe, transform)
inner = transform_item(inner, transform)
outer = transform_item(outer, transform)
pipe2 = transform_item(pipe2, transform2)
inner2 = transform_item(inner2, transform2)
outer2 = transform_item(outer2, transform2)
#pipe_2m1 = boolean_combine(pipe2, pipe, 'difference')
pipe_2m1 = boolean_combine(pipe2, pipe, 'union') # Ugly! There is a bug in vtk!
result_bool = pipe_2m1
#result_bool = boolean_combine(pipe, pipe_2m1, 'union')
#result_bool = remeshSurface(result_bool, targetArea=.1, iterations=10)
# Add items to renderer.
renderer = vtk.vtkRenderer()
opacity=1.0
#add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity)
#add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity)
add_to_renderer(renderer=renderer, item=result_bool, color='red')
############################################################################
# IMPLICIT BOOLEAN
############################################################################
else:
# We need to know the domain where the implicit function will be
# evaulated. There is certainly other ways to achieve this. Here,
# we simply get the bounds from the meshes. Also, we add a margin
# to avoid artifacts close to the domain boundary.
pipe = create_pipe(radius=radius, thickness=thickness, height=length)
pipe2 = create_pipe(radius=radius2, thickness=thickness2, height=length2)
pipe = transform_item(pipe, transform)
pipe2 = transform_item(pipe2, transform2)
bounds = pipe.GetOutput().GetBounds()
bounds2 = pipe2.GetOutput().GetBounds()
def applyMargin(bounds, margin):
extent = [ bounds[1]-bounds[0],
bounds[3]-bounds[2],
bounds[5]-bounds[4] ]
bounds = [ bounds[0]-extent[0]*margin, bounds[1]+extent[0]*margin,
bounds[2]-extent[1]*margin, bounds[3]+extent[1]*margin,
bounds[4]-extent[2]*margin, bounds[5]+extent[2]*margin ]
return bounds
bounds = applyMargin(bounds, margin=0.1)
bounds2 = applyMargin(bounds2, margin=0.1)
# The bounds of the combined object pipe+pipe2
boundsCombo = [min(bounds[0], bounds2[0]),
max(bounds[1], bounds2[1]),
min(bounds[2], bounds2[2]),
max(bounds[3], bounds2[3]),
min(bounds[4], bounds2[4]),
max(bounds[5], bounds2[5])]
# Let's create implicit functions for the pipes.
pipeImp, innerImp, outerImp = create_pipe_implicit(radius=radius, thickness=thickness, height=length)
pipeImp2, innerImp2, outerImp2 = create_pipe_implicit(radius=radius2, thickness=thickness2, height=length2)
pipeImp.SetTransform(transform.GetInverse())
pipeImp2.SetTransform(transform2.GetInverse())
innerImp.SetTransform(transform.GetInverse())
innerImp2.SetTransform(transform2.GetInverse())
outerImp.SetTransform(transform.GetInverse())
outerImp2.SetTransform(transform2.GetInverse())
# Apply the intersection.
difference = vtk.vtkImplicitBoolean()
difference.AddFunction(pipeImp2)
difference.AddFunction(outerImp)
difference.SetOperationTypeToDifference()
union = vtk.vtkImplicitBoolean()
union.AddFunction(difference)
union.AddFunction(pipeImp)
union.SetOperationTypeToUnion()
# This last operation is required to "cut through" the first pipe.
difference = vtk.vtkImplicitBoolean()
difference.AddFunction(union)
difference.AddFunction(innerImp2)
difference.SetOperationTypeToDifference()
# Convert the implicit functions into surfaces.
pipe = evaluate_implicit(implicit_function=pipeImp,
resolution=100,
bounds=bounds)
pipe2 = evaluate_implicit(implicit_function=pipeImp2,
resolution=100,
bounds=bounds2)
result = evaluate_implicit(implicit_function=difference,
resolution=100,
bounds=boundsCombo)
# Add items to renderer.
renderer = vtk.vtkRenderer()
opacity=1.
add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=result, color='red')
# Create a renderer, render window, and interactor.
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName("Overlapping cylinders example")
render_window.SetSize(1000,1000)
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Add the actors to the scene.
renderer.SetBackground(colors.GetColor3d("Gray"))
# Render and interact.
render_window.Render()
render_window_interactor.Start()
if __name__ == '__main__':
main()
# This code has been written by normanius under the CC BY-SA 4.0 license.
# License: https://creativecommons.org/licenses/by-sa/4.0/
# Author: https://stackoverflow.com/users/3388962/normanius
# Date: July 2018
import vtk
import numpy as np
try:
# Remesher based on VMTK. Sorry, cannot share this with you.
from geometry.remesher import remeshSurface
from vtkutils.misc import extractEdges
from geometry.capper import capSurface
except:
remeshSurface = None
extractEdges = None
capSurface = None
def compute_transform(start, end):
# Better compute the matrix in numpy!
normalized_x = [0]*3
normalized_y = [0]*3
normalized_z = [0]*3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(end, start, normalized_x)
length = vtk.vtkMath.Norm(normalized_x)
vtk.vtkMath.Normalize(normalized_x)
# The Z axis is an arbitrary vector cross X
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.
arbitrary = [0]*3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
vtk.vtkMath.Normalize(normalized_z)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
matrix = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
for i in range(3):
matrix.SetElement(i, 0, normalized_x[i])
matrix.SetElement(i, 1, normalized_y[i])
matrix.SetElement(i, 2, normalized_z[i])
transform = vtk.vtkTransform()
transform.Translate(start) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateY(90.0) # align cylinder
# Don't scale! This changes mesh properties (e.g. aspect ratio)
#transform.Scale(1.0, 1.0, length) # scale along the height vector
return transform
def transform_item(item, transform):
transformed = vtk.vtkTransformPolyDataFilter()
transformed.SetInputConnection(item.GetOutputPort())
transformed.SetTransform(transform)
transformed.Update()
return transformed
def create_pipe(radius, thickness, height):
# This type of pipe is not suited for remeshing, because remeshing does not
# preserve (feature-) edges. See create_pipe2
assert(radius>thickness)
disk = vtk.vtkDiskSource()
disk.SetCircumferentialResolution(128)
disk.SetRadialResolution(1)
disk.SetOuterRadius(radius)
disk.SetInnerRadius(radius - thickness)
pipe = vtk.vtkLinearExtrusionFilter()
pipe.SetInputConnection(disk.GetOutputPort())
pipe.SetExtrusionTypeToNormalExtrusion()
pipe.SetVector(0, 0, 1)
pipe.SetScaleFactor(height)
pipe.Update()
return pipe
def create_pipe2(radius, thickness, height):
# Create pipes with decently meshed surfaces, if remeshSurface is
# availaable.
# Align the cylinder in the same way as create_pipe() does.
transform = vtk.vtkTransform()
transform.RotateX(90.0)
transform.Translate(0,height/2,0)
outer = vtk.vtkCylinderSource()
outer.SetRadius(radius)
outer.SetResolution(128)
outer.SetHeight(height)
outer.CappingOff()
outer.Update()
outer = transform_item(outer, transform)
inner = vtk.vtkCylinderSource()
inner.SetRadius(radius-thickness)
inner.SetResolution(128)
inner.SetHeight(height)
inner.CappingOff()
inner.Update()
inner = transform_item(inner, transform)
# remeshSurface, extractEdges and capSurface are not available, sorry!
if remeshSurface:
outer = remeshSurface(outer, targetArea=.1, iterations=10, smoothing=False)
inner = remeshSurface(inner, targetArea=.1, iterations=10, smoothing=False)
# So far, we have two concentric cylinders.
# Close the upper and lower caps using vtkContourTriangulator.
result = combine_polydata(outer, inner)
edges1 = extractEdges(outer, mode='separated')
edges2 = extractEdges(inner, mode='separated')
assert(len(edges1)==len(edges2)==2)
for i in range(2):
edgesBottom = combine_polydata(edges1[i], edges2[i])
bottom = vtk.vtkContourTriangulator()
bottom.SetInputConnection(edgesBottom.GetOutputPort())
bottom.Update()
result = combine_polydata(result, bottom)
# Return also the inner and outer cylinders.
#return result, inner, outer
inner = capSurface(inner, remesh=True, returnCaps=False)
outer = capSurface(outer, remesh=True, returnCaps=False)
return result, inner, outer
def clean_mesh(source):
clean = vtk.vtkCleanPolyData()
#clean.ConvertPolysToLinesOff()
clean.SetInputData(source.GetOutput())
clean.Update()
return clean
def fill_holes(source):
fill = vtk.vtkFillHolesFilter()
fill.SetInputConnection(source.GetOutputPort())
fill.SetHoleSize(100)
fill.Update()
return fill
def combine_polydata(source1, source2):
if source2 is None:
return source1
if source1 is None:
return source2
combo = vtk.vtkAppendPolyData()
combo.AddInputData(source1.GetOutput())
combo.AddInputData(source2.GetOutput())
combo.Update()
return clean_mesh(combo)
def boolean_combine(source1, source2, method='union'):
assert(method.lower() in ['union', 'or',
'intersection', 'and',
'difference', 'subtract', 'minus'])
source1 = source1 if source1 is not None else None
source2 = source2 if source2 is not None else None
method = method.lower()
if source1 is None and source2 is None:
return None
# vtkBooleanOperationPolyDataFilter cannot handle empty sources!
if source2 is None or source2.GetOutput().GetNumberOfPoints() == 0:
return source1
if source1 is None or source1.GetOutput().GetNumberOfPoints() == 0:
return source2
boolean = vtk.vtkBooleanOperationPolyDataFilter()
if method in ['union', 'or']:
boolean.SetOperationToUnion()
elif method in ['intersection', 'and']:
boolean.SetOperationToIntersection()
elif method in ['difference', 'subtract', 'minus']:
boolean.SetOperationToDifference()
boolean.SetInputData(0, source1.GetOutput())
boolean.SetInputData(1, source2.GetOutput())
boolean.Update()
result = boolean
if result.GetOutput().GetNumberOfPoints() == 0:
# No intersection betweeen source1 and source2.
if method in ['union', 'or']:
result = combine_polydata(source1, source2)
elif method in ['intersection', 'and']:
result = vtk.vtkPolyData()
elif method in ['difference', 'subtract', 'minus']:
result = vtk.vtkPolyData()
result.DeepCopy(source1.GetOutput())
pt = vtk.vtkPassThroughFilter()
pt.SetInputData(result)
pt.Update()
result = pt
return clean_mesh(result)
def add_to_renderer(renderer, item, color, opacity=1., translate=None):
colors = vtk.vtkNamedColors()
mapper = vtk.vtkPolyDataMapper()
mapper.SetScalarVisibility(False)
mapper.SetInputConnection(item.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(color))
actor.GetProperty().SetOpacity(opacity)
if translate:
trafo = vtk.vtkTransform()
trafo.Translate(translate)
actor.SetUserTransform(trafo)
renderer.AddActor(actor)
return mapper, actor
def main():
colors = vtk.vtkNamedColors()
# Params.
radius = 2.
thickness = 0.5
start_point = np.array([0] * 3)
end_point = np.array([0, 0, 10])
length = np.linalg.norm(start_point-end_point)
radius2 = 2.
thickness2 = 0.5
start_point2 = np.array([-10, 0, 0])
end_point2 = np.array([0, 0, 7])
length2 = np.linalg.norm(start_point2-end_point2)
# Compute transforms.
transform = compute_transform(start_point, end_point)
transform2 = compute_transform(start_point2, end_point2)
############################################################################
# BOOLEAN OPERATIONS ON MESHES
############################################################################
if remeshSurface and False:
pipe, inner, outer = create_pipe2(radius=radius,
thickness=thickness,
height=length)
pipe2, inner2, outer2 = create_pipe2(radius=radius2,
thickness=thickness2,
height=length2)
# Apply the transforms.
pipe = transform_item(pipe, transform)
inner = transform_item(inner, transform)
outer = transform_item(outer, transform)
pipe2 = transform_item(pipe2, transform2)
inner2 = transform_item(inner2, transform2)
outer2 = transform_item(outer2, transform2)
# Ugly! There is a bug in vtk!
result_bool = boolean_combine(pipe2, pipe, 'union')
else:
pipe = create_pipe(radius=radius, thickness=thickness, height=length)
pipe2 = create_pipe(radius=radius2, thickness=thickness2, height=length2)
pipe = transform_item(pipe, transform)
pipe2 = transform_item(pipe2, transform2)
# A warning is printed: "No Intersection between objects"...
# This has something to do with the mesh properties.
result_bool = boolean_combine(pipe2, pipe, 'difference')
# Add items to renderer.
renderer = vtk.vtkRenderer()
opacity=1.0
add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=result_bool, color='red')
# Create a renderer, render window, and interactor.
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName("Overlapping cylinders example")
render_window.SetSize(1000,1000)
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Add the actors to the scene.
renderer.SetBackground(colors.GetColor3d("Gray"))
# Render and interact.
render_window.Render()
render_window_interactor.Start()
if __name__ == '__main__':
main()