name: gjames-dimensions description: Use when editing G.James V6 frame dimension formulas in BatchDimensionXml / QuoteFirstItemDimensionRoundTrip XMLs — getting equal glass widths (Equal_Glass=True) and correct slider rail length (False). SERIES-AGNOSTIC: principles apply to any frame series (246, 247, …); specific extrusion codes / mm values differ per series and must be verified, never copied across.
G.James V6 Dimension Formula Editing
⚠️ Series-agnostic rule (read first)
This skill describes principles and mechanisms that hold across frame series. Specific numbers
(extrusion codes like 245xxx, jamb/stile millimetre values, default fallbacks) are series- AND
track-specific — they differ between e.g. 246 vs 247, and between 3T vs 5T within a series.
- ✅ Correct kind of statement: "3T and 5T generally use different framing extrusions, so verify each."
- ❌ Wrong kind of statement: "3T jamb = 30mm" (treating one family's value as universal).
- A code shared across families may map to the same value, but the default branch usually differs.
- Never copy a value from one family/series into another. Re-derive or verify per family. (A real bug this caused: applying a 5T default of 31.5mm to 3T frames that needed 49.0mm → 17mm out.)
Any concrete 245xxx/mm values below appear ONLY in the clearly-marked "247 worked reference" appendix.
What this is
Editing dimension formulas exported from V6 (BatchFrameLibrary) so that when Frame:Equal_Glass = True
every glass comes out equal width, and when False the sliding sashes get the set rail length and the
fixed panels take the remainder. Output XMLs are applied back by a macro (BATCH_DIM_XML.bas).
Frame config codes
O = fixed sash, X = sliding sash, read left-to-right. Trailing track tag (e.g. IS/OS = inside/
outside slider). Corner frames carry an angle suffix _90/_270; flat frames have none.
Per-config dimensioning rules
| Family | Rule |
|---|---|
| One-direction (OX, XO, OXX, XXO, OXXX, XXXO, OXXXX, XXXXO …) | Dims on sliders only. Fixed panel = un-dimensioned remainder. |
| Meeting-stile / symmetric (OXXO, OXXXXO, O4XO …) | First N-1 sashes get dims including a fixed; rail applies to the sliders, fixed panels remain. More complex — treat separately. |
| Special fixed (FXO, OXF …) | A non-standard fixed panel; own jamb/meeting-stile handling. |
Lead vs inner sliders (one-direction):
- Exactly one lead slider — the slider against the jamb opposite the fixed panel. Its offset includes that jamb + lock stile + half interlock.
- Every other slider is inner (sits between two interlocks); its offset is one interlock.
- Identify lead vs inner from the nested frame code (lead = the plain slider panel; inners = the
secondary
_OXX/_XXO-style panels), confirmed by the receptor — NOT from the light-name digit.
Corner vs flat geometry (PRINCIPLE)
- Corner frames: one "jamb" is actually a corner stile (
Frame:ATR:Corner_Stile), resolved by a nested CASE on corner-type / inside-slider. Only the opposite side is a real jamb. - Flat frames: both sides are real jambs (
Frame:Left_Jamb,Frame:Right_Jamb) plus a lock stile. No corner stile, no corner CASE. - The lead-slider offset therefore differs: flat includes the lock-stile term; corner does not (its corner stile replaces that side's jamb).
Canonical formula shape (variable names are conventional, values are series-specific)
Let vLJ = if(Frame:Left_Jamb <> 0, Frame:Left_Jamb, "NF") ' IF-guard, see below
Let vRJ = if(Frame:Right_Jamb <> 0, Frame:Right_Jamb, "NF")
Let vJL = Case( left(vLJ,6) = "<code>", <val>, … <default> ) ' left jamb width lookup
Let vJR = Case( left(vRJ,6) = "<code>", <val>, … <default> ) ' right jamb width lookup
Let vFS = <fixed/blade stile> Let vI = <interlock> Let vLS = <lock stile>
( meeting-stile frames also: Let vMSL/vMSR/vMSa = <meeting stile assy> )
let vD = vJL + vFS + (interior members…) + vJR ' sum of ALL non-glass widths
Case( Frame:Equal_Glass = True, (FRAME:WIDTH - vD)/N + <offset>,
Frame:Rail_Length_Slider + <offset> )
N= glass count (= sash count).- Equal branch positions each dim's glass:
(WIDTH - vD)/N + offset. - Unequal branch (one-direction):
Frame:Rail_Length_Slider + <same offset>. NoFrame:Width-(vD+Rail*N)multiplier form — that's the obsolete shape. - Offset by role: inner slider = one interlock; lead slider = half-interlock + lock-stile + the opposite jamb (flat) / corner-stile (corner). Meeting-stile dims use the meeting-stile term.
IF-guard for string manipulation (critical, series-agnostic)
Before ANY string op (Left(), etc.) on a frame attribute, guard it:
Let vX = if(Frame:Attr <> 0, Frame:Attr, "NF")
... left(vX, n) ...
If the attribute is missing/deleted it returns 0 (an integer); a string op on an integer errors the
whole formula. The guard substitutes a harmless string ("NF") that falls through to the CASE default.
Note: on some frames a jamb attribute legitimately doesn't exist (e.g. certain track counts) → the CASE
default branch is what actually evaluates, so the default value must be correct for that family.
The attachment mechanism (the biggest lesson)
DIRECT vs destructive
- DIRECT set updates Name, Formula, MovingDirection only. It CANNOT change which lines/light a dim spans.
- Destructive set (
Light.CreateDimBtwnLines) deletes the dim and recreates it on a specified light. REQUIRED to move a dim onto a different sash / re-anchor it. - Failure mode that wasted hours: changing a dim's offset to suit a new sash while leaving it anchored to the old sash (DIRECT) → produces garbage. If the dim must measure a different sash, you MUST destructively re-anchor; the formula offset alone won't move it.
CoordObjName1 / CoordObjName2 = the attachment truth
Each dim exposes CoordObjName1 / CoordObjName2 — the two lines (mullions/jambs) it currently spans.
Read these to know where a dim actually is. Never infer a dim's sash from its order or its name.
Destructive re-anchor recipe (the worked pattern)
To attach a dim to slider light L (the ref is the light itself, corner-to-corner; V6 resolves the
bounding mullion/jamb lines from which light you pick — you do NOT name mullions):
TargetCreateMethod = Light.CreateDimBtwnLines
TargetDirection = H
TargetLightName / TargetLightFrameWideID / TargetLightId = L
TargetFirstRef = L TargetFirstRefLocation = TL
TargetSecondRef = L TargetSecondRefLocation = BR
TargetLocationToAdjust = BR
MovingDirection = <v> TargetMovingDirection = <v> ' set BOTH — both matter
Lead → attach to the lead slider light; inner → attach to the inner slider light. After recreate, V6
sets CoordObjName1/2 to the correct bounding lines automatically.
MovingDirection
Controls which way the dim grows / which reference edge is held stationary on resize. Set both
MovingDirection and TargetMovingDirection. The correct value reflects which jamb is the stationary
datum for that config — symptom of getting it wrong: "left jamb moving when the right jamb should be
stationary." Determine per config and confirm by test rather than assuming a cross-series constant.
TEST vs SET paths
The TEST run and the SET run can treat destructive recreates differently — always confirm the recreate
actually fired (look for the "Deleting Dimension for … recreate" log line and a non-zero
dimensions_changed). If a re-anchor doesn't seem to take, this is the first thing to check.
Reading the test CSV
Columns: frame_code, quote_item_number, stage, status, dimensions_read, dimensions_matched, dimensions_changed, glass_quantity, glass_widths_mm, glass_heights_mm, distinct_width_count, distinct_height_count, glass_equal, note.
dimensions_changed = 0→ the macro applied NOTHING (stale folder, already-applied state, or no match/diff). The result does not reflect your latest edits — re-export/re-run before trusting it.- glass-width delta ≤ 1mm = integer rounding → treat as pass.
- delta > 1mm = real formula error.
- A 17–18mm delta is often a single wrong framing value (e.g. wrong jamb default) feeding one glass.
Naming convention
- Lead slider:
Lead T<n> - Inner slider:
<Config> T<n>(e.g.OXX T2,XXXO T4) - Meeting-stile fixed/sliders:
LH Fixed T<n>,OXX LH/RH T<n>,OXXO LH/RH T<n> <n>comes from the nested-frame receptor (SLIDER_T<n>/FIXED_T<n>) — never the light-name trailing digit (which can differ from the real track).
XML formats
V6BatchDimensionXmlVersion 2 (current):<FormulaText><![CDATA[…]]></FormulaText>directly under<Dimension>;SetMode="DIRECT"; no FormulaB64/length/metric attributes. Destructive fields live in theTarget*attributes.V6QuoteFirstItemDimensionRoundTripVersion 1 (older): has aFormulaB64audit copy → set it toSTALE-SEE-FORMULATEXTafter editingFormulaText; also recomputeFormulaLength,FormulaContainsNBSP,FormulaNBSPCount,FormulaTabCount.- Whitespace:
\xa0(NBSP) separates logical lines inside the CDATA; tabs indent. Preserve them.!starts a comment that runs to the next NBSP/line-end.
Process discipline (hard-won — follow these)
- Use the data, don't assume. Read
CoordObjName1/2, receptors, nested frames, line list, frame width before deciding a dim's role/attachment. - Prove on one frame, then roll out. Fully fix ONE representative frame, TEST it, confirm, then batch. Partial application across many frames produces regressions that masquerade as the method failing.
- Verify on disk after editing (re-parse the file); don't treat your own edit log as proof of correctness or that the macro will apply it.
- Never change a passing frame without confirmation. A value that's right for one family can break another — defaults especially differ by series/track.
- Keep edits reversible (retain a source copy) and idempotent.
- When unsure which value is correct, ask for one worked example (frame width + expected glass width) and reverse-engineer, rather than guessing.
General gotchas
- Light-name trailing digit ≠ track number. Receptor is gospel.
- Result-var name vs jamb source: the computed var may be
vJL/vJRorvSJ/vFJetc.; what matters is which attribute (Frame:Left_Jamb/Frame:Right_Jamb) the CASE reads. - Don't declare the same variable twice.
- In Python edits, regex backreferences (
\1) can be mangled — use replacement callbacks. - Whitespace-only differences still need NBSP/tab preservation.
APPENDIX — 247-series worked reference (EXAMPLE VALUES; DO NOT reuse on other series)
These are the concrete values that proved correct for the 247 series during the 2026-05 work. They are illustrative of the shape of the data. Another series will have different codes/values — verify before using.
- Corner stile (corner frames, in the
vJR/vJLnested CASE): codes245208,245211/208,245225,245211/225. Values depend onFrame:ATR:Inside_SliderandFrame:ATR:Corner_Type(OUTSIDE/INSIDE) — e.g. IS=True/OUTSIDE 152.0 & 163.85; IS=True/INSIDE -16 & -4; IS=False/OUTSIDE 85.52 & 97.25; IS=False/INSIDE 50.52 & 62.35 (mm). - Flat jamb width CASE — shared code
245045 → 6.5mm; defaults differ by track: 3T family default49.0mm, 5T family default31.5mm. (This default difference was a real bug source — see the series-agnostic rule.) - Lock stile (flat one-direction):
vLS = 67mm + 4.5mmwith comment "4.5mm less penetration than fixed panel". - Other framing:
vFS = 20mm,vI = 45mm, meeting stilevMSL = vMSR = 50mm. - MovingDirection observed for 247:
0for configs ending in X (OX/OXX/OXXX),2for ending in O (XO/XXO/XXXO/OXXO). SetTargetMovingDirectionto match. (Verify per series.) - Folders:
246WPFF_Analysis/(old round-trip format),246WP247CORNER/(corner, v2),REMAINING/andFinal Check/(247 push). Test output insummary/+log/(UTF-16 CSV).
247 one-direction templates (example)
Flat F-on-LEFT (OX/OXX/OXXX/OXXXX), MD=0:
vD = vJL + vFS + (N-1)*vI + vLS + vJR
inner: (W-vD)/N + vI / Rail + vI
lead : (W-vD)/N + vI/2 + vLS + vJR / Rail + vI/2 + vLS + vJR
Flat F-on-RIGHT (XO/XXO/XXXO/XXXXO), MD=2 (mirror):
vD = vJL + vLS + (N-1)*vI + vFS + vJR
inner: (W-vD)/N + vI / Rail + vI
lead : (W-vD)/N + vJL + vLS + vI/2 / Rail + vJL + vLS + vI/2
Corner equivalents: same shape but the corner-stile side has no separate vLS term.