5
$\begingroup$

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.

Example of shortcoming

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:

Object diagram

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

$\endgroup$

2 Answers 2

3
$\begingroup$

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.

enter image description here

$\endgroup$
2
  • $\begingroup$ 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. $\endgroup$ Commented Sep 13, 2023 at 8:59
  • $\begingroup$ @TiberiumFusion i'm currently working on a more exact python solution. will post it later :) $\endgroup$
    – Harry McKenzie
    Commented Sep 13, 2023 at 9:01
2
$\begingroup$

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:

enter image description here

Complex Profile with segments=32:

enter image description here

Now you have a cleaner interpolated profile: enter image description here

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

        visited.add(vertex.index)

        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.scene.collection.objects.link(obj)
    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)

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

def add_subdiv_modifier(obj):
    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)
    add_mirror_y_modifier(obj)
    add_subdiv_modifier(obj)
    bm.free()

main()
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .