1
$\begingroup$

I am developing a commandline tool to experiment with lofting a set of curves, and would appreciate any guidance.

Usage

blender_loft c0.txt c1.txt output.txt          # simplest to start with

Input

  • c0.txt, c1.txt: two or more 3D curves represented as a text file each listing the ordered curve samples, one xyz coordinate triplet per line. These are usually just polylines finely sampling some curve fragments.

Output

  • A file storing a surface between these curves, tautly stretching over the curve fragments as best as possible.

Requirements

  • Speed is desirable but not a big deal since I just want to experiment first prior to writing optimal code.
  • Easy to install for others to use. Ideally, just downloading blender should suffice.

What I have so far

  • So far it seems BSurfaces's loft functionality is the most feature-rich out there, so I went with that. I want to target all its fancy controls and possibilities in the long run.
  • A bpy script that reads the text files, creates bezier curves from the samples, and try to simulate what Bsurfaces does - for now it is limited to this: enter image description here

Code I have so far

# Loft curves given by files into surface
#
# INPUT
#    p0.txt, p1.txt
#
# OUTPUT
#    loftsurf.ply
#
# USAGE
#
#   Commandline
#      blender -b -nosound -P blender_loft.py
#       
#   Inside blender console
#
#      filename = "PWD/blender_loft.py"
#      exec(compile(open(filename).read(), filename, 'exec'))
#
# AUTHOR
#   Ricardo Fabbri <rfabbri@gmail.com>, 3 Dec 2015
#
import bpy
import bpy_extras
import numpy
from mathutils import Matrix

# Create a spline/bezier from a list of points
def new_curve_from_points(p0,name_prefix):
    npts = len(p0)
    c0 = bpy.data.curves.new(name_prefix + 'CurveToLoft', 'CURVE')
    o0 = bpy.data.objects.new(name_prefix + 'CurveToLoft', c0)
    bpy.context.scene.objects.link(o0)
    c0.dimensions = "3D"

    spline = o0.data.splines.new('BEZIER')
    spline.bezier_points.add(npts - 1) 
    # ^-- less one because one point is added when the spline is created.
    for p in range(0, npts):
        spline.bezier_points[p].co = p0[p]
        # spline.bezier_points[p].co = [p0[p][0], p0[p][1], p0[p][2]]
    return c0, o0, spline 

def read_files():
    p0_fname = 'p0.txt'
    p1_fname = 'p1.txt'
    p0 = numpy.loadtxt(p0_fname)
    p0.tolist()
    p1 = numpy.loadtxt(p1_fname)
    p1.tolist()
    return p0, p1

# to be called inside console after testing
def cleanup():
    bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
    bpy.ops.object.delete(use_global=False)

def test():
    print('hello')
    p0 = []
    p0.append([1, 0, 0])
    p0.append([0, 0, 0])
    p0.append([0, 1, 0])

    p1 = []
    p1.append([1, 1, 0])
    p1.append([0, 1, 0])
    p1.append([0, 0, 1])
    loft(p0,p1)

def test1():
    # cleanup()
    p0, p1 = read_files()
    loft(p0, p1)
    bpy.ops.export_mesh.ply(filepath='loftsurf.ply')

def loft(p0, p1):
    c0, o0, s0 = new_curve_from_points(p0,'first')
    c1, o1, s1 = new_curve_from_points(p1,'second')

    # join curves into a group
    bpy.data.objects[o0.name].select = True
    bpy.data.objects[o1.name].select = True
    bpy.context.scene.objects.active = bpy.data.objects[o1.name]
    bpy.ops.object.join('INVOKE_REGION_WIN')

    # create a mesh to store the final surface
    me = bpy.data.meshes.new("outputLoft")
    ob = bpy.data.objects.new("outputLoft", me)
    scn = bpy.context.scene
    scn.objects.link(ob)
    scn.objects.active = ob
    ob.select = True

    # curves + object should be selected
    # call lofting
    bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT', toggle=True)
    bpy.ops.mesh.reveal()
    bpy.ops.gpencil.surfsk_add_surface('INVOKE_DEFAULT')

if __name__ == "__main__":
    test1()

The Issue

The current solution works from the GUI python console, but I'm still having a hard time calling it from commandline without the '-b' option.

  • Cheating by calling without '-b' and calling bpy.ops.wm.quit_blender() in the end main works OK as prototype.
  • This is basically a context problem

Remarks

  • Besides the current issue above, I would like to call the script directly from C or standalone python so that we don't have to set up blender entirely each time we're lofting between curves.
  • I cannot use a lower-level API than bpy with this solution since BSurfaces is very intertwined with bpy to the basic level.

This discussion should be useful for anyone writing tools that seemlessly use blender in the background. I am not very experienced with blender. My main question is whether this all looks like a decent way to go, besides the above specific issue.

Note: After I initially wrote this question, partial solutions have been evolving and I have been updating this accordingly. Sorry if it seems like a multi-question question during this process. Feel free to post partial answers and evolve, this would already be useful, though I will accept the answer if it is thorough enough.

$\endgroup$
  • $\begingroup$ Edit: I quickly wrote the script listed in the question as a partial answer, now moved into the question body. $\endgroup$ – rfabbri Dec 4 '15 at 0:51
  • 1
    $\begingroup$ Why are you making two Curve Objects when you can make a single Curve Object which contains two disjoint Beziers? (this is what the Bsurfaces code wants anyway) call data.splines.new('BEZIER') a second time on the same object and it will return a new reference to the next disjoint bezier. $\endgroup$ – zeffii Dec 4 '15 at 10:17
  • $\begingroup$ @zeffii true. good to learn. I just hacked it quickly, thats why.. tks. $\endgroup$ – rfabbri Dec 4 '15 at 11:43
  • 1
    $\begingroup$ You have something like this and that's throwing errors? - it throws errors here GPENCIL_OT_SURFSK_add_surface object has no attribute 'is_fill_faces' $\endgroup$ – zeffii Dec 4 '15 at 14:21
  • $\begingroup$ @zeffii awesome, I wasn't sure even if I should open a new question and have to explain it all over again. I'll do what you suggested. $\endgroup$ – rfabbri Dec 4 '15 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.