How do I cut an arbitrary prism and end up with two manifold solids?

1. First, start with an arbitrary, closed, manifold prism like so in Figure 1 (i.e. an arbitrary shaped polygon extruded up in the Z direction)

1. Define a cut line as accurately as possible (not by eyeballing) as shown in Figure 2

1. Using that cut line, split the prism into two separate objects. Each object must still be manifold and closed. Notice how at the cut line, there are faces closing off the object (like the "fill" feature of Bisect).

How might I achieve this in the least amount, and simplest amount of steps?

I have considered the bisect tool, but it does not allow cutting along arbitrary cut lines - it only allows infinite plane cutting.

I have considered the knife tool, but it is not very accurate. The knife project tool solves the accuracy portion, but it does not fill in the gaps like the bisect feature. It is quite a few steps to select faces of each portion, separate into a new object (p), then manually fill in the gaps.

Thoughts?

• Not an answer, but if you have to do this a lot, then cutting in the flat and using Solidify to extrude would seem more efficient? It might even be worth duping the top/bottom faces of existing prisms to start with Commented Oct 26, 2021 at 9:42
• Yeah unfortunately this geometry comes from an external source and is very common in the AEC industry where floor slabs are already modeled from external software. Good idea though! Commented Oct 26, 2021 at 22:04

Assuming your cutter line is extruded along the Z axis, you can use it in a Boolean modifier and invert its normals to get one part or the other.

It requires duplicating the mesh beforehand, and it seems it works reliably only with the Exact method. It might also break with more complex or non-manifold geometry. It's pretty reliable but very slow on dense meshes.

Starting with this setup :

As a python script :

import bpy
import bmesh

cutter = bpy.context.active_object
cutter_mesh = cutter.data

col = cutter.users_collection[0]
bm = bmesh.new()
bm.from_mesh(cutter_mesh)
bm_flipped = bm.copy()
for f in bm_flipped.faces:
f.normal_flip()

selected_objects = bpy.context.selected_objects

for i in range(2):
for obj in selected_objects:
if obj.type != "MESH" or obj == cutter:
continue
part = obj.copy()
part.data = obj.data.copy()

mod = part.modifiers.new(type="BOOLEAN", name="Bool")
mod.object = cutter
bpy.ops.object.modifier_apply({"object": part}, modifier="Bool")
bm_flipped.to_mesh(cutter_mesh)

bm.to_mesh(cutter_mesh)  # Restore previous normals
bm.free()  # Not mandatory, but it can help if the mesh is modified elsewhere in code


Or to modify the object mesh in place (might be more optimized ways) :

import bpy
import bmesh

cutter = bpy.context.active_object
cutter_mesh = cutter.data

col = cutter.users_collection[0]
bm = bmesh.new()
bm.from_mesh(cutter_mesh)
bm_flipped = bm.copy()
for f in bm_flipped.faces:
f.normal_flip()

selected_objects = [o for o in bpy.context.selected_objects if o.type == "MESH" and o != cutter]
parts = []

for i in range(2):
for obj in selected_objects:
part = obj.copy()
part.data = obj.data.copy()

mod = part.modifiers.new(type="BOOLEAN", name="Bool")
mod.object = cutter
bpy.ops.object.modifier_apply({"object": part}, modifier="Bool")

parts.append(part)
bm_flipped.to_mesh(cutter_mesh)

for i, obj in enumerate(selected_objects):
mesh = obj.data
offset = 0
verts = []
faces = []
for j in range(2):
part_mesh = parts[i * 2 + j].data
verts.extend([v.co for v in part_mesh.vertices])
for face in part_mesh.polygons:
faces.append([idx + offset for idx in face.vertices])
offset = len(part_mesh.vertices)
bpy.data.objects.remove(parts[i* 2 + j])
bpy.data.meshes.remove(part_mesh)
mesh.clear_geometry()
mesh.from_pydata(verts, (), faces)
mesh.update()

bm.to_mesh(cutter_mesh)
bm.free()
bm_flipped.free()