# Screw modifier with mutiple curves (how to get good result)

The screw modifier is good for creating a solid mesh from a single curve profile, but it does not seem to provide any options for handling multiple curve profiles.

This is the result of using a screw modifier on two (asymmetrical) curves. The generated mesh does not line up at the seam, since the two profiles generate independent submeshes.

Can the screw modifier interpolate between the two curve profiles as it rotates around and creates each vertical slice of the mesh? The dimension of interpolation should be the radius of any point on the curve, measured from the central screw axis.

Diagram:

A result with clean topology would be a bonus, if possible.

I think the closest you can get is using Bridge Edge Loops. For it to work you need to select and rotate half of the vertices by a very small amount like $$0.0001m$$ with RZ0.0001, and then select the other side, excluding the center top and bottom vertices. Then go to menu Edge > Bridge Edge Loops and increase the number of cuts by any desired amount to fix your topology needs. Make sure you use Interpolation method Blend Path. And then add Mirror Modifier.

• It's not automatic or exact, but this is a really good approximation. The bridge edge loops operator gets a little confused at the poles, especially on complex shapes. But, on the simpler shapes, like the one from my OP, this method is close enough that manual tweaking can finish the job. Getting a good topology seems to lie in having an exact & uniform vertex distribution (by segment length between vertices) for every profile curve when converting them to meshes. Will need to find an addon that can do this. Commented Sep 13, 2023 at 8:59
• @TiberiumFusion i'm currently working on a more exact python solution. will post it later :) Commented Sep 13, 2023 at 9:01

Here's a more exact solution using python. Make sure your shape is on the XZ plane and the number of vertices are exactly the same on the left and on the right of the Z axis. Select the top most vertex and run the script. This will generate a new object that interpolates each ring of vertices from the left curve profile to the right curve profile.

Simple Profile with segments=16:

Complex Profile with segments=32:

Now you have a cleaner interpolated profile:

Here's the script:

import bpy
import bmesh
import math
from bpy import context as C
from bpy import data as D

def deselect_all_objects():
for obj in C.scene.objects:
obj.select_set(False)

def traverse_vertices(bm, pairs, forward):
start_vertex = bm.select_history.active
iterations = int(len(bm.verts) / 2)
i = 0
visited = set()
stack = [start_vertex]
end = -1

while stack and i < iterations:
vertex = stack.pop()
if vertex.index in visited:
continue

if i > 0:
pairs[i-1][0 if forward else 1] = vertex.index

neighbors = [edge.other_vert(vertex) for edge in vertex.link_edges]
stack.extend(neighbors if forward else reversed(neighbors))
end = neighbors[1]
i += 1

return (end.co.x, end.co.y, end.co.z)

def init(bm, obj):
if not obj:
raise RuntimeError("You need to select a mesh!")

bpy.ops.object.mode_set(mode="OBJECT")
bm.from_mesh(obj.data)
total_verts = len(bm.verts)
selected_verts = len([v for v in bm.verts if v.select])

if total_verts % 2 > 0:
bm.free()
raise RuntimeError("You need an even amount of total vertices")
elif selected_verts != 1:
bm.free()
raise RuntimeError("You must select exactly 1 vertex")
obj.hide_set(True)
return [[0, 0] for _ in range(int(total_verts / 2)-1)]

def cubic_bezier_z(z1, z2, t):
mt = 1 - t
mt2 = mt * mt
t2 = t * t
z = z1 * mt2 * mt + 3 * z1 * mt2 * t + 3 * z2 * mt * t2 + z2 * t2 * t
return z

def create_mesh_ring(bm, v1, v2, segments):

x1, x2 = v1.co.x, v2.co.x
r1, r2 = (abs(x1), abs(x2)) if x1 > x2 else (abs(x2), abs(x1))
z1, z2 = (v1.co.z, v2.co.z) if x1 > x2 else (v2.co.z, v1.co.z)

h = segments // 2
verts = []

for i in range(segments + 1):
f = i < h
j = (i + (0 if f else 1)) % (h + (0 if f else 1))
p = j / h if f else (h - j) / h
angle = math.pi * 0.5 * p

r = r1 if f else r2
x = r * math.cos(angle - (0 if f else math.pi))
y = min(r1, r2) * math.sin(angle)
z = cubic_bezier_z(z1, z2, i / segments)
verts.append(bm.verts.new((x, y, z)))

return verts

def create_mesh(bm_orig, ref_v, end_v, pairs, segments):
mesh = D.meshes.new(C.active_object.name + "_result")
obj = D.objects.new(mesh.name, mesh)
C.view_layer.objects.active = obj
obj.select_set(True)

bm = bmesh.new()
bm_orig.verts.ensure_lookup_table()

prev_verts = None
v = bm.verts.new(ref_v)
v_end = bm.verts.new(end_v)

for p in pairs:
v1, v2 = [bm_orig.verts[i] for i in p]
verts = create_mesh_ring(bm, v1, v2, segments)

if prev_verts:
for j in range(1, len(verts)):
v1, v2, v3, v4 = prev_verts[j], verts[j], prev_verts[j-1], verts[j-1]
bm.faces.new([v2, v1, v3, v4])
if p == pairs[1]:
bm.faces.new([v, v3, v1])
if p == pairs[len(pairs)-1]:
bm.faces.new([v_end, v2, v4])

prev_verts = verts

bm.to_mesh(obj.data)
bm.free()
return obj

def get_reference_vert(bm):
x, y, z = bm.select_history.active.co
return (x, y, z)

mirror_modifier = obj.modifiers.new(name="Mirror", type='MIRROR')
mirror_modifier.use_axis[0] = False
mirror_modifier.use_axis[1] = True

subdiv_mod = obj.modifiers.new(name="Subdivision", type='SUBSURF')
subdiv_mod.levels = 1
subdiv_mod.render_levels = 2

def main():
obj = C.active_object
segments = 16
bm = bmesh.new()
pairs = init(bm, obj)
v = get_reference_vert(bm)
traverse_vertices(bm, pairs, True)
end = traverse_vertices(bm, pairs, False)
deselect_all_objects()
obj = create_mesh(bm, v, end, pairs, segments)