Internal · SpaceMusic Engineering · Research Spike

From Export to Object

A staged path from pressing “Export” to a hands-off sculpture pipeline — built almost entirely on hardware and services we already run.

The ritual we want to delete

Every physical object SpaceMusic has ever produced started life as a digital export and then passed through a human relay race: open the file in Rhino, clean it up, save it in a printable format, open a slicer, send it to a printer. The work is real, but it is the same work every time — and that is exactly what makes it worth automating.

The ambition our founder describes is simple to state and harder to build: SpaceMusic generates hundreds of variations unattended overnight, each one lands as a project in a web feed with a preview image, and someone logs in the next morning, scrolls the results, picks a handful of favourites, and sends them to be printed. From that moment on, the machine should carry the object the rest of the way to the print bed with as little human touch as possible.

The pleasant surprise from this research is that the geometry — the part that feels hard — is the easy part. Almost none of it needs an expensive Rhino Compute cluster or a subscription to ShapeDiver. We already write every export to cloud storage with a thumbnail; we already run a database, an admin interface, and a message relay that can host the feed; we already own the Rhino licences that do the one genuinely Rhino-only step. The task ahead is mostly wiring things we already have together, one stage at a time, so that nothing has to be built as a single big leap.

The manual pipeline today

To see what we are automating, follow one object through the pipeline as it runs now. A person does each of these by hand, for one file at a time:

  1. In SpaceMusic, press Export. The app writes a Rhino .3dm file plus a screenshot into the user library, which syncs to our cloud storage automatically.
  2. Open that .3dm in Rhino. If the export is a point cloud, run the ShrinkWrap command to turn the loose points into a solid, watertight mesh.
  3. Scale it to the real-world size it should print at, sanity-check that it is watertight, and perhaps add a small base so it stands up.
  4. Export the result as an STL file.
  5. Open Bambu Studio, slice the STL with the right printer and material profile, and send the job to a Bambu printer.

Every step is deterministic and repetitive. The same object type always needs the same treatment — a point cloud always gets shrink-wrapped, a mesh usually just gets checked and scaled. That regularity is the opening: a repetitive, rule-driven sequence is precisely the kind of work a pipeline can take over, provided we can reach each tool from a script instead of a mouse.

What we already own

Before proposing anything to build, it is worth being honest about how much of this already exists in our stack. Verified against the live server, the inventory is short and the gaps are small.

  1. rhino3dm — license-free geometry, already shipping. We already use the open rhino3dm library to read and write .3dm files. Reading meshes, point clouds and instanced blocks, flattening a block’s placement, and writing an STL are all things it (or a tiny helper) can do with no Rhino licence, on the Linux server we already run. VL.Rhino3dm · reads meshes / point clouds / blocks · STL is triangles + normals
  2. MinIO on Hetzner — every export is already here. The per-user library syncs two-way to a MinIO bucket (userlibs). Each export already arrives as a folder containing the .3dm and a _thumb.jpg preview. This is the feed’s source material, and it is already flowing. userlibs/{uuid}/Exports/{timestamp}_{slug}/…_Pointcloud.3dm
  3. Directus — a running portal backend with automation built in. The same Directus instance that powers our DNA platform gives us a database, an admin UI, roles, and a “Flows” engine that already fires outbound webhooks in production. A results feed is just a new collection on it. Directus 12 · Postgres · Flows · already fires webhooks today
  4. The SvelteKit API and Centrifugo relay — the glue is in place. Our API already owns all the storage plumbing and sits on the same internal network as MinIO. The Centrifugo relay can push a live “new export landed” signal to a browser feed. spacemusic-api · same docker network as MinIO · live push over the relay
  5. Rhino licences and spare PCs — the worker costs nothing new. We hold five Rhino licences for two people, and we have idle Windows PCs. A dedicated headless Rhino worker on one of them runs the one genuinely Rhino-only step with no per-hour cloud fees. 5 licences · an old Windows box · zero core-hour billing on a desktop OS
  6. Bambu printers and a scriptable slicer. Bambu Studio and OrcaSlicer both ship a command-line mode, so slicing an STL into a printable job can be automated rather than clicked. Bambu Studio / OrcaSlicer CLI · STL → printable 3mf

A brain in the cloud, hands in the studio

The whole design rests on one distinction. The parts that track what exists and what state it is in — the feed, the metadata, the status of every object — want to be central and always-on, so they belong on our cloud server. The parts that physically act — running Rhino, driving a printer — are tied to specific machines in the studio and cannot move to the cloud. So we split the system in two: a brain on Hetzner that holds the feed and the truth, and a set of hands on-premises that reach up to the brain, take work, and act on it.

Two rules keep the split clean. First, MinIO stays the single source of truth for files; Directus only holds a lightweight reference (an object key) plus metadata and status, and never owns the actual geometry. Second, the studio hands reach outward — the Rhino worker polls the cloud for jobs rather than accepting incoming connections — so there is no firewall hole to open and nothing exposed to the internet.

Figure 1 · System shape — cloud brain, on-prem hands Open full size · print A3 landscape ↗

Boss · web browser HETZNER EX44 · CLOUD MinIO userlibs bucket SpaceMusic API SvelteKit Results portal Directus + Flows Centrifugo live relay STUDIO LAN · ON-PREM SpaceMusic Host export + library sync Rhino worker PC Rhino.Compute + Grasshopper Bambu farm slice + print notify create feed pick favourites library sync poll jobs + presign outbound only slice + send (LAN)

Read the figure as a loop. Exports flow up from the studio into MinIO through the existing library sync. Inside the cloud, a new export is noticed, ingested, and turned into a feed item — a chain of three short internal hops. The boss reads that feed and marks favourites. The Rhino worker, back in the studio, reaches up and out to ask for work, does the geometry, and hands the finished object to the printers. Nothing in the studio ever has to be reachable from the internet, and Directus never has to hold a single heavy file.

How one export becomes an STL

The architecture only matters if a single object can travel it end to end without a person. Here is that journey in detail, which is also the definition of what we build in the middle stages.

An export lands in MinIO as a folder — the .3dm, a _thumb.jpg, and some Windows icon files. MinIO can fire a notification the moment an object is written, so we point one at the bucket and let it call a small route on our API. That route reads the object’s name — the type is written into the filename as a suffix such as _Pointcloud — applies a rule (“a point cloud needs shrink-wrapping”), and creates a Directus feed item that references the file and carries a status of new. The Rhino worker, polling that same Directus for anything unfinished, picks the item up, fetches the .3dm through a short-lived signed link, runs the Grasshopper recipe on its local Rhino engine, writes the resulting STL back to MinIO, and flips the item’s status to wrapped. Every later step — slice, send — hangs off the next status change in the same way.

Figure 2 · One export, end to end Open full size · print A3 landscape ↗

Export lands userlibs / … / _Pointcloud.3dm MinIO notify ObjectCreated filter .3dm API ingest parse key type → rule Portal item Directus status = new Worker · Compute shrinkwrap · scale check · base STL back to MinIO status → wrapped · later: slice · send poll write STL a folder fires several events — key on the .3dm only “scale to size” needs data the filename lacks — add export.json

Two rough edges are marked in the figure because they are the kind of thing that bites late if ignored. The first is mechanical: a single export is a folder, so MinIO fires several notifications per object; the ingest must act only on the .3dm and ignore the thumbnail and icon files, or it will create duplicate feed items. The second is more important and sits upstream of everything: the filename tells us the object’s type but not its intended size, so the “scale it to the correct size” step has no input to work from. The fix is cheap — have SpaceMusic drop a small export.json next to the geometry with the target dimensions and source scene — but it is the difference between “mostly automated” and “genuinely hands-off,” so it belongs on the list now, not later.

From humble beginnings to full automation

None of this needs to arrive at once, and it shouldn’t. The pipeline is a staircase: each step is independently useful, ships on infrastructure we already run, and leaves us better off even if we stop climbing there. The figure reads bottom to top, from today’s all-manual ritual to a fully tended system.

Figure 3 · The automation staircase Open full size · print A3 landscape ↗

full automation automation ↑ M4 Slice + send Bambu / Orca CLI + on-prem send agent M3 Portal + selection gallery · favourites · priority M2 Worker loop poll → wrap → STL → status M1 Ingest + feed MinIO webhook → Directus results M0 Geometry spine one .3dm → Compute + Grasshopper → STL NOW Manual ritual every step by hand, one at a time

The highlighted rung, M0, is the one to build first and the only one carrying real risk. It is a single scripted round-trip: take one real _Pointcloud.3dm, run our Grasshopper shrink-wrap recipe on a headless Rhino, and get a watertight STL out — no portal, no queue, no printer. It answers the one question this whole plan rests on that we cannot answer from a desk: do our Grasshopper scripts run cleanly on a headless Rhino engine? Everything above M0 is plumbing we already know how to build; M0 is the load-bearing proof.

From there the climb is gentle. M1 wires the MinIO notification to a Directus feed so exports appear on their own. M2 gives the Rhino worker its polling loop, at which point the geometry half is hands-off. M3 adds the boss-facing gallery and the favourite-and-prioritise gesture. M4 automates slicing and adds a small on-premises agent that sends jobs to the Bambu printers over the studio network. Each rung is a place we can pause and still have gained something concrete.

Two honest caveats belong here. First, “500 sculptures overnight” is a claim about the digital pipeline, not the printers — we can generate, wrap, slice, and queue five hundred objects by morning, but the printers themselves meter out over days, because a print takes hours and no one auto-clears the bed. The overnight promise is “five hundred sliced and ready to choose from,” which is both true and enough. Second, because we only ever run the Rhino recipe on the handful of objects someone actually selects, full Rhino plus Grasshopper is the pragmatic default; the license-free open-source path for shrink-wrapping (and dropping ShapeDiver entirely) is a later optimisation to reach for if volume ever justifies it, not a blocker on any of this.

Why this is worth doing now

“The geometry was never the hard part. The hard part was that a human had to be in the room.”

What this buys us is not a moonshot; it is the steady removal of a person from a loop that never needed them. Today someone opens a file, runs a command, saves it, and clicks send — the same five motions, over and over. The staircase replaces those motions one rung at a time, and it does so on the server, the storage, the database, the relay, the licences, and the spare PCs we already pay for. The only genuinely new spend is a bit of wiring and, eventually, a small on-prem agent by the printers.

It also changes our footing. Every step we build ourselves is a step we are not renting from ShapeDiver, and it is built on parts we understand and control. We are not betting the pipeline on a single vendor or a single big release; we are climbing a staircase where every rung stands on its own. The endgame is quietly ambitious and entirely reachable from here: the founder opens a feed of last night’s five hundred, taps five favourites, and the only human left in the loop is the one lifting a finished sculpture off the print bed.

Glossary

Terms and proper nouns used in this document, in plain language.

.3dm
Rhino’s native 3D file format. Every SpaceMusic export is one of these.
rhino3dm
A free, open library that reads and writes .3dm files without needing a Rhino licence. We already ship it.
Rhino.Compute
Rhino running headless behind a web API, so scripts can drive it. On a normal desktop Windows machine with a licence we own, it runs with no per-hour fees.
Grasshopper
Rhino’s visual scripting tool. Our reusable geometry recipes (shrink-wrap, scale, add a base) are Grasshopper scripts.
ShrinkWrap
A Rhino command that turns a loose point cloud into a solid, watertight mesh. The one step in our pipeline that genuinely needs Rhino.
point cloud
A shape stored as a scatter of individual points rather than a solid surface — needs shrink-wrapping before it can be printed.
STL
The plain triangle-mesh format 3D printers and slicers expect.
MinIO
The S3-compatible object storage we run on our server. Where every export already lives.
userlibs
The MinIO bucket holding each user’s personal library, including their exports.
Directus
The headless database-plus-admin-UI we already run for the DNA platform; here it hosts the results feed.
Flows
Directus’s built-in automation engine — it can fire a webhook when an item is created or its status changes.
Centrifugo
Our message relay, used to push live updates (like “new export landed”) to a browser.
SvelteKit
The web framework our API and web tools are built in.
bucket notification
A message MinIO can send the instant a file is written, so we can react without polling.
presigned URL
A short-lived signed link that lets the worker fetch or upload one file without holding storage passwords.
Bambu Studio / OrcaSlicer
Slicing apps for Bambu printers; both have a command-line mode we can automate.
ShapeDiver
A paid cloud service that runs Grasshopper on demand. We use it today but want to depend on it less.
EX44
Our dedicated Hetzner server — the “cloud” box in this document.

Settled

Architecture, verified

Brain on Hetzner, hands on-prem, MinIO as the single source of truth, Directus as the feed. All confirmed against the live server — the pieces already exist and connect.

Next step

M0 — the geometry spine

One real _Pointcloud.3dm → headless Rhino.Compute → Grasshopper recipe → watertight STL. Proves our scripts run headless, which is the only real unknown.

Later

Print side + de-risking

CLI slicing plus an on-prem Bambu send-agent; optionally, open-source shrink-wrapping to drop the Rhino dependency once volume justifies it.

From Export to Object · designed for A3 landscape print ← back to document

Figure 1 · System shape — cloud brain, on-prem hands

Boss · web browser HETZNER EX44 · CLOUD MinIO userlibs bucket SpaceMusic API SvelteKit Results portal Directus + Flows Centrifugo live relay STUDIO LAN · ON-PREM SpaceMusic Host export + library sync Rhino worker PC Rhino.Compute + Grasshopper Bambu farm slice + print notify create feed pick favourites library sync poll jobs + presign outbound only slice + send (LAN)
From Export to Object · designed for A3 landscape print ← back to document

Figure 2 · One export, end to end

Export lands userlibs / … / _Pointcloud.3dm MinIO notify ObjectCreated filter .3dm API ingest parse key type → rule Portal item Directus status = new Worker · Compute shrinkwrap · scale check · base STL back to MinIO status → wrapped · later: slice · send poll write STL a folder fires several events — key on the .3dm only “scale to size” needs data the filename lacks — add export.json
From Export to Object · designed for A3 landscape print ← back to document

Figure 3 · The automation staircase

full automation automation ↑ M4 Slice + send Bambu / Orca CLI + on-prem send agent M3 Portal + selection gallery · favourites · priority M2 Worker loop poll → wrap → STL → status M1 Ingest + feed MinIO webhook → Directus results M0 Geometry spine one .3dm → Compute + Grasshopper → STL NOW Manual ritual every step by hand, one at a time