Skip to content

Releases: chisomobanzi/Serpentine3D

Serpentine3D 0.9.0

Choose a tag to compare

@chisomobanzi chisomobanzi released this 08 Sep 09:54

Serpentine3D 0.9.0 makes the Mica point-cloud session record a released file-format contract. Native version 3 .serp files now declare requires: "0.9.0", so scanners and Serpentine agree on the minimum compatible reader.

Point-cloud workflow

  • Open a Mica session or PLY scan as a first-class pointcloud object, then save it back without losing points, colours, confidence, levels, trajectories, or session metadata.
  • Draw scans through a dedicated GL point path with far-geometry anchoring, frustum culling, and a configurable two-million-point display budget.
  • Pick the nearest point, select and tint the cloud, inspect its bounds and per-level counts, and use pointcloud info, pointcloud subsample, and selpointcloud.
  • Skip point clouds cleanly in technical, hidden-line, Make2D, and export formats that cannot represent them.

Compatibility

  • Drawings containing point clouds, trajectories, or session data use native format 3 and require Serpentine3D 0.9.0.
  • Ordinary drawings continue to use native format 2 and remain compatible with older releases.

Known limitations

  • Trajectories round-trip in the file but are not drawn yet.
  • Current Mica sessions can appear tilted against Serpentine's Z-up grid until
    the Mica writer applies the shared camera-to-points transform and gravity
    alignment from the phone IMU.

Validation

  • All 2,882 tests were collected on Linux: 2,875 passed under the offscreen runner and 7 OpenGL-context tests skipped there; all 5 SpaceMouse tests passed with local-socket access.
  • The packaged AppImage passed its application/geometry self-test, reported version 0.9.0, and answered a live RPC startup probe.

Full details: CHANGELOG.md

Serpentine3D 0.8.4

Choose a tag to compare

@chisomobanzi chisomobanzi released this 07 Sep 17:16

Serpentine3D 0.8.4 adds native point-cloud workflows and makes held-face editing behave more like direct modelling.

Added

  • Import, display, select, inspect, save, and export point clouds, including PLY interchange and version 3 .serp session records.
  • Align whole-object and sub-object gumballs to World, CPlane, or Object axes with consistent red, green, and blue axis colours.
  • Use every meaningful move, rotate, and scale handle on held planar faces while adjacent faces rebuild to meet the result.

Fixed

  • Keep planar faces rigid while moving or rotating them, and keep the gumball attached to the last valid result during fast or invalid drags.
  • Preserve holes when extruding nested closed curve profiles.
  • Pin the AppImage geometry kernel to the compatible OCP release and validate the bundled application before updating the installed copy.

Validation

  • Linux: 2,882 tests passed headlessly.
  • Packaged AppImage and macOS application self-tests run as part of their build scripts.

Full details: CHANGELOG.md

Serpentine3D 0.8.3

Choose a tag to compare

@chisomobanzi chisomobanzi released this 03 Sep 15:46

Six things asked for by one early user, closed in a week, plus one of
our own.

Added

Getting at the object behind the one in front. A click has always
taken the nearest thing under the cursor, so an object inside or behind
another could only be reached by hiding whatever was in the way. Hold
the left button still for a moment, or Alt+click, and everything under
the cursor is offered as a short list, nearest first, each row carrying
the object's colour, name and kind. Pointing at a row lights that object
in every pane; the arrow keys walk the list, Enter picks, Esc cancels. A
plain click is unchanged.

A held face or edge gets a whole gumball. Ctrl+Shift-click a face
and the gumball used to shrink to one arrow. A flat face now gets the
arrow, two rings and the filled box. The arrow moves the face and lets
the faces beside it stretch to meet it, so a chamfer stays a chamfer;
the rings tilt the face about its own edges (Shift snaps to 15 degrees);
the box, or Ctrl and the arrow, extrudes it with new walls. A held edge
keeps its fillet arrow and gains an arrow along each face it sits
between, which moves the edge the way MoveEdge does: lift the top-front
edge of a box and the top tilts while the front just gets taller. Typed
values work on all of them.

Delete takes out a face you are holding. Ctrl+Shift-click a face,
press Delete, and it goes. A solid that loses a face becomes an open
shell, which is the point: it is how you get at the inside of something,
or lift a lid off to put it on a different shape. The command line says
which objects stopped being closed. Delete already worked this way for
held control points.

rotate3d lets you drag the angle, the way rotate does. It used
to ask for the axis and then demand a number. It now asks for a
reference direction and for where that direction should end up, turning
the objects live as you swing the second pick round. Typing an angle
still works: the reference prompt takes a bare number.

A clipping plane shows which way it faces. A rectangle looks the
same from both sides, so nothing said which half of the model the plane
was about to take away. There is now an arrow at the middle of the plane
pointing at the half that goes, a fixed size on the glass, and a paused
plane keeps a fainter one, since deciding which way round a plane goes
is mostly something you do while it is switched off.

arraypath turns the copies to follow the path. It used to
translate and nothing else, so a row of chairs set out along a curving
path all faced the way the first one did.

Fixed

Deleting the curves make2d drew no longer kills a viewport.

Installers

Linux AppImage, Windows installer, macOS .dmg (Apple silicon). The
macOS build is unsigned: right-click, Open the first time.

Serpentine3D 0.8.2

Choose a tag to compare

@chisomobanzi chisomobanzi released this 02 Sep 17:40

Fixed

The app starts on Wayland.

Reported from Manjaro/KDE on an RTX 3080: the welcome window came up
over a stream of QEGLPlatformContext: Failed to create context: 3009,
and New Model did nothing at all. Two assumptions in the startup path
only ever held on X11's GLX, and eight releases went out without either
being noticed.

The first is the surface format. Serpentine3D asked for an OpenGL 3.3
core profile without ever saying it meant desktop OpenGL rather than
OpenGL ES. On GLX there is nothing else it could have meant. On EGL,
which is what Wayland gives you, Qt takes the default and binds OpenGL
ES, which has no core profile, so every context request comes back
EGL_BAD_MATCH (3009): the viewport's, and the one Qt needs for its own
window backing store, which is why the errors started before anything
of ours had tried to draw.

The second is behind that one, and is why New Model did nothing rather
than showing an error. Qt and PyOpenGL each decide independently
whether to reach the driver through GLX or EGL, Qt by asking the driver
and PyOpenGL by reading the session type. When the two disagree,
PyOpenGL cannot see the context it has been handed, and the viewport's
first drawing call raises. Serpentine3D now asks Qt which binding it
ended up on and tells PyOpenGL, instead of letting both guess. That
covers the mirror case too, a Wayland session that falls back to
XWayland and GLX.

Installers

Linux AppImage, Windows installer, macOS .dmg (Apple silicon). The
macOS build is unsigned: right-click, Open the first time.

0.8.1: far geometry holds still, and a pick reads clearly

Choose a tag to compare

@chisomobanzi chisomobanzi released this 02 Sep 15:02

A patch cut the same day as 0.8.0, all of it fixes, most of them things
you can see the moment you touch them.

The gumball and friends hold still far from the origin too

0.8.0's follow-up anchored the geometry, but everything drawn over it,
the gumball, the control-point display, the rubber band, ghosts,
curvature combs, direction arrows, image planes and the grid of a far
construction plane, still went to the GPU in absolute coordinates and
trembled against the now solid model. Overlays redraw every frame, so
they all share one anchor now: where the camera is looking. Near the
origin the frame is bit-for-bit what it was.

A picked edge is drawn wide enough to actually see

The highlight was there all along, but it asked glLineWidth for its
pixels and plenty of drivers cap lines at one, so the pick came back as
a gold hairline over a gold object. It now goes through the same
screen-space wide line shader the object edges already use, gold over a
dark rim like the control-point markers, on every driver and in details
on sheets as well. Reported by Lourenço.

A live fillet drag no longer lights up a random edge

Dragging the gumball's fillet handle rebuilds the solid on every mouse
move, and the picked edge's index then names some unrelated edge of the
new topology, so the highlight wandered while you dragged and could
stay on the wrong edge after. The highlight now goes quiet for the
object being rebuilt, in every pane, until the drag settles; the
growing fillet is the feedback. Push/pull and multi-face drags had the
same latent wander and are covered by the same flag.

Zoom Selected reaches what you are holding

Pick a control point (or an edge, or a face) and ask for Zoom Selected,
and the command said "Nothing selected": it only ever asked about whole
objects. A held sub-object now counts, framed together with any objects
selected beside it, and zoom opens on Selected when one is held.


Suite: 2697 passed on Linux, nothing skipped.

Downloads: the Linux AppImage below; the Windows installer is built
and attached automatically a few minutes after publish; the macOS
.dmg follows.

Serpentine3D 0.8.0

Choose a tag to compare

@chisomobanzi chisomobanzi released this 01 Sep 16:56

Added

  • A section cut is hatched by what it cut through. A detail with a
    section on it drew every cut face the same, so a drawing of a concrete
    wall with a steel pipe through it read as one material. The cut now
    carries the object it came from, and each face is drawn in the hatch
    of that object's layer: cross for the concrete, solid for the steel,
    plain lines for a layer that says nothing. Solid is a flood and not a
    line pattern, so a cut face keeps its bores open where a set of lines
    could not. Screen and plot ask the same two functions, so the sheet
    and the PDF cannot disagree. Clicking a cut face with hatch in
    Region mode opens the prompt on the material that was cut, so
    dropping a real hatch into a face does not quietly change what the
    face is made of.

  • A layer says what its material is hatched with. A drawing is read by
    its fills, and what a thing is made of belongs to the layer rather than to
    the afternoon it was drawn on. Set Concrete to cross once and the hatch
    command opens on cross every time it is drawn on Concrete, in Corners mode
    and in Region mode, so hatching a wall is a click and an Enter. It stays a
    prompt, so the one region in another material still gets what you type.
    Set it from the Layers row menu, on every picked layer at once, or with
    layer and its new Hatch action. A layer that says nothing leaves the
    command where it was, on lines.

  • section: draw a line across the model and get the cut back. Pick
    the objects, then the two points the saw goes through. The plane stands
    on that line and leans with the construction plane, so the same two
    points cut vertically in Front and horizontally in Top. A solid gives
    back the filled face rather than its outline, which is what a section
    drawing shows and what a hatch will need: a pipe cut across stays a ring
    of wall with a bore down the middle instead of reading as two unrelated
    circles. A surface has no inside, so it gives the curve. Everything
    lands on a Sections layer, and the objects you picked are left alone.

Fixed

  • A hatch over a section leaves the holes in it empty. A cut through
    a pipe is a ring of wall with a bore down the middle, and hatching it
    as one outline filled the bore in, so the drawing said "bar" where the
    model said "pipe". A detail's section cut now comes back as regions,
    the ring around the outside plus whatever is punched out of it, and
    hatch in Region mode picks that up when you click on the wall. The
    screen and the plot used to work the hatching out separately and could
    disagree about the same cut; they now ask one function, so what you
    see on the sheet is what comes out of the PDF.

  • The Layers panel is as wide as its columns need, not as wide as our
    own font wanted.
    The panel opens at a set width and the layer name
    takes whatever the four narrow columns beside it leave, so on a machine
    whose sans-serif is wider than the one this was measured on there was
    nothing left and the layer you draw on read Defa.... The panel now
    says how many pixels short it is and the window hands them over. A width
    you have set yourself is left alone.

Serpentine3D 0.7.3

Choose a tag to compare

@chisomobanzi chisomobanzi released this 01 Sep 11:12

Added

  • Sublayers. A layer can now live under another layer, as deep as you
    like. Walls holds Interior and Exterior, Exterior holds
    Cladding, and the layers panel draws each one indented under its
    parent with a triangle to open and close the branch. The button
    below the list makes a new layer under the one you have picked, and a
    layer can be dragged onto another to move it, along with everything
    under it. The button beside it takes the picked layer back out
    again, one level at a time, and a right-click on a row offers the same
    move by name ("Move out of Walls") along with "Move to the top level"
    for a layer buried deeper. Switching a parent off takes its whole branch
    off the screen and locking a parent locks the branch, while each layer
    keeps its own switch: a child under a switched-off parent reads greyed but
    is still set to on, so switching the parent back on brings the branch back
    the way you left it. Deleting a parent deletes the branch and moves the
    objects to Default. Requested by Lourenço Vaz Pinto.

  • The layer tree is on the API too. scene_info reports each layer's
    path and parent, plus shown, which is whether the branch above it
    lets it onto the screen as against the layer's own switch. The layers
    call takes a parent to make a sublayer with, and a layer can be named
    by its path, so an assistant asked to work on Walls::Interior no
    longer has to guess which Interior was meant. Same on the MCP server.

  • A layer tree is saved and loaded. Both .s3d and .3dm carry the
    tree. On the way out to Rhino a layer is written with its parent and
    with the visible and locked switches Rhino keeps for a layer of its own,
    so a branch you switched off there comes back switched off here.

  • Layers move up and down the list. Two arrows under the list move the
    picked layer, so the layers read in the order the drawing wants rather than
    the order they happened to be made in. A layer moves among its own siblings
    and takes its branch with it, so one press steps over a whole branch;
    several picked layers travel as a block and stop as a block at the end of
    the list; one press is one undo, and a press that can move nothing costs
    none. The order is written into both .s3d and .3dm, the Default layer
    included. Requested by Lourenço Vaz Pinto.

  • Layers can be dragged where they belong. Dropping a layer on another
    one has always put it inside; dropping it between two rows now moves it
    there, and dropping it above the first row or below the last takes it to
    either end. That is also the way back out of a branch: drag a sublayer into
    the gap between two top-level layers and it leaves the branch and lands in
    that gap. A line shows where the layer would land, indented to the level it
    would land at, so a drop beside a branch reads differently from a drop into
    it. The quarter of a row at each end means beside it and the half between
    them means inside it, rather than the two pixels Qt offers. Several layers
    dragged together land in the order they were in, and one drop is one undo.
    Requested by Lourenço Vaz Pinto.

Fixed

  • Walls::Interior and Roof::Interior no longer collide on import.
    Reading a .3dm looked only at a layer's own name, so two sublayers
    named the same under different parents landed on one layer and the
    objects of both were merged onto it. An imported layer is now matched by
    its full path, and the parents it hangs from are made as it goes, with
    the colour, print width and switches the file gives them. Reported by
    Lourenço Vaz Pinto.

Changed

  • The layer you draw on is chosen with a double-click. A single click
    on a layer's name used to make it the current layer, so clicking three
    layers in turn to switch them off left the last one current and the next
    curve landed there. A click now only picks the row; a double-click on
    the name says which layer to draw on. Renaming a layer, which that
    double-click used to do, is on the row's right-click menu, and the
    buttons under the list are grouped by what they do: the two that make a
    layer, the one that moves it, the one that deletes it. Reported by
    Lourenço Vaz Pinto.

  • The + button makes a layer beside the one you picked. It used to add a
    top-level layer at the bottom of the panel however deep the picked row was,
    so pressing it with a sublayer picked gave that layer an uncle rather than
    a sibling, nowhere near the row being looked at. The new layer now lands
    directly under the picked one, at the picked one's level: + makes a layer
    beside it, makes one inside it. With nothing picked it still goes at
    the end of the top level. Either button leaves the new row picked too, so a
    second press works from the layer just made and a name typed straight away
    lands on it. Reported by Lourenço Vaz Pinto.

Serpentine3D 0.7.2

Choose a tag to compare

@chisomobanzi chisomobanzi released this 01 Sep 07:01

Added

  • Space is Enter at the command line. Type line and press Space and
    the command starts; type a point and Space commits it; Space on an empty
    prompt repeats the last command, all exactly as Enter does and as Rhino
    does, so one hand can stay on the mouse. While a command is asking for
    text, a layer name say, Space is still a space, so "Ground floor" can be
    typed. Requested by Lourenço Vaz Pinto.

  • The right mouse button orbits, out of the box. Rhino's chords on the
    orbit button are now Serpentine's: drag orbits, Shift pans, and Ctrl
    zooms, dragging up to come closer. Ctrl+Shift orbits from anywhere,
    including a parallel view, where a plain drag pans. The Preferences page
    now says which button Rhino uses instead of claiming the middle one is
    the Rhino default; anyone who chose the middle button keeps it. A
    right-click with no drag is still Enter. Requested by Lourenço Vaz Pinto.

  • Layer linetype and print width are drop-downs. The Type cell offered
    its options one click at a time, each click rolling on to the next
    linetype with no way to see the list, and Print was a bare box to type
    into. Both now open a list: every linetype for Type, and Default plus
    the standard pen widths (0.13 to 1.0 mm) for Print, which still takes a
    typed width such as 0.42. A single click on Type no longer changes
    anything. Requested by Lourenço Vaz Pinto.

  • Several layers can be switched off at once. The layers panel takes a
    Ctrl- or Shift-click selection, and the on/off box of any selected layer
    applies to all of them, as one undo step and one redraw. Clicking a box
    leaves the selection as it was, so the next click switches the same
    layers back on, and a Shift-click picks the range from the layer you
    last clicked, not from the top of the list. Requested by Lourenço Vaz
    Pinto.

  • A detail's scale is set in the Properties panel. Pick a detail on a
    sheet and the panel shows it as a detail, with its frame size and a Scale
    drop-down of the scales an architect draws at, 1:1 to 1:200, that also
    takes a typed 1:75. A scale that makes no sense is refused and the
    control goes back to the real one. detailscale still works. Requested
    by Lourenço Vaz Pinto.

Fixed

  • isolate on a big file no longer crawls. Hiding, showing, locking,
    grouping, restyling or re-layering many objects at once told the viewport
    about each object separately, and the viewport rebuilt everything it
    draws each time, so isolating one object among three hundred rebuilt the
    scene three hundred times. Every command that changes many objects now
    reports once for the lot: isolate, unisolate, hide, show, lock,
    lockother, unlockall, group, ungroup, changelayer, linetype,
    matchprops, material and the draw-order commands. Reported by
    Lourenço Vaz Pinto.

  • Enter in a Properties field no longer runs the last command. Type a
    new name for an object, or a scale for a detail, and press Enter, and the
    edit was applied and then the last command started again on top of it,
    because an Enter that nothing consumed fell through to the command line's
    "Enter repeats the last command". A field keeps its Enter now; the command
    line and the viewport still repeat.

  • Two crashes in the layers panel. Renaming a layer by double-clicking
    its name, and clicking its on/off box, could each delete the row Qt was
    still holding and take the app down with it; the panel now finishes
    handling a click before it redraws.

  • The layers panel fits the space it is given, and its drop-downs can be
    read.
    Adding the Type and Print columns had made the tree wider than the
    panel column a fresh window gives it, so Print sat off the right-hand edge
    behind a sideways scrollbar and Type was cut in half. The layer name column
    now takes whatever room is left over and the other four keep the width
    their content needs, so all five are on screen at any panel width. The
    Type and Print drop-downs open at the width their own list needs rather
    than the width of the cell, where "Continuous" came up as "Contin", and
    stay inside the panel when there is nothing to their right.

Serpentine3D 0.7.1

Choose a tag to compare

@chisomobanzi chisomobanzi released this 22 Aug 05:58

Added

  • A layer carries a print width and a linetype you can set in the panel.
    The layers panel grows a Print column and a Type column: Print is the pen
    width in millimetres a layer plots at, blank for the device default, and
    Type cycles the layer's linetype. A detail on a layout now plots each
    layer's edges at that layer's width, in PDF and in DXF, rather than the one
    width every line used to get. The on-screen weight is untouched, so nothing
    looks different in the viewport, and the width round-trips through a .3dm as
    Rhino's PlotWeight. Requested by Jonas Pedrotti.

  • mergeallcoplanarfaces tidies a polysurface after a boolean. A union
    of two boxes side by side leaves each spanning side split into two
    coplanar strips, so the result has ten faces where six would do.
    mergeallcoplanarfaces (alias mergeallfaces) fuses coplanar neighbours
    and drops the seam edges with them, so the box reads as a box again. This
    is Rhino's MergeAllCoplanarFaces. The volume does not move, and an object
    with nothing to merge is left alone. Requested by Jonas Pedrotti.

  • join works on surfaces, not just curves. Pick two surfaces that
    share an edge and join (alias j) now stitches them into one
    polysurface, exactly as it always has for curves. Surfaces that close up
    a volume between them, the six faces of a box for instance, come back as
    a solid rather than a hollow shell. Surfaces that touch nothing are left
    where they are, and the command says so instead of quietly bundling them
    together. Requested by Jonas Pedrotti.

Fixed

  • A boolean that cuts a solid in two now gives you two objects. Saw a
    bar in half with booleandifference and Serpentine kept it as one: one
    name in the layers panel, one gumball sitting in the gap, and both halves
    moving together. The kernel was never confused, it handed back a compound
    with a solid in it per piece, but nothing downstream looked inside. Now
    each loose piece is its own object, the first one keeping the original's
    id so its name, its layer and anything built from it survive the cut.
    Union and intersection do the same, so unioning two solids that never
    touch leaves two objects rather than one. Anything still joined is left
    alone, including a solid with a void in it, which has a second shell but
    is emphatically still one object. Reported by Jonas Pedrotti.

  • explode on a severed solid gives you the pieces, not the faces.
    A bar cut in half exploded into twelve faces rather than the two bars,
    because the shape was asked what kind of thing it was before it was asked
    what it held. Compounds of curves exploded into nothing at all, and now
    come apart properly too.

  • Text files are UTF-8 everywhere, not the machine's locale. A document,
    a config, an autosave, an .obj, an .stl, a .usda or a journal was read and
    written in whatever encoding the operating system defaulted to, which is
    UTF-8 on Linux and macOS but can be cp1252 on a Windows box in a non-English
    locale. A layer name or a file path outside ASCII round-tripped to mojibake,
    or raised, on that one machine and nowhere we could reproduce. Every text
    file now states UTF-8.

Serpentine3D 0.7.0

Choose a tag to compare

@chisomobanzi chisomobanzi released this 17 Aug 17:12

Changed

  • curve draws by control points now, and interpcrv is its own
    command.
    These are Rhino's names, and they finally mean what Rhino
    means by them: curve (alias cv, for control vertex) pulls a NURBS
    curve toward the points you pick, and interpcrv runs one through them.
    Serpentine's curve used to be the interpolating one, so a Rhino user
    typing the command they use most got the other tool with no sign that
    anything was wrong: both make a smooth curve through roughly the right
    place, and only the control points give it away. A script, alias or habit
    that relied on curve interpolating should say interpcrv.

Added

  • You can see the curve while you are drawing it. Both curve commands
    now ghost the curve the next click would make, instead of showing only
    the points already picked and the straight chain between them. That chain
    is the one shape the curve is not: an interpolated curve bulges away from
    it, and a control point curve does not go through the middle of it at
    all. So interpcrv drops the chain entirely, while curve keeps it,
    because there the chain is the control polygon and part of what you are
    drawing rather than a stand-in for it.

  • dir and flip, Rhino's pair for direction. Which way a curve runs
    and which way a surface faces are invisible until something draws them,
    and then they decide what happens next: which end an offset comes out on,
    which way a sweep travels, which side a shell thickens. dir stands green
    arrows on what you picked, along a curve or off a surface, and stays up
    with Flip on the prompt so you can turn a face round and watch the arrows
    come back the other way. flip is the same edit without the looking.
    Reversing works on the geometry rather than the topology, so a circle
    turned round is still a circle rather than the b-spline a conversion would
    leave behind. The arrows are a fixed size on screen and they belong to the
    command: they go when it ends, including on Escape.

  • undoview and redoview, a history for the camera. An orbit that
    went too far leaves the drawing exactly as it was, so the general undo is
    no help at all: there is nothing about the model to take back, and what
    you want is the view you had a second ago. A whole gesture is one step, so
    a drag that turns the model right round comes back in one, and so does a
    burst of wheel clicks. Typing top or running zoomextents is a step
    like any other. Each pane keeps its own history, because where the Top
    view has been says nothing about where Perspective has been.

  • lockother, the other half of isolate. Isolate takes the rest of the
    drawing off the screen; this leaves it up to line the new work against and
    only stops you picking it, which is what you actually wanted when the
    thing you keep catching is a background curve you have no intention of
    moving. What you picked stays picked, and unlockall releases the rest.
    The scripting API reports locked on every object now, so a script can
    see why a pick found nothing.

  • insertknot, insertcontrolpoint, removeknot and
    removecontrolpoint
    , under Rhino's names. Inserting a knot is the one
    edit that hands you a control point without moving the curve by so much
    as a tolerance, so you can get a handle where you want to pull from
    rather than making do with the ones the curve was built with; Automatic
    puts one in the middle of every span. Removing a knot goes the other way
    and does move the curve, so the command says by how far. With several
    curves picked, the point you click says which one you meant and which
    knot you meant. Both commands turn the control points on for what you
    picked, and both preview under the cursor: the control polygon you would
    get for an insertion, the curve you would be left with for a removal.

Fixed

  • A closed control point curve is now closed all the way round.
    make_control_curve(..., closed=True) took the argument and ignored it,
    and built an open curve. It builds a periodic one, so the poles are a
    ring with no first or last and the curve runs through the seam as
    smoothly as it runs anywhere else, rather than meeting itself at a
    corner.