Export a Rhino 8 scene to a Holophonix package (Overview CSV + venue.glb + one .glb per speaker model), from a Grasshopper GhPython3 component.
Project language is English. All code, comments, docs, and commit messages are in English.
- Rhino >= 8.3 (embedded CPython 3 on the Grasshopper side +
Rhino.FileIO.FileGltfAPI used for programmatic .glb export). - A Rhino scene with:
- Speakers — Block Instances on sub-layers of
SPEAKERS::*(one sub-layer per model:SPEAKERS::MDC5,SPEAKERS::HOPS8, ...). The layer color is used as the speaker color in the CSV. - Venue — any geometry on the
VENUElayer (orVENUE::*). Exported as-is to .glb.
- Speakers — Block Instances on sub-layers of
A sample scene is bundled at starting_scene.3dm — open it in Rhino 8 to try the export without having to build a layer hierarchy from scratch.
Quick path — open holophonix_export.ghx in Grasshopper. The definition already embeds the GhPython3 component, its script, all the inputs, the Button for run, and the output Panels.
Manual path (if you prefer to rebuild the component from scratch):
-
Open Grasshopper inside Rhino 8.
-
Drop a GhPython3 component (Script → Python 3).
-
Paste the contents of
holophonix_export.pyinto the component editor. -
Add the inputs (right-click on the component,
+):Name Type Details folderstr Panel with the output folder path (created if missing) runbool Button — writes CSV + GLB package layer_rootstr Panel, defaults to SPEAKERSauto_orientbool Toggle, defaults to Falseexport_venuebool Toggle, defaults to True— writesvenue.glbwhen enabledexport_speakersbool Toggle, defaults to True— writes one<MODEL>.glbper modelsyncbool Button — pushes speakers to Holophonix via OSC osc_hoststr Panel, defaults to 127.0.0.1osc_portint Panel, defaults to 4003 -
Add the outputs
lines,count,log.
The
.ghxis the versioned Grasshopper definition (XML, diff-friendly). The binary.ghvariant is intentionally kept out of the repo.
- Wire a Panel to
linesto preview the CSV rows. - Check
count(number of detected speakers) andlog(per-model breakdown + written paths). - Click
run→ files produced infolder:holophonix_overview.csvvenue.glb— venue geometry<MODEL>.glbfor eachSPEAKERS::<MODEL>sub-layer (e.g.MDC5.glb,HOPS8.glb,G18-SUB.glb, ...) — one asset per model, containing only the block definition with its insertion point as pivot. Not the instances placed in the scene.
- In Holophonix: import the CSV through the Overview window, load
venue.glbas the stage decor, and each<MODEL>.glbas an asset instantiated at the positions of the matching speakers in the CSV.
Once the speakers exist in Holophonix (from the CSV import above), you can push live updates without going through a file round-trip:
- Set
osc_hostandosc_portto match your Holophonix instance (defaults127.0.0.1:4003). - Click
sync→ the component sends one UDP/OSC message per parameter per speaker:name,color(RGBA),azim/elev/dist,view3D/pan,view3D/tilt,view3D/autoOrientation. - The speakers keep their index from the CSV import, so positions update in place.
The OSC encoder is a ~30-line helper in holophonix_export.py (no external dependency). It speaks OSC 1.0 over UDP — address + typetag + 4-byte-aligned args.
OSC cannot create new speakers — only update existing ones. If you add/remove speakers in Rhino, re-export the CSV and re-import it in Holophonix so slot indices stay in sync.
OSC Address;Name;Color;X;Y;Z;Azim;Elev;Dist;Auto Orientation;Pan;Tilt;Lock
/speaker/1;MDC5_01;0.4588235294117647,0.20392156862745098,0.20392156862745098,1;1.200;-2.500;1.800;-64.358;31.128;3.431;true;0;0;false
...
- OSC: global index (
/speaker/1,/speaker/2, ...) in sort order. - Name:
<model>_NNzero-padded per group, or<model>_<objname>_NNwhen the Rhino block instance has a Name attribute set (e.g.G18-SUB_CENTER_01). - Color:
R,G,B,Aas 0–1 floats. - X/Y/Z: meters — automatic conversion from the Rhino document unit.
- Pan / Tilt: derived from each speaker block's orientation in the scene. Forward axis convention: local +Y inside the block definition (see
FORWARD_LOCALin the script). Pan = azimuth of the forward vector in the horizontal plane, Tilt = elevation above horizontal, both in degrees. - Numeric fields written at full precision (
repr()-style), kept consistent with whatsyncpushes over OSC so the two paths produce identical positions in Holophonix.
Locked CSV mapping: (X_h, Y_h, Z_h) = (Y_r, X_r, Z_r) — swap X and Y, Z unchanged. Validated on the reference scene. To change the convention, edit to_holophonix() in holophonix_export.py.
The GLBs are exported in raw Rhino coordinates (no pre-export transform). Verify on the Holophonix side; if the geometry appears mirrored relative to the CSV positions, a transformation step should be added in export_glb / export_block_def_as_glb.
- Filtering by block definition name is not native in Grasshopper (RH-78812). We filter by layer, which is enough here.
- No origin-offset handling (feature deliberately abandoned — reposition the Rhino scene origin instead if needed).
- GLB export relies on
Rhino.FileIO.FileGltf(available since Rhino 8.3). On older versions the script fails with anAttributeError. - Nested
InstanceReferences inside a speaker block definition are not resolved (their definition is not copied into the temporary document used for export). Usually a non-issue for "flat" speaker blocks.
Official Holophonix Ruby SketchUp plugin HOLOPHONIX_speaker_export (Holophonix S.A.S., 2024) — same AED logic and same serialization conventions (string booleans, separators, no trailing newline), adapted to Rhino/Grasshopper.