SolidCode / SolidPython

A python frontend for solid modelling that compiles to OpenSCAD
1.1k stars 172 forks source link

Interface to BOSL library? #141

Closed Efroymson closed 4 years ago

Efroymson commented 4 years ago

This is either a request for a new feature, or a documentation request. I am trying to use the bezier curve functions in the BOSL library, but can't work out how to do that from SolidPython. I've been able to build polygons, and connect them into polyhedra, which works beautifully. Now that I am trying to revisit my code and add this functionality I am just hitting a road block. For example a naive attempt to include BOSL code in python leads to obvious errors like: NameError: name 'bezier_surface' is not defined

Thanks,

etjones commented 4 years ago

I’ve never used BOSL before, but it does look pretty capable. Can you post some sample code that shows how you would like the libraries to integrate and show how they don’t work the way you expect?

Efroymson commented 4 years ago

I am not exactly sure how I would like it work, from an API perspective. I will try briefly to explain what I want to do. First, what I am doing is using polygons to build polyhedra. OpenScad naturally creates straight lines to connect the vertices when I provide it a list of vertices and faces. My code uses polar geometry to create 360-gons (usually) so I have a static routine to connect 360-gons in a stack (i.e. don't need to worry about n-gons in general).

What I would like to do is have it create bezier curves to connect the vertices. What would work best for me is to be able to provide the control points in other polygons, but BOSL expects the data points organized vertically not horizontally (if you understand me). It is easy enough to rotate the array, but how to call the function, and how to navigate the various bezier APIs in BOSL is not 100% clear to me. I will paste below a .scad file that gives an example of one way to do it, perhaps.

If it is of interest, here is the reason I am using SolidPython and OpenScad: https://bronzeartifacts.com

//file start include <BOSL/constants.scad> use <BOSL/beziers.scad>

patch1 = [ [[10,10,0], [0, 10, 7], [ 3, 12, 20], [-10,10,30]], [[ -10,10,0], [ -10, 0,5], [-8, -3, 15], [-10,-10,30]],

]; patch2 = [ [[ -10,10,0], [ -10, 0,5], [-8, -3, 15], [-10,-10,30]], [[ -10,-10,0], [ 0,-10,10], [3,-8, 20], [10,-10,30]], ]; patch3 = [ [[ -10,-10,0], [ 0,-10,10], [3,-8, 20], [10,-10,30]], [[10,-10,0], [10,0, 10], [ 8,3, 15], [ 10,10,30]], ]; patch4 = [ [[10,-10,0], [10,0, 10], [ 8,3, 15], [ 10,10,30]], [[10,10,0], [0, 10, 7], [ 3, 12, 20], [-10,10,30]], ];

patch5 = [ [[10,10,0],[10,10,0],[10,-10,0],[10,-10,0]], [[ -10,-10,0],[ -10,-10,0],[ 10,-10,0],[ 10,-10,0]],

]; patch6 = [ [[10,10,0],[10,10,0],[-10,10,0],[-10,10,0]], [[ -10,-10,0],[ -10,-10,0],[ -10,10,0],[ -10,10,0]], ];

patch7 = [ [[10,10,30],[10,10,30],[10,-10,30],[10,-10,30]], [[ -10,-10,30],[ -10,-10,30],[ 10,-10,30],[ 10,-10,30]], ]; patch8 = [ [[10,10,30],[10,10,30],[-10,10,30],[-10,10,30]], [[ -10,-10,30],[ -10,-10,30],[ -10,10,30],[ -10,10,30]], ];

vnf = bezier_surface(patches=[patch1, patch2, patch3, patch4,patch5, patch6,patch7,patch8], splinesteps=50);

polyhedron(points=vnf[0], faces=vnf[1]);

//file end

etjones commented 4 years ago

Wow, your work is really lovely, Robert! I couldn't think of a better way for this program to be used. Let me take a look at this code and see if I can come up with a best way forward.

etjones commented 4 years ago

OK! So, I spent some time with your code today and I found both a way to use it from SolidPython code (TL;DR: bosl.polyhedron() will work in SP; bosl.surface() will not) and some ways to get similar results from SP's spline code without using the BOSL code. So here's some code:

#! /usr/bin/env python

import os
from solid import *
from solid.utils import *
from solid.splines import catmull_rom_points, bezier_points
from math import radians

from euclid3 import Point3, Vector3
from solid import import_scad

# NOTE: this is default OpenSCAD library path on MacOS. If your
# libraries are somewhere else, you'll need to set this path differently
bosl_path = Path(os.getenv('HOME')) / 'Documents/OpenSCAD/libraries/BOSL/beziers.scad'
bosl = import_scad(bosl_path)

def bosl_imported():
    patch1 = [
        [[10,10,0], [0, 10, 7], [ 3, 12, 20], [-10,10,30]],
        [[ -10,10,0], [ -10, 0,5], [-8, -3, 15], [-10,-10,30]],
    ]
    patch2 = [
        [[ -10,10,0], [ -10, 0,5], [-8, -3, 15], [-10,-10,30]],
        [[ -10,-10,0], [ 0,-10,10], [3,-8, 20], [10,-10,30]],
    ]
    patch3 = [
        [[ -10,-10,0], [ 0,-10,10], [3,-8, 20], [10,-10,30]],
        [[10,-10,0], [10,0, 10], [ 8,3, 15], [ 10,10,30]],
    ]

    patch4 = [
        [[10,-10,0], [10,0, 10], [ 8,3, 15], [ 10,10,30]],
        [[10,10,0], [0, 10, 7], [ 3, 12, 20], [-10,10,30]],
    ]

    patch5 = [
        [[10,10,0],[10,10,0],[10,-10,0],[10,-10,0]],
        [[ -10,-10,0],[ -10,-10,0],[ 10,-10,0],[ 10,-10,0]],
    ]

    patch6 = [
        [[10,10,0],[10,10,0],[-10,10,0],[-10,10,0]],
        [[ -10,-10,0],[ -10,-10,0],[ -10,10,0],[ -10,10,0]],
    ]

    patch7 = [
        [[10,10,30],[10,10,30],[10,-10,30],[10,-10,30]],
        [[ -10,-10,30],[ -10,-10,30],[ 10,-10,30],[ 10,-10,30]],
    ]
    patch8 = [
        [[10,10,30],[10,10,30],[-10,10,30],[-10,10,30]],
        [[ -10,-10,30],[ -10,-10,30],[ -10,10,30],[ -10,10,30]],
    ]

    patches = [patch1, patch2, patch3, patch4, patch5, patch6, patch7, patch8]

    # NOTE:  bosl.bezier_surface() won't work in SolidPython, because SolidPython 
    # doesn't know how to get arrays back from imported SCAD code, such as the 
    # two-part `vnf` that bezier_surface() returns
    # vnf = bosl.bezier_surface(patches=patches, splinesteps=50)
    # print('vnf: %s'%(vnf,))
    # poly = polyhedron(points=vnf[0], faces=vnf[1]) <=== FAILS

    # HOWEVER, we can let the BOSL library make the polyhedron for us, and go from there:
    poly = bosl.bezier_polyhedron(patches=patches, splinesteps=10)
    return poly

# ============
# = EXAMPLES =
# ============
def curved_loft():
    height = 30
    # I use catmull-rom points because you get a curve passing through your
    # specified points rather than having to decide where control points go.
    # If you know the Bezier control points you want, you could use splines.bezier_points() to specify them
    single_curve_2d = catmull_rom_points(points=[[10, 0], [5, 5], [0, height/2], [-5, height-5], [-10, height]], subdivisions=20)
    single_curve = [Point3(p.x, 10, p.y) for p in single_curve_2d]
    all_curves = []

    UP_VEC = Vector3(0,0,1)

    sides = 4
    for i in range(sides):
        theta = i*radians(360/sides)
        rot_curve = [p.rotate_around(UP_VEC, theta) for p in single_curve]
        all_curves.append(list(rot_curve))

    profiles = list(zip(*all_curves))
    poly = loft_profiles(profiles, close_ends=True)
    return poly

# =========
# = UTILS =
# =========
def loft_profiles(profiles:Sequence[Sequence[Point3]], close_ends:bool=True):
    # Note that each sub-list in profiles should have the same number of points
    # We'll run into problems with non-convex profiles at start and end, btw
    faces: Sequence[Tuple[int, int, int]] = []
    points: Sequence[Tuple[float, float, float]] = []

    loop_count = len(profiles)
    prof_sides = len(profiles[0])

    for loop in profiles:
        for pt in loop:
            # FIXME: if pt is a Point3, we want pt.as_arr()
            points.append(pt)

    for which_loop in range(loop_count - 1):
        loop_base = which_loop * prof_sides
        last_prof_index = loop_base + prof_sides - 1
        for i in range(loop_base, last_prof_index):
            faces.append((i, i+prof_sides + 1, i+prof_sides))
            faces.append((i, i+1, i + prof_sides + 1))
        faces.append((last_prof_index, loop_base + prof_sides, last_prof_index + prof_sides))
        faces.append((last_prof_index, loop_base, loop_base + prof_sides))

    if close_ends:
        total_points = loop_count * prof_sides
        last_count_base = (loop_count - 1) * prof_sides
        # first end cap
        faces += list((0,i,i+1) for i in range(1,prof_sides))
        # last end cap
        faces += list((last_count_base, i, i + 1) for i in range(last_count_base + 1, total_points))

    return polyhedron(points, faces, convexity=10)

if __name__ == '__main__':
    a = curved_loft()
    b = right(40)(bosl_imported())
    a += b
    scad_render_to_file(a, include_orig_code=True)
etjones commented 4 years ago
Screenshot 2020-05-13 17 13 16

Here's a snip of the BOSL-based object (splinesteps=10) and the SP.splines object on the left. The BOSL library really makes much nicer looking surfaces! I'll see about adding a splines.surface() function that looks as nice.

etjones commented 4 years ago

This has been a good exercise because it shows me a couple places where SP's spline code should be generalized. 1) splines.catmull_rom_points() will only generate 2D points in X,Y right now. There's no reason this should be the case, and it would enable some smoother uses when defining a path between points in 3-space.

2) I've written the loft_profiles() code I included above lots of times, but I don't think I've ever included it in SP; I'll see about adding that to solid.utils; sometimes that's just what you want.

3) BOSL's bezier surface is a much nicer way to make smooth surfaces than the potentially long faces that loft_profiles() creates. That would be worth adding to the spline code

If you've got other questions about how to get things working for you, please ask. You're making really lovely stuff!

Efroymson commented 4 years ago

Evan,

Thank you so much! First, for your kind comments about my work, and second for the work you so quickly pulled together to make this go!

It took me a a while to be able to test this, first because I finally got around to ditching anaconda and using virtualenv to manage things, which was all driven because apparently the pip version of SolidPython doesn't have solid.splines in it, and when I tried the git version of the library it wouldn't run because my anaconda was at 3.6.something and I couldn't upgrade it because it couldn't solve the environment because of some bug or other ... but never mind all that.

I was able to get your code running (only change needed: added "import os" at top of file). The BOSL version of the solid does look better, but I thought I would try printing both out to see which looks better in PLA. Unfortunately I can't render both to STL because F6 gives a CGAL error on the BOSL version. I would paste in the error but apparently copying text out of the OpenScad console is broken? Anyway it is an assertion violation, and I suspect you will see the same thing if you try. I am on version 2019.05.

Strangely even though the SP spline version does generate an STL, my slicer program complains about it, saying there are "no layers". I have not seen it do that before, so I am not sure what to tell you. I'm using PrusaSlicer, you can get it here: https://github.com/prusa3d/PrusaSlicer/releases or here: https://www.prusa3d.com/prusaslicer/

I am able to slice the version I wrote directly in OpenScad, so I don't know quite what happened. Probably has to do with the difference between a surface and a polyhedron, but it might be down to face ordering or something like that. I see a lot of CGAL errors when I get things not quite right!

I saw you set a job for yourself to make a native SP version of the BOSL splines, which would be great.

Just one last thing: what I need is not just a solid object, but actually a hollow object with a uniform wall thickness, because I am ultimately having them cast. That turns out to be non-trivial problem to solve, but if as you are playing around with this you have any ideas, that is great.

Ok, one more last thing: are you familiar with Hobby's algorithm? It is a 2d method for generating control points for Beziers that look "nice". It is used in Knuth's metafont. It is extensible to 3d I believe, and might be of interest to you.

Thanks again, and apologies for running on,

Robert

etjones commented 4 years ago

Nah, running on is where the good stuff is!

I'll look into the CGAL errors; I did see some of that in the console.

And I haven't heard about Hobby's algorithm. Especially in OpenSCAD where it's not possible to manually drag handles around, I've avoided using Beziers where possible in favor of Catmull-Rom splines, which actually pass through the specified control points. That makes editing splines a lot easier. I wrote a Catmull-Rom version of BOSL's surface this morning, and now I'm debugging it; I'll post here when I've committed it. I'll check out Hobby's though; I'd love to be able to make smooth curves without having to mess with control points or dealing with some of the weirdness that Catmull-Rom splines create.

W.R.T. to making a shell, do you know if polyhedron() will play well with offset() or minkowski() functions? Both of those can make some kinds of thin shells. There's also BOSL's patch_translate(), which might be worth including in the splines additions I'm making.

Efroymson commented 4 years ago

Have you looked at the BOSL APIs? One thing that seems weird to me is that the patches require twice the number of points they might, if another method was chosen. If you are doing solids, like I am, then the right side of one patch is duplicated on the left of the patch on it's right, and vice versa. It seems redundant.

I am not familiar with Catmull-Rom, but I will look into it. I am pretty happy with the bezier stuff I have, and I think it will work well for me, if I can get it working not just in the ad hoc "by hand" way I did for the small file I sent, but with the full python library I have, and plan to extend.

As to shells, I just make two versions of a polyhedron, one with control points moved closer to the vertical axis. Then I subtract it from the larger. That sort of works, but sometimes there are twisting things happening in between that make the walls too thin or too thick. I used to use MeshMixer, which has built in hollowing, but it was buggy and weird.

I don't think patch_translate() will work, because that just moves the patch, it does not shrink it, which is necessary. It is especially complicated because I like to remove a chunk of the base as well, because I pay for every bit of bronze, and there is no need for a full "floor" if you understand me.

etjones commented 4 years ago

I checked the BOSL code out last week. Seems solid. I think the redundant patches are necessary because the surface functions don’t necessarily know that someone’s going to want to make a long strip connecting one patch to the next to the next; each patch is independent, and therefore its control points need to be specified twice (once as the right side of patch A, say, and once as the left side of patch B). So it makes sense, but isn’t necessarily convenient for creating closed polyhedra like you’ve done.

In fact, that may be behind the CGAL errors you’re seeing when trying to render these shapes; even though points are logically the same, they may be specified multiple times and so the program may not (or may! I definitely don’t know CGAL’s inner workings) realize there’s a closed seam between two patches.

I did get some SolidPython-native patches going that look like BOSL’s but don’t depend on any external library. Still needs some polish, though, and work really exploded on me this week.

And I did check out Hobby's algorithm, which is the best thing I’ve seen in a while! I found some javascript I can translate and I totally want to add those to the splines module. The Hobby-generated curves look so much better than naive Beziers, it’s amazing.

Anyway, I’m probably too swamped with work for a bit to be able to write all that up too soon. In the meantime, what’s the most important sticking point holding up your work? Is it the CGAL errors in all these polyhedron() objects? The lack of good shelling? There’s a bunch I’d like to do to improve SP’s spline code, but I want to make sure it’s getting you what you need.

I'm thinking about shelling with scale(), which would work for some convex shapes but probably runs into problems with some of the overhangs, etc that you've got. And a shelling system that moved each point a certain distance along the point's normal vector would work some of the time, but run into corner conditions at any, er, corners. Now I want to figure this out!

Also, have you looked at Blender at all? OpenSCAD/SolidPython are fine and all, but these curves are definitely pushing them a little bit outside of their comfort zone. I think Blender has pretty approachable Python APIs, and it or Rhino are probably better suited for making smooth curves. I mean, I love to see work made with SP, but for your own sake a NURBS -based modeler might get you closer to what you want than a prismatic one like OpenSCAD.

Anyway, I'll enjoy coming back to this when I get some more time to breathe. Thanks for showing up with interesting problems!

Efroymson commented 4 years ago

I guess what is holding me up right now is no convenient way to extend my extant python polyhedron generating code with additional control points to generate bezier surfaces. The bezier_surface() function works well in my hand-coded OpenScad examples, and I think that is the way to go, if possible.

I've looked into Blender before, but it would be sort of starting over, and I feel I've invested a certain amount of time into understanding OpenScad and it's peculiarities, so I am reluctant to do that. If Blender can use multicore rendering, that would be another reason to switch. OS is quick at previews, but full F6 renders can take a while with large and complex shapes. I am not at all familiar with Rhino, so the same applies.

I'm not too worried about the hollowing problem, it is a general problem, and there are general solutions. In fact, PrusaSlicer has hollowing for SLA printers, and should support it for FDM soon as well.

So I really just need a way to get bezier surfaces that will render to STL, and be sliceable in PrusaSlicer (that error where OS exported an STL but PS couldn't understand it is a new one!). I have a routine that generates stacks of polygons, and I can easily (relatively easily) add a routine to add polygons that contain the control points I want, or maybe compute some for smoother transitions. Then I can massage the resulting data structures into the form that bezier_surface() wants, and kick it out for rendering.

etjones commented 4 years ago

Hey - I haven't pushed to PyPI yet, but I did just push a function called splines.catmull_rom_prism() to SP. OpenSCAD doesn't complain about the generated STL at all, and it looks like it slices okay in Cura Lulzbot and Prusa Slic3r. I'll attach an example program below.

Screenshot 2020-05-21 13 43 05

Here's code that generated the surface in the picture. catmull_rom_prism() will connect a string of control points and put a flat cap on top and bottom. It's not as general as calls to BOSL.bezier_surface(), but this should suffice for a lot of use cases. And also it seems to generate workable STLs.

If you want to try it out, you'll need to pip install from Github: pip install git+https://github.com/SolidCode/SolidPython

#! /usr/bin/env python
from solid import *
from solid.utils import euclidify
from solid.splines import catmull_rom_prism
from math import pi

from euclid3 import Point3, Vector3

def polyhedron_cap():
    height = 30
    subdivisions = 20
    sides = 5
    UP = Vector3(0,0,1)

    # The curve passes through these points
    control_points = [[10, 10, 0], [5, 5, 5], [0, 10, height/2], [-5, 15, height-5], [-10, 10, height]]

    # rotate control_points around the Z axis `sides` times to specify a prism shape
    cat_tube = []
    angle_step = 2*pi/sides
    for i in range(sides):
        # euclidify() turns 3-tuples or lists of them into euclid3.Point3s, which
        # have normal vector math capabilities like rotation, scalar multiplication,
        # and vector addition.
        rotated_controls = list((euclidify(p, Point3).rotate_around(UP, angle_step*i) for p in control_points))
        cat_tube.append(rotated_controls)

    poly = catmull_rom_prism(cat_tube, subdivisions, closed_ring=True, add_caps=True)
    return poly

if __name__ == '__main__':
    a = polyhedron_cap()
    out_path = scad_render_to_file(a, include_orig_code=True)
    print(f'Wrote SCAD file to {out_path}')
Efroymson commented 4 years ago

Great! I was able to install that version of SP and run your example. It does indeed render to STL and slice just fine. It also seems to render quite quickly. Now I just have to see if I can tweak my code to generate a list of euclid3 Point3 points. That should not be too hard, I think ...

I will update you with progress (I hope!) or problems (hope not!).

Thanks again!!

etjones commented 4 years ago

I’m glad you got it working! And while solid.utils.euclidify([[0,0,0],[5,5,5]]) will return a list of Point3s, you should be able to just pass the unmodified list directly to solid.splines.catmull_rom_prism(); the conversion happens internally. So, you shouldn’t have to modify your code at all in that case. Do note that catmull_rom_prism() doesn’t take redundant lists of control points like the BOSL functions; each control curve should be included only once.

On May 21, 2020, at 3:49 PM, Efroymson notifications@github.com wrote:

Great! I was able to install that version of SP and run your example. It does indeed render to STL and slice just fine. It also seems to render quite quickly. Now I just have to see if I can tweak my code to generate a list of euclid3 Point3 points. That should not be too hard, I think ...

I will update you with progress (I hope!) or problems (hope not!).

Thanks again!!

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/SolidCode/SolidPython/issues/141#issuecomment-632337827, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEWGLM4NTT3DPZ2G3ILLIDRSWHVPANCNFSM4MXOG72A.

Efroymson commented 4 years ago

I got it working, sort of. It looks like the end caps are not being generated, and the hollows are definitely failing, but maybe I am just trying to make things too complicated. I think it will get some really cool results if I just figure out exactly how this works. You can see from the picture below (if it uploaded) that I am not quite there yet ...

Screen Shot 2020-05-21 at 17 46 27
etjones commented 4 years ago

Wow. Yep, something’s definitely wrong there. Does the OpenSCAD render look any better, or is it similarly as disjointed?

Post some code here if you want; I’d love to figure out what’s going wrong. And it might help me to figure out what I need to say in the documentation; it’s hard to see my own assumptions until I see where somebody else assumed differently.

Among other things, each control curve needs to have the same number of points as the others, and they all need to be in the same order (e.g. each curve starting with its ground point and going to its highest point, or vice versa)

I don’t think self-intersecting shapes should be an issue although they might make things hard to parse visually. If the cap shapes, the polygons made by connecting the first point in each control curve and the last point in each control curve, have irregular concavities, they could look weird, too. (A regular-ish star-shaped end cap would work, a labyrinth shape would not)

That’s all I can think of for now. You might try decreasing subdivisions to 2 or so to see if that makes it easier to sort out what’s connected incorrectly. Usually it’s off-by-one errors someplace in my vertex counts. Good luck!

On May 21, 2020, at 7:37 PM, Efroymson notifications@github.com wrote:

 I got it working, sort of. It looks like the end caps are not being generated, and the hollows are definitely failing, but maybe I am just trying to make things too complicated. I think it will get some really cool results if I just figure out exactly how this works. You can see from the picture below (if it uploaded) that I am not quite there yet ...

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

Efroymson commented 4 years ago

I was just doing something very silly with my slices. I had never used the lst[a::b] notation before, and did not understand that it works best in this case if b is held constant. b+a skips stuff ...

Once I was correctly transforming the vertices from "horizontal" to "vertical" if you understand me, it worked great. I'm printing something now.

I really appreciate all your help on this!

etjones commented 4 years ago

Glad it's worked out so far. It's been fun to add some extra abilities to the program, even after 9 years.

etjones commented 4 years ago

I don't think I'll get to it too soon, but I'll mention two improvements I want to make. One is adding Hobby's algorithm for making nice-looking curves, and two is doing curve interpolation in both directions (along control curves and between control curves) instead of only one. That should make some much more organic shapes possible.

Efroymson commented 4 years ago

Hobby's algorithm would be great! Also, I have a feeling that regular bezier curves could be interesting, for some reason I like the idea of being able to put in control point polygons that don't actually show up in the final polyhedron, but influence it. I imagine them as sort of ghosts, invisible but influential ...

The other idea is also interesting, I will have to look at the pieces I am getting and see if that would be helpful.

Finally: Nine years? Wow! Thanks again for taking time out to work on this.

etjones commented 4 years ago

So, I didn't finish my Hobby's algorithm port, but I did add a second dimension of curvature to the catmull rom surfaces and pushed it to master. If you're ever looking for smoother surfaces, this should get you at least part of the way there:

Screenshot 2020-05-24 11 23 42

The code difference between the version on the left and the one on the right is just the smooth_edges=True argument:

    poly = catmull_rom_prism(cat_tube, subdivisions, closed_ring, add_caps)
    poly2 = catmull_rom_prism(cat_tube, subdivisions, closed_ring, add_caps, smooth_edges=True)
    return poly + translate((30,0,0))(poly2)
Efroymson commented 4 years ago

I was looking at Blender earlier today, because I am having so much trouble with render times in OpenScad. I finally got around to trying this, and I get this error: TypeError: catmull_rom_prism() got an unexpected keyword argument 'smooth_edges'

I did redo the pip install from GitHub command you put in a few messages back, so I don't know what happened.

It is possible that this could help me out some, by reducing the number of vertices I need to supply to OpenScad, which in turn could reduce the effort it makes in rendering, so I don't need the still nonexistent multicore support.

Happy Memorial Day!

etjones commented 4 years ago

It could be that you'd need to do pip uninstall solidpython followed by pip install git+https://github.com/SolidCode/SolidPython to get things to update. That's my first bet.

Unfortunately, while the surface code I've added should create more reliable STLs, it won't decrease vertices, and OpenSCAD takes a looong time to render complex shapes. My process for developing shapes has usually been to have a top-level SUBDIVISIONS variable that I set low for development and only set high for final renders. The final renders still take a long time, but you can usually get pretty speedy results with low subdivisions while still being able to refine the shapes you're making.

It occurs to me that there are a couple functions in SP that might be useful to you if you haven't found them already. There's solid.splines.control_points(), that just makes red cylinders at a set of points, which is useful for debugging a Bezier or Catmull-Rom curve (so you can see the invisible ghosts pulling your curves where you want them) and there's solid.utils.extrude_along_path() which extrudes a given outline along a specified curve, which seems like it might be useful for some of the shapes you're making. Enjoy!

Efroymson commented 4 years ago

I realized after taking a break that I probably needed to uninstall first, as you commented. That worked! I also really like the effect it has. It should let me use fewer control points, which may help.

If you look at your object with "thrown together" (F12), then you will see that the top and bottom are yellow, but the sides are all purple, which means, as I understand it, that the vertices are not in the order that OpenScad expects. It is still able to render it, but I think it may be causing problems in my shapes when I try to create hollows. I notice that I can change the order of the vertices I send to OpenScad, and that will make the sides of the object correct, but in that case the top and bottom turn purple. I wonder if there is something missing in the "cap" routine that you have? It is not a problem if smooth_edges is left off, which works fine.

I've been complaining about rendering speed, but I can render a solid version of my shape lightning fast, but when I try to do a hollow, it is incredibly slow, or even crashes. It should not be much slower, it is just a subtract of a smaller version, so maybe a bit more than twice the time? But twice a very short time should still be quick. Maybe the difference() operation is just really slow with complex shapes. I think this is a separate problem from the Purple faces one, because even if the faces are all yellow, it still happens.

I am also curious is there is any way to add a "tension" parameter to the routine, because that would get it a lot closer to the flexibility of bezier curves. There is the problem of how to specify different tensions at different points of course.

etjones commented 4 years ago

Wow, I never knew about the Thrown Together trick before!. That's super useful. I just pushed a change that should make the face direction identical whether or not the smooth_edges argument is set. That doesn't mean any shape you pass will have all its faces pointing in the correct direction, but it should mean that all its faces are either pointing out (yellow) or pointing in (purple). And if you reverse the order of control_curves (catmull_rom_prism(reversed(control_curves))) all faces in the generated prism should change direction. Thanks for the tip on that one!

I think you're right about the difficulty of specifying tension properly for the curve interpolations. When I've designed shapes and the curves I was getting were too round, I've added more control points closer to the corners I wanted to keep tight, but especially if you've got two dimensions of control curves (the shapes of the curves themselves, and their placement in space, defining a different curve), that could get tricky too. For now, I don't have a sense of what would be the simplest conceptual way to control curve shape beyond adding points precisely where you want them, so I'll probably hold off on any change there until something else occurs to me.