Text-to-scene rules (Rider importer)
Fixture type resolution preflight
Create from text analyzes unique normalized fixture aliases with the same rider parser used by the final import. The analysis is non-destructive and aggregates quantity and Positions before any scene objects are created. The active fixture dictionary is always checked first; when all referenced profiles exist, scene creation proceeds without an additional prompt or catalog work.
Unknown aliases may be compared with the cached GDTF Share catalog, so catalog suggestions can be inspected offline. Exact normalized model matches are safe suggestions, plausible weaker matches require review, numeric model contradictions remain unresolved, and no selection is downloaded merely by opening preflight. A confirmed real selection is remembered in the fixture dictionary, while Generic means the established fallback for that import and does not create a permanent dummy mapping. Profiles with one valid DMX mode may select it automatically; profiles with multiple modes require an explicit mode choice before their mapping can be committed.
The runtime call chain is RiderTextDialog → AnalyzeFixtureTypes → cached
catalog parsing and dictionary-first resolution → RiderFixtureResolutionDialog
when intervention is required → confirmed downloads and dictionary persistence
→ RiderImporter::ImportText. Cancellation and failed resolution return before
the importer pushes its undo state or accesses mutable scene content. When no
cached catalog exists, the open resolver makes one acquisition attempt through
DetermineCatalogAccessAction, GdtfCatalogService, and the established GDTF
Share credential flow. A successful validated refresh updates the normal shared
cache and starts matching in the current resolver; cancellation or failure
leaves Generic and Cancel available offline.
The dialog owns the cache and online-acquisition workers. Network login,
catalog retrieval, parsing, and matching run outside the UI thread and report
stages to the dialog's existing label and gauge. Only credential entry crosses
back to a modal UI prompt; automatic acquisition never opens a nested progress
dialog. Confirming or cancelling stops acceptance of late results.
Catalog matching targets only enabled, unresolved, untouched rows. Dictionary,
DictionaryModified, UserSelection, AutomaticMatch, Generic, and Skipped rows
are excluded, and determinate progress totals count only the remaining targets.
AnalyzeText() owns the single raw rider parse used by preflight and returns
both filtered import text and fixture requests. Final creation imports that
prepared text in already-filtered mode. Equipment classification is shared by
analysis and creation: screen descriptions on the SCREEN Position become scene
objects, and semantic operator/content-control requests are non-fixture
equipment rather than dummy fixtures.
The dialog initially receives dictionary-only state and starts validated cached catalog loading after it becomes visible. Review and Unresolved rows visibly default to Generic; incomplete or mode-ambiguous real selections normalize to Generic on confirmation. Catalog payloads are usable only when their schema is recognized and at least one downloadable named entry is parsed. Invalid online refreshes are rejected before cache publication, preserving the last known good snapshot.
Both Create from text and file-based Import Rider use this prepared analysis and the same fixture-resolution preflight before scene mutation.
Each resolution row preserves its immutable parsed fixture identity separately
from its editable effective fixture type. A prepared import plan applies the
effective type or skips the original normalized request during fixture creation;
non-fixture scene elements are unaffected. Automatic resolution delegates to
SelectBestDownloadMatch() from the shared MVR matcher.
Downloaded catalog profiles are registered as validated external library
sources through CreateOrUpdateExternalLibraryMapping; this path deliberately
does not invoke the @Perastage derivative publication contract. Recoverable
authentication, download, validation, mode, and persistence failures update
only the affected row to Generic fallback. User cancellation remains the only
normal preflight outcome that cancels the complete import.
External downloads use the shared portable catalog-identity filename policy;
the GDTF Share RID remains revision identity and is used as a suffix only when
the readable local filename collides.
The GUI-independent resolver reports LoadingCatalog, ParsingCatalog,
MatchingFixtures, Complete, and Unavailable progress. Matching progress
uses actual processed unique fixture rows, and the dialog ignores automatic
results after its import plan has been committed. Resolution provenance maps to
a shared semantic status palette also used by the MVR GDTF download queue.
Resolver rows have immutable non-zero model keys for the dialog lifetime.
Matching updates cells and Status attributes in place rather than deleting and
rebuilding the active data view. The dialog owns a cancellable managed worker;
confirming or cancelling requests stop, ignores queued results, and preserves
the finalized import plan. Current toolchains use std::jthread, while the
dedicated macOS 15 compatibility build uses an owned, explicitly joined
std::thread with equivalent cooperative cancellation.
This document explains the rules currently applied by Perastage when creating a scene from rider text loaded from .txt or extracted from .pdf.
It is intended to be the single source of truth for:
- User-facing documentation (Help/manual).
- Internal maintenance when parser rules change.
- QA validation of expected text-to-scene behavior.
Scope and entry points
The feature is exposed in:
- Tools → Create from text (dialog import from pasted text or rider file).
- Rider file import (
.txtand.pdf).
Implementation entry points:
RiderImporter::Import(path)RiderImporter::ImportText(text)RiderImporter::LoadText(path)RiderImporter::BuildFixtureFilterPreview(text)(UI filter pass before create)
Text filter step in "Create from text"
The dialog includes an Apply filter button that runs the same line selection rules used by fixture parsing, and replaces the editor content with the filtered result before scene creation.
Create also runs that same filter pass internally. This keeps scene creation
deterministic whether users click Create directly after loading text or
click Apply filter first and then Create.
Filter behavior:
- Keeps only lines interpreted as fixture entries in fixture sections.
- Keeps truss/rigging lines and emits a normalized
RIGGINGblock. - Keeps motor/hoist lines found in rigging and normalizes them as
N MOTOR <capacity>Kg FOR <hang>. - Groups fixture output by detected hang (
LX1,LX2,FLOOR, ...). - Normalizes physical floor aliases such as
suelo,piso,floor,deck,calle(s) a suelo, andground lane(s)toFLOOR. - Truss lines using
PUENTES LXare expanded asLX1,LX2,LX3, ... - Hoist lines using
PUENTES LXare expanded and distributed over detectedLX*targets in the same filtered rigging block. - Truss hang alias
PANTALLAis normalized toSCREEN. - Truss hang aliases
BACKDROP,BACKDROPS,TELON,TELONESare normalized toBACKDROP. - Truss lines normalized to
FLOORare skipped in filtered output/import. - Expands
+compound lines into individual fixture lines. - Supports quantity-only lines (
Nfollowed by description on next line). - Removes parenthesized notes from fixture tokens to reduce rider noise.
- The complete filter pass is idempotent: reapplying Apply filter to any canonical result produces byte-identical text.
- Truss targets with accessory suffixes are normalized to the effective hang
target (for example,
... + HUESITOS PARA PUENTES LXis interpreted asPUENTES LX). - Hoist/motor target keywords in the filtered preview are normalized to
English
FOR, whilePARAremains accepted in input text and existing normalized text.
After applying the filter, users can manually adjust the filtered text and then press Create; the same normalization rules are still applied at creation time.
Preview serialization
Filtered previews use UTF-8 text with LF line endings and no carriage returns,
trailing spaces, or leading/trailing blank lines. Exactly one empty line
separates each non-empty fixture or RIGGING block, and empty blocks are
omitted. Reapplying the filter to its preview produces byte-identical output.
Autocomplete keywords and ranking in "Create from text"
The multiline editor in Tools → Create from text exposes autocomplete to speed up rider authoring/editing before import.
Supported built-in keywords currently include:
- Position/section terms:
RIGGING,LX1,LX2,LX3,LX4,SIDES,FLOOR,SCREEN,BACKDROP. - Rigging/object terms:
TRUSS,PIPE,MOTOR,FOR,KG,M,LED SCREEN,PRIMITIVE:CUBE,PRIMITIVE:CYLINDER. - Utility terms used by the dialog flow:
APPLY FILTER,HAZER,FAN.
Autocomplete also includes dynamic dictionary terms loaded from:
- Fixture dictionary type names (GDTF dictionary entries).
- Truss dictionary model names.
Ranking order is relevance-based (not alphabetical-only):
- Exact token match.
- Prefix match.
- Substring/fuzzy match.
- Recent accepted suggestions get a recency boost.
- Context boost:
- after
FOR, position labels (LX*,SIDES,FLOOR,SCREEN) are prioritized. - after a numeric token (for example
8), type/model terms are prioritized.
- after
Ranking is configurable through autocomplete ranking weights in code
(RiderTextAutocompleteProvider::RankingWeights), allowing score tuning
without changing matching logic.
Input normalization
- File type:
.txtis read as plain text..pdfis converted to text before parsing.
- Parsing is line-based.
- Windows carriage returns (
\r) are removed from each line. - Inline annotations wrapped as
*(...)*are ignored during parsing. - Most keyword matches are case-insensitive.
Section detection rules
The importer classifies complete normalized heading lines rather than searching
for section words inside arbitrary descriptions. Quantity-prefixed equipment
therefore remains equipment even when its model or purpose contains words such
as iluminar, control, video, or audio.
ILUMINACION/LIGHTINGandAPARATOS/FIXTURESenter fixture mode.RIGGINGandRIGGING Y ESTRUCTURASenter rigging mode.EFECTOS/EFFECTSenters fixture mode with the canonical physical positionFLOOR. Equipment in this section follows the normal dictionary, GDTF, and category resolution pipeline; the section does not assign theSmokecategory.CONTROL DE ILUMINACION/LIGHTING CONTROLstops fixture collection.VIDEOestablishes video context without itself selecting a physical hang; a following screen heading selectsSCREENand enables screen collection.- Audio and other recognized unrelated headings stop active collection.
- If a hang-position line appears before explicit sections, the importer assumes fixtures mode.
- If fixture list lines appear before any recognized section header and no
hang header has been defined yet, the importer assumes fixtures mode and
defaults active hang to
FLOOR.
Hang/position detection
Recognized hang labels:
LX<number>(for exampleLX1,LX2, ...)- Front/LX1:
frontal,frente,puente frontal,front,front light,front truss,downstage - Middle/LX2:
cenital,central,medio,puente central,mid,middle,center,centre,midstage - Rear/LX3:
contra,contraluz,puente trasero,trasero,back,rear,backlight,upstage - Sides:
calle(s),lateral(es),side(s),side light(s), and descriptiveCALLES DIRECTAS ...headings - Floor:
suelo,piso,calle(s) a suelo,floor,ground,deck,ground lane(s) - Screen:
screen,pantalla,pantalla led,proyeccion,proyección,led screen,led wall,projection backdrop/backdrops/telon/telones/puente de telon(es)
Behavior:
- Hang labels are normalized to uppercase (
LX1,FLOOR, ...). - Fixture hang headers can include extra descriptive suffix text before
:(for exampleCALLES EN LAYHER:), while still keeping the detected hang alias (CALLES->LX SIDES). - Front, middle, and rear semantic headings normalize to
LX1,LX2, andLX3; an explicitLX<number>always takes precedence. - Physical floor aliases are normalized to
FLOOR;EFFECTSselects that placement context without becoming a position name itself. - Side-position aliases are normalized to the canonical value
LX SIDES. - Explicit normalized headers such as
LX SIDESare accepted as hang labels in both filtered text and direct import input. - Screen aliases are normalized to
SCREEN. This allows the commonVIDEO→PANTALLA LED/LED SCREEN/LED WALLstructure to reuse the existing screen-object importer. - Backdrop aliases (
backdrop(s),telon(es),puente de telon(es)) are normalized toBACKDROP. - The active hang affects fixture/truss layer naming and default placement (
Y/Z).
Fixture parsing rules
Accepted fixture patterns:
N description- Optional list bullets (
-or*) before quantity are accepted. - Quantity-only lines (
N) are accepted and applied to the next non-empty line. +splits compound lines into independent fixture groups.- A plus segment without its own quantity inherits the first segment's
quantity (
1 FOLLOWSPOT + OPERATORbecomes two quantity-one entries). - Ordinary parenthesized fixture annotations are removed from the canonical token; no richer hidden fixture description is retained.
Examples:
12 Spot8 Wash + 4 Beam6(next line:PAR) ⇒6 PAR
For each fixture created:
instanceNameis initialized with incremental numbering per parsed token (Type 1,Type 2, ...).typeNameis set from parsed text and may be refined through the GDTF dictionary.- If dictionary entry exists:
- Fixture dictionary lookup is case-insensitive and ignores whitespace in
type names (for example
MACAURA,Mac Aura, andmac auraresolve to the same entry). gdtfSpecandgdtfModeare assigned.- If dictionary color is defined, it is copied into fixture
color. - If dictionary category is defined, it is copied into fixture
categorywith sourceManual. - Fixture display/type name can be replaced by the parsed GDTF fixture name when available.
- Physical properties (weight/power) are filled from GDTF when missing.
- Fixture dictionary lookup is case-insensitive and ignores whitespace in
type names (for example
positionNameis set from current hang.- If no hang header was parsed and parsing started from a headerless
fixture list (before any recognized section header),
positionNamedefaults toFLOOR. - In layer-by-position mode, side fixtures use
pos LX SIDESwhen side trusses exist and fallback topos SIDESwhen no side truss is present. - Side fixtures are ordered as a linear sequence and then mirrored by side: first half rises on the left side, second half rises on the right side in reverse order.
- If no hang header was parsed and parsing started from a headerless
fixture list (before any recognized section header),
- Initial hang coordinates are injected (
Y,Z) and later refined by distribution logic. - Importer falls back to category
Unknownonly when category is still empty after the above steps (for example, dummy fixtures without usable GDTF data).
Special screen-object handling:
- When current hang is
SCREENand a fixture line containsscreenorpantalla, the importer creates scene objects instead of fixtures. - Size parsing looks for
<width>x<height>values in meters (accepted forms include8x5,8x5m,8m x 5m). - Dimension parsing uses the physical meter pair and does not replace it with
a later pixel-resolution pair such as
1664x832 PIXELS. - Parsed screen dimensions are applied as:
- X (width) = parsed width
- Z (height) = parsed height
- Y (thickness) = fixed
0.1 m
- Screen geometry is emitted as a
primitive:cubeentry inSceneObject.geometriesusing local geometry scale (same primitive pipeline used by regular cube scene objects), while object transform keeps position. The canonical cube mesh measures1.0 mon each axis, so the local X/Y/Z scale values are the final width/thickness/height in meters. Rider screen dimensions are therefore stored only on the geometry transform; the scene object basis remains unscaled. - If no valid size is found, importer falls back to
8.0 x 5.0 m. - Screen objects are centered on the associated screen truss span and placed so
their top edge sits
0.2 mbelow the truss.
Fixture color precedence
Fixture color resolution follows this order:
- Explicit fixture color (already stored on the fixture row/object).
- Dictionary color for the fixture
typeName. - Deterministic fallback color (GDTF/model-derived fallback used by the current creation flow).
When changing a fixture type color from Summary, Perastage updates matching fixtures in the scene and also persists that color in dictionary entries for the edited type (and entries sharing the same GDTF file identity).
Truss parsing rules
Supported truss syntax includes:
N truss MODEL LENGTH m [para HANG]N truss MODEL LENGTH m [HANG](hang can appear with or withoutpara)N truss MODEL LENGTH m [for HANG]N truss MODEL [para HANG](lengthless form, used for backdrop lines)N truss MODEL [for HANG](lengthless form, used for backdrop lines)- More generic fallback lines containing
trussand a measurable length. - Pipe aliases are also accepted:
pipe,pipes,vara,varas.N pipe MODEL LENGTH m [para/for HANG]N pipe MODEL [para/for HANG](defaults to14 m).- Pipe lines use the same parenthesis coordinate overrides and bracket margin overrides as truss lines.
- Optional coordinate override appended to hang in parentheses, e.g.
LX1 (0, -1, 9)=>x, y, zLX1 ( -1, 9 )=>y, z(keeps defaultx)LX1 (7)=>yonly (keeps defaultx, z)- Non-numeric text inside parentheses is ignored while extracting numbers.
- This can be written both in truss targets (
... PARA LX1 (7)) and in hang headers (LX1 (7)/LX1 (7):), where truss placement inherits the hang override. - If both are present, truss-line overrides take precedence over hang-header overrides for any axis values provided in the truss line.
- The Apply filter pass preserves these coordinate tokens so pressing Create after filtering keeps the same truss placement overrides.
- Optional margin override appended to hang in square brackets, e.g.
LX1 [0.8]=> per-rider margin override forLX1.- Can be written in truss targets (
... PARA LX1 [0.8]) and hang headers (LX1 [0.8]/LX1 [0.8]:). - Values use the active UI distance unit system (
metricorimperial). - If both are present, truss-line bracket overrides take precedence over hang-header bracket overrides for the same hang.
- The Apply filter pass preserves bracket tokens so pressing Create after filtering keeps the same margin overrides.
Key truss behaviors:
- Length is parsed in meters and converted to millimeters.
- For
BACKDROPlines without explicit length, importer reuses the latest parsedLX*orSCREENtruss span. - If no prior
LX*/SCREENspan exists,BACKDROPuses12 mby default.
- For
- If model token contains dimensions like
40x40, width/height defaults are inferred from it. para <hang>/for <hang>overrides current hang.- Prefix cleanup supports
PUENTE/PUENTESin hang names. - Long trusses are split into symmetric pieces using preferred segments (
3000, 2000, 1000, 500 mm) plus a center piece when beneficial. - Each piece becomes a truss object with computed
xplacement and hang-basedy/z, unless a coordinate override is provided in the hang token. Coordinate values use the active UI distance unit system (metricorimperial) at import time. - Dictionary/model resolution is attempted using normalized lookup keys.
- Lookup also tries model-token variants that remove common finish/color
adjectives (e.g.
NEGRO,BLACK) so names like40X40 PRO NEGROcan resolve against canonical dictionary entries such asTRUSS 40X40 PRO 3M.
- Lookup also tries model-token variants that remove common finish/color
adjectives (e.g.
- If model/symbol cannot be fully resolved to renderable geometry (
.3ds/.glbavailable), importer keeps dummy-box truss data and logs a warning.
Pipe-specific behavior:
- Pipe lines create
SceneObjectentries (object table), not truss entries. - Imported pipes are created as simple cylindrical placeholders:
- length = parsed length (or
14 mfor lengthless pipe lines), - radius =
2.5 cm(50 mmdiameter). - Import stores a cylinder primitive geometry reference on the object so the 3D view renders the pipe directly as a cylinder (not only as a generic meshless fallback box).
- Transform path matches regular primitive cylinders for geometry sizing:
pipe proportions are stored in
SceneObject.geometries[].localTransform. The default pipe orientation (+90°onY) is stored inSceneObject.transformtogether with world position. - Pipe object names use hang naming, not length naming:
PIPE <hang>(for examplePIPE LX1).
- length = parsed length (or
- Pipe lines still create/target the same normalized hang names (
LX1,LX2, ...), includingPUENTES LXexpansion toLX1..LXN. - Pipes do not carry truss hang-weight fields; fixtures still keep their hang position assignments exactly as usual.
Special case:
- If hang is exactly
LX, quantityNexpands toLX1..LXN. - If hang resolves to
LX SIDES, importer creates two mirrored side trusses (left/right) per split piece:Xanchor:0.5 moutside the widest detectedLX*truss span.Ydirection: truss axis is aligned alongY(notX).Z(height): fixed at5.0 m.- If no
LX*truss exists, importer uses a fallback reference span of[-3, +3] m.
- If no side truss exists but
LX*trusses are available, side fixtures still use mirrored fallback anchors at0.5 moutside the widest detectedLX*truss span. - If hang is
SCREENand there is no dedicated screen config key, trusses are placed from the last created LX truss reference, withY = last_lx_y + 1mandZ = last_lx_z - 0.5m. If no LX truss has been created yet, importer falls back to the highest configured LX (height > 0) using the same offsets. - If hang is
BACKDROP, trusses are placed1 mbehind the latest parsedLX*orSCREENtruss (+Y) and keep the same height (Z).
Hoist (motor) parsing and placement rules
Supported hoist syntax includes:
N MOTOR <capacity><unit> [ ... ] FOR <hang>N HOIST <capacity><unit> [ ... ] FOR <hang>- Optional list bullets (
-or*) before quantity are accepted.
Capacity parsing:
- Recognizes case-insensitive mass units and compact forms (
500Kg,2TO,1 t). - Accepted kilogram aliases:
kg,kgs,kilo,kilogramos. - Accepted ton aliases:
t,to,tn,ton,tons,toneladas. - Ton values are converted to kilograms (
2TO=>2000 kg).
Hang normalization for hoists:
PA,P.A,P.A.=>PAgroup behavior (position name is stored asP.A.).SIDE FILLandSIDEFILL(case-insensitive, with normalized whitespace) =>SIDEFILL.SIDE/SIDES,CALLE/CALLES, andLX SIDE/LX SIDES=>LX SIDES; floor phrases such asCALLES A SUELOremainFLOOR.PANTALLA=>SCREEN.PUENTE(S) LX=>LXdistribution mode.
Placement defaults:
- PA/P.A. hoists
- Split into left/right groups around
LX1. - Anchored 1 m outside
LX1truss span (X), sameY/ZasLX1. - Group internals use 1 m spacing; if needed, additional items form a grid.
- Split into left/right groups around
- SIDEFILL hoists
- Split left/right around
LX1truss span with 1 m outside offset inX. - Placed 2 m behind
LX1onY.
- Split left/right around
- LX hoists (
PUENTES LX)- Quantity is distributed across detected
LX*trusses (LX1,LX2, ...). - Each per-LX set is placed on that truss at the same
Y/Z, using a 1 m margin from truss ends inX. - Direct
LX<number>hoist targets also use a 1 m margin from truss ends.
- Quantity is distributed across detected
LX SIDEShoists (SIDES,CALLES)- Quantity is split between left/right side trusses (
left = ceil(N/2),right = floor(N/2)). - Each side set is distributed along the side truss length on
Y, preserving the truss direction (startY -> endY) and using a 1 m margin from both ends. - Hoists use each side truss own
Xanchor andZheight. - If side trusses are missing, importer falls back to side fixture anchors.
- Quantity is split between left/right side trusses (
- SCREEN hoists
- Distributed using internal truss divisions (for
Nhoists, truss span is split intoN+1equal parts and hoists are placed on internal cuts).
- Distributed using internal truss divisions (for
Created hoists:
- Are stored as
Supportobjects. - Use capacity in kilograms.
- Are assigned one of the default dummy hoist profile ids by capacity range:
dummy_standard_500kg,dummy_standard_1000kg, ordummy_standard_2000kg. - Hoist function defaults by target:
PA,P.A.,SIDEFILL,OUTFILL=>AudioSCREEN,LEDSCREEN,VIDEO=>Video- Any other target =>
Lighting
- Hoist naming defaults (ordered left-to-right on
X):LX*targets =><position> <index>(LX1 1,LX1 2, ...).SCREEN/video targets =>SCR <index>.BACKDROPtargets =>BACKDROP <index>.LX SIDEStargets =>SIDE L <index>,SIDE R <index>(ordered byYinside each side).SIDEFILL=>SF L,SF R(adds index when side has more than one hoist).PA/P.A.=>PA L <index>,PA R <index>.- Fallback targets =>
RP <index>(Rigging Point).
- Hoists are assigned to rig layers by function:
rig Audio,rig Video,rig Lighting, etc.- Missing rig layers are created automatically.
- Newly created rig layers use these default colors:
Audio=#FF0000,Video=#00FF00,Scenic=#0000FF,Extra=#8F00FF,Other=#C7A3C7,Lighting=#FF00FF. - Hoist symbols are drawn with white fill and use their current layer color for the colored regions/outline, so changing a hoist to another layer updates the symbol color automatically.
- Hoist loads are auto-distributed after import for each hang position that
received newly created hoists:
- Position total uses the same rigging table rule: sum of fixture+truss+hoist
weights in the hang, then
+5%, then rounded up to the next 5 kg. - If hoists in that hang are not collinear, total is split equally by hoist count and each share is rounded up to the next 5 kg.
- If hoists are collinear and count is
2..8, the importer applies standard percentage factors (2: 50/50,3: 19/62/19, ... ,8: 6/16/14/14/14/14/16/6), then rounds each hoist load up to the next 5 kg.
- Position total uses the same rigging table rule: sum of fixture+truss+hoist
weights in the hang, then
Layer assignment rules
Driven by config key rider_layer_mode:
positionmode:- Fixtures:
pos <hang> - Trusses:
pos <hang> - Pipe objects:
pos <hang>
- Fixtures:
typemode:- Fixtures:
fix <fixture type> - Trusses:
truss <hang> - Pipe objects:
obj <hang>
- Fixtures:
- Hoists (both modes):
rig <hoist function>
Missing/empty names fall back to the default scene layer.
Fixture distribution over trusses/pipes
After parsing, imported fixtures are distributed per hang:
- Truss span for each hang is inferred from imported truss pieces.
- Margin comes from
rider_lx*_margin(for LX hangs), unless a square-bracket override ([value]) was provided for that hang in the imported rider. - If no truss is found for a hang, fallback spacing is centered around origin with 500 mm steps.
- Exception for
LX SIDES: fixtures are still split into left/right groups as if side trusses existed, with fallback anchors atY = 1.0 mand500 mmspacing moving upstage (positive Y).
- Exception for
- Ordering mode per hang:
- Default mode is symmetric by type:
- odd counts place a center fixture,
- remaining fixtures are paired left/right.
- If a fixture type is listed more than once for the same hang in the rider
fixture list (for example
MegaPointe ... Spiider ... MegaPointe), that hang switches to linear left-to-right mode and keeps rider list order.
- Default mode is symmetric by type:
- Fixtures are split into placement groups:
- Bottom group: all categories except
Wash,Blinder,Strobe. - Bottom-back override:
Washis placed on the back side. - Top-front group:
BlinderandStrobeare placed on top-front. - Smoke side-floor group:
Smokefixtures are placed using side-style mirroring (left/right split alongY) at floor level.
- Bottom group: all categories except
xspacing is computed independently per group and per ordering mode:- Bottom fixtures share one spacing/order pass.
- Top-front fixtures use a separate spacing/order pass and do not affect bottom spacing.
- This means bottom and top-front groups can each resolve symmetric vs linear behavior independently while keeping their own list order.
- If a group has exactly one fixture, it is placed at the horizontal center of the usable hang span (instead of at a lateral edge).
- Final fixture transform by group:
- Bottom-front:
x: evenly spaced from start to end (bottom group),y:baseY - trussWidth/2,z:baseZ.
- Bottom-back (
Wash):x: from bottom group spacing,y:baseY + trussWidth/2,z:baseZ.
- Top-front (
Blinder,Strobe):x: evenly spaced from start to end (top group),y:baseY - trussWidth/2,z:baseZ + trussWidth/2.
- Bottom-front:
LX SIDESfixture distribution:- Fixtures are split into two symmetric groups (left/right side).
- Each side group is distributed along
Y(matching side truss direction). Xanchors match side-truss anchors (or fallback anchors when no side truss exists).
Smokefixture distribution (all non-LX SIDEShangs):
Smokefixtures are split into two symmetric groups (left/right side), distributed alongYusing side-truss extents when available.- Without side trusses, each side uses fallback
Y = 1.0 mwith500 mmspacing moving upstage (same side-style fallback spacing). Zis forced to floor (0 mm).Xanchors:- with real side trusses: same side-truss anchors as
LX SIDES, - fallback without side trusses:
0.5 minside the widest detectedLX*truss span (left = minX + 0.5 m,right = maxX - 0.5 m).
- with real side trusses: same side-truss anchors as
- If a hang is created from
pipe/varasyntax, all fixture categories on that hang use the same direct placement (xdistribution + sharedbaseY/baseZ); no extra front/back/top offsets are applied.
Numbering and identity rules
Only newly imported fixtures are renumbered/reassigned:
- Existing fixtures keep their
fixtureId,unitNumber, name, and transform. - Imported fixtures are grouped by fixture type and assigned ID blocks of 100:
- Type A:
101.. - Next type:
201.. - etc., continuing in 100-size blocks.
- Type A:
- Starting block is computed from the highest existing fixture ID.
- Unit numbers continue from the highest existing unit number detected for the same type.
Auto-patch behavior
At the end of import:
- Auto-patch runs by default.
- It is skipped only when config
rider_autopatch = 0.
Auto-color behavior after text scene creation
After a successful Tools → Create from text import:
- The same Auto color routine used after MVR import is executed.
- Fixture/layer colors are normalized immediately, including fallback random colors for fixture groups with no dictionary color. Fixtures without a GDTF library match are grouped by their normalized parsed type name, so repeated instances of the same missing fixture type receive the same color.
- This runs before the final scene refresh, so users see the colored result as soon as text import completes.
Config keys that affect import
rider_layer_mode(positionortype)rider_autopatch(1/enabled by default,0to disable)rider_lx1_height..rider_lx6_heightrider_lx1_pos..rider_lx6_posrider_lx1_margin..rider_lx6_margin
Console transform command rule contract
The console transform parser also defines user-visible behavior that must stay stable across releases:
rot x|y|z <values>keeps per-element rotation behavior (existing contract).rot x|y|z <values> --groupandrot x|y|z <values> --grotate the full current selection as one rigid group.- Group rotation default pivot is the center of the selected elements' axis-aligned bounding box (bbox center), computed from selected fixtures, trusses, supports, and scene objects.
- If the command ends with
x,y,z, that triplet is used as pivot override. Coordinates are interpreted in meters (same user-facing unit convention as other console position inputs) and converted to internal millimeters. - For group rotation, element orientation and position are both updated so the selection behaves as a single transformed block around the chosen pivot.
Maintenance protocol (mandatory when rules change)
When changing any text-to-scene parsing or placement behavior:
- Update this file (
docs/developer/text_to_scene_rules.md) in the same PR. - If user-visible behavior changes, update Help/README references.
- Add or update tests under
tests/covering the new/changed rule. - Mention the exact rule change in the PR description.
Suggested user-doc excerpt
Perastage parses rider text by identifying sections, hang positions, fixture lines, and truss declarations, then generates fixtures/trusses with predictable layer, placement, numbering, and auto-patch rules. PDF riders are first converted to text and parsed using the same rule set.
Truss dictionary and resource resolution
- Dictionary matching is case- and whitespace-insensitive. Known finish words
such as
NEGRO,BLACK, andPLATAare removed only as complete tokens from both model candidates and length-bearing truss names; explicit lengths remain part of the candidate key. - A GDTF path identifies the source archive. An extracted
.glbor.3dsis the renderable 3D symbol resource. An optional GDTF SVG remains a 2D resource and is not assigned to the truss 3DsymbolFile. - Valid metadata-only or SVG-only archives may provide truss metadata, but Rider import retains deterministic dummy geometry when no supported 3D model exists. Imports never fabricate a model path or borrow another hang's resource for an unresolved truss.
- Rider truss resource tests distinguish supported 3D resource-path resolution from mesh rendering validation. GLB/3DS decoding and native mesh dimensions remain owned by the dedicated model-loader tests.
- Truss dictionary imports are copied into the active dictionary's managed
storage. Dictionary-backed Rider trusses use that managed GDTF archive for
modelFileandgdtfSpec; a transient import source is not their stable resource identity. - A
PERASTAGE_LIBRARY_PATHoverride root must already exist and be writable. Isolated tests create that root before resolving the active dictionary so transient sources and normal user-data fallback storage never become test fixture ownership locations.
GDTF metadata attached to generated fixtures uses Core electrical resolution. Planning power keeps explicit presence, including declared zero; ambiguous inputs and Beam-only definitions remain unknown. Project power overrides take precedence when metadata is refreshed.
Layer identity at the rider boundary
Rider generation keeps its existing position/type naming rules as presentation
metadata. Every generated Layer receives a canonical UUID immediately; the
application default uses DeriveDeterministicUuid("mvr:layer:default"). Generated
objects acquire UUID ownership at the rider/domain boundary, including objects
moved between rider layers during generation. No rider path emits
layer_default. See Layer identity for legacy compatibility.