Dynamo Built the Whole Tunnel and Never Read the Excel File

  Part 1 of a series — a Dynamo tunnel geometry that was already finished, a 39-sheet Excel workbook it had never been wired to, the web viewer built to referee the two, and the Revit add-in at the end of it.

A working Dynamo graph already existed. It produced the full 3D geometry of a Korean road tunnel project (NATM), 892 m of it, and it ran without errors. The problem was that nothing inside it knew where its numbers came from.

This post is about the two artifacts I inherited: a 617-node graph with zero external data references, and a 39-sheet workbook that is a complete, beautiful drawing set for a human being and an unlit maze for a machine. Everything below is measured from the files themselves — static parsing of the graph JSON and cell-level reads of the workbook.

What the original graph was actually building

The 892 m tunnel solid the original Dynamo graph already produced, shown in the viewer's 3D mirror. The geometry was never the problem — nothing in it was linked back to the workbook that defined it.

The pre-Dynamo QA screen. The workbook counters in the top-left are the whole brief in four numbers: 33 sections, 66 frames, 34 unique, 892 m of alignment.

Nobody started from a blank canvas. The starting point looked like this:

ItemCount
Nodes617
Connectors827
Code Blocks159 (117 containing numeric literals)
Python Script nodes4
Groups / annotations59 (34 still on the default untitled name)
External data references (Excel / CSV / JSON)0

That last row deserves its own sentence. A full-text search of the graph JSON for the strings Excel, excel, CSV, JSON, ImportExcel, ReadFromFile and FilePath returns zero hits for every one of them. This is not "the Excel link was stale." There was never a link.

The full function histogram

Break the node list down by function and the intent becomes obvious. Below is the complete histogram — every distinct function that appears in the graph, not a selection. 416 of the 617 nodes carry a function signature; the remaining 201 are 159 Code Blocks, 35 extension nodes (Watch, List Create, Revit selectors), a boolean input, a string input, a number input and a handful of others.

FunctionCountRole
Line.ByStartPointEndPoint28Straight segments
List.Flatten22List rank housekeeping
Curve.StartPoint / Curve.EndPoint19 / 19Curve endpoint extraction
Geometry.Translate16Offsetting
List.FirstItem / List.LastItem16 / 15Picking one end of a result list
List.Transpose16List rank housekeeping
List.SortByFunction13Ordering split fragments
Geometry.Intersect12Intersection points
Solid.ByLoft11Longitudinal extrusion
Curve.Length10Alignment and segment lengths
Geometry.Rotate9Guide-line rotation
Vector.ZAxis8Rotation axes
PolyCurve.ByJoinedCurves8Chaining profile curves
Vector.XAxis / Vector.ByTwoPoints6 / 6Frame construction
Point.Z6Level extraction
Curve.PointAtParameter6Normalised sampling
Geometry.Explode / Geometry.Split6 / 6Breaking curves at intersections
List.GetItemAtIndex6Indexed extraction
Point.ByCoordinates5Section seed points
Surface.ByLoft5Surfacing
Arc.ByThreePoints5Arch construction
List.DropItems5Removing one circle before the envelope solve
Plane.ByOriginNormal5Cutting planes
GeometryColor.ByGeometryColor / Color.ByARGB5 / 5Preview colouring
Line.ByBestFitThroughPoints4Section skeleton
Vector.Reverse4Direction flips
PolyCurve.ByGroupedCurves4Grouping perimeter curves
Curve.ExtendEnd / Curve.ExtendStart4 / 2Over-extending curves before trimming
Curve.PointAtSegmentLength4Distance-based sampling
Line.ByStartPointDirectionLength4Ray construction
CoordinateSystem.XAxis / YAxis4 / 3Frame axes
CoordinateSystem.ByOriginVectors4Frame construction
Curve.TangentAtParameter3Tangent vectors along the alignment
List.TakeItems3Truncating lists
Surface.PerimeterCurves3Surface back to curves
Circle.ByCenterPointRadius / …RadiusNormal2 / 2Clearance and tangent construction circles
Arc.ByStartPointEndPointStartTangent2Arch construction
Arc.ByCenterPointStartPointEndPoint2Arch construction
Curve.OffsetMany2Thickness offsets
CoordinateSystem.Rotate / Translate2 / 2Frame placement
CoordinateSystem.ZXPlane / YZPlane2 / 2Section planes
Curve.Patch / Surface.ByPatch2 / 2Capping closed curves
Curve.ParameterAtPoint2Locating a point on a curve
List.FilterByBoolMask / List.Join2 / 2Filtering circles out of the transform output
Object.Identity2Pass-through
Element.Geometry / Element.Curves2 / 2Revit element to geometry
FamilyInstance.ByCoordinateSystem2Family placement
DirectShape.ByGeometry2Export to Revit
Singletons (one each)32Solid ops (Difference, DifferenceAll, ByUnion, BySweep, BySweep2Rails, SweepAsSolid), Geometry.Transform, Geometry.Scale, Rectangle.ByWidthLength, Sphere.ByCenterPointRadius, String.Contains, Material.ByName, ImportInstance.ByGeometries, and 19 more sampling/plane/vector helpers

Two things stand out. First, list housekeeping is the third-largest category in the graph — the twelve List.* functions total 111 nodes, 18% of the graph. Second, there is exactly one Geometry.Transform in the whole file, and everything hinges on it. It is the boundary between "a section drawn flat at the origin" and "that section placed on the alignment."

The extension nodes

Extension nodeCountWhat it means
List Create23The section is assembled as nested lists, by hand
Watch6Six things the author wanted to keep an eye on
Family Types2Left and right utility-duct family selection
Categories2Revit category for the DirectShape output
Select Model Element1The only external input in the entire graph
From Object1Object-to-string for the circle filter

One Select Model Element. That single node — a human clicking the alignment curve in the Revit model — is the graph's whole interface with the outside world. Everything else is typed in.

The four Python nodes, one by one

Part 2 will come back to these, but their division of labour is the clearest statement of what the graph believed it was doing.

Canvas XLengthRoleWhere its output goes
1,2641,215 charsEnclosing-circle solverList.LastItem / List.FirstIteminto production
15,6282,192 charsTangent-line solver (one side)Watch only
15,6312,192 charsTangent-line solver (other side)Watch only
27,973142 charsAdjacent-profile pairing helperSolid.ByLoft.crossSections

The two tangent solvers are the same code twice. Both are 2,192 characters, and reading them side by side they are identical text: build the midpoint between a start point and the target circle's centre, make a helper circle through both, intersect it with the target circle to get the tangency points, shoot a ray, intersect the ray with a target line, return the segment. They sit at X = 15,628 and X = 15,631 — three units apart, effectively stacked on top of each other on the canvas. One of them is the left side and one is the right, and nothing in the file says which.

More importantly: the outputs of both tangent solvers go to a Watch node and nowhere else. Tracing the connectors, neither one feeds any geometry that survives into a solid. So the tangent-circle machinery — the most mathematically interesting thing in the file — was diagnostic. The upstream review note recorded this carefully: they "should be treated as diagnostic/helper evidence unless further Dynamo UI behaviour proves hidden use." The real side-wall geometry came from somewhere else entirely, which we get to below.

The enclosing-circle solver is the one that matters. It takes a list of clearance circles and a centre point, and for each frame computes:

d1 = center_point[i].DistanceTo(p1)
req_r1 = d1 + r1
...
final_radius = max(req_r1, req_r2, req_r3)

That is: the smallest radius, about a given centre, that swallows all three clearance circles. Its two outputs (solution_circle, final_Rad) go straight into the R1 crown branch. R1 is a computed result. Part 2 explains why that single fact was the most expensive thing in the project.

The fourth Python node is 142 characters and does one thing:

for i in range(len(geom_list)-1):
    out_geom.append([geom_list[i], geom_list[i+1]])

It pairs each profile with the next one so Solid.ByLoft can extrude between consecutive stations. A four-line node, and it is the entire longitudinal logic of the model.

One detail worth flagging now: the tangent solvers contain Line.ByStartPointDirectionLength(start_point[i], direction_vector, 99999). That 99999 is a hardcoded number, and it is invisible to any census of Code Block literals — it lives inside a Python string field in the JSON. Every count in this series of "how many hardcoded numbers were there" is therefore a floor, not a total.

The pipeline, read off the group titles

The group titles, read in order, are the pipeline:

import and rebuild the alignment
  -> 2D projection and verticals
  -> 2D tangent vectors (X axis, Y axis)
  -> 2D coordinate system: x-axis along travel, z-axis to true north
  -> 3D coordinate system (2D x,y carried over; z automatic)
  -> section construction and clearance envelope
  -> crown / crown inner clearance

The alignment enters through the single Select Model Element node picking the curve straight out of the Revit model. Coordinate systems are erected along it, a section is drawn on each one, and lofts sweep them longitudinally.

But that clean seven-step story is told by 15 of the 59 groups. The rest of the annotation layer looks like this:

Group titleCount
Title <Double click here to edit group title>34
Empty string5
* / **2 / 1
CoordinateSystem.Axis2
Meaningful pipeline titles (the seven above, plus "sectional configuration and facility limits", "crown", "circle plane rotate check", "check", "skew", "Geometry", "X")15

39 of 59 groups carry no information at all. The author organised the canvas and then did not name two-thirds of the boxes. The group layer tells you where boundaries were drawn, not what they mean.

Where the section actually comes from, in screen coordinates

The graph has no declared workspace inputs and no declared workspace outputs. There is no entry point and no exit point; what you see depends on which terminal nodes have preview switched on. The only navigational structure is position on the canvas, which spans roughly 49,000 units left to right. The analysis note that mapped it reads:

X rangeStage
−13,600 to −8,000Local section seed geometry — points, best-fit lines, closure lines, small circles, one rotated rectangle
−12,000 to −4,600Alignment extraction and coordinate-frame preparation
−1,700 to −1,300The single Geometry.Transform — local section meets alignment frames
0 to 3,000Filtering the transform output for circles; the envelope solver; rotated guide lines
9,000 to 12,000R1 crown arc, Curve.OffsetMany(300), surfacing, the R1 solid
13,000 to 17,000R2 helper circles, the two tangent solvers, the two family-instance branches
17,000 to 26,000R2 and closure curve assembly; the INSIDE and OUTSIDE labels
27,000 onwardSolids, sweeps, differences, DirectShape, colouring

How the sidewall was really built

Here is the part that no summary of the graph would predict. The left and right sidewall geometry does not come from arcs at all. It comes from Revit families:

Family Types: [utility duct, left]
  -> FamilyInstance.ByCoordinateSystem
    -> Element.Curves
      -> PolyCurve.ByJoinedCurves
        -> Geometry.Rotate(90)
          -> Curve.StartPoint / Curve.EndPoint
            -> Plane.ByOriginNormal
              -> Geometry.Intersect
                -> Geometry.Split
                  -> List.SortByFunction
                    -> Line.ByStartPointEndPoint
                      -> List.Transpose

and the identical chain again for the right side. The graph places a family instance in the Revit model, reads its curves back out, and uses those curves as section geometry. That is a legitimate technique — the duct profile is authored once as a family and reused — but it has a consequence that dominated the rest of the project: this branch cannot be reproduced by parsing a file. It depends on Revit session state, on which family is loaded, on what that family's type parameters currently are. A later review note put a hard rule on it:

Prefer native Dynamo nodes for Family Types, FamilyInstance.ByCoordinateSystem and Element.Curves, because they depend on Revit family and session state. And the appearance of native function names as text inside a Python node is not the same as preserving the original native branch behaviour.

The important part is what is missing

Full-text search across the graph JSON:

KeywordOccurrences in graph
utility duct / crown / clearance envelope4 / 4 / 5
lining, shotcrete, rockbolt, grouting, forepoling0 each
superelevation, pavement, barrier, sidewall, steel rib0 each

So the graph built the inner arch, the clearance envelope and the section skeleton out to the utility ducts, plus its longitudinal extrusion. Lining thickness, shotcrete, rockbolt patterns, steel-pipe reinforcement grouting, forepoling: none of these existed in the graph, not even as a word. That is the exact scope of "the geometry was already done."

R1 / R2 / R3 — the shared vocabulary

The standard tunnel section as it lives in the Excel sheet. R1, R2, R3, dist_cr_ct and angle_R1_arc are written on the drawing itself — the vocabulary the rest of the series uses.

The section is a three-centred arch. If you have never drawn one, the construction is worth understanding, because every argument in the rest of this series is conducted in these three letters.

Start from the road. The road centreline is the datum: it is where the chainage is measured and where the surface levels are defined. The tunnel is not centred on the road — the carriageway is asymmetric (7.2 m of running lanes with a 2.505 m shoulder on one side and 1.005 m on the other), so the arch has to be shifted. That shift is dist_cr_ct, 4.35 m horizontally. The shifted point, raised or lowered by h_cr_ct (here −0.2295 m), is the tunnel centre, and it is the centre of R1.

  • R1 — the crown arch, radius 6.98 m about the tunnel centre. It does not sweep a full semicircle; it covers angle_R1_arc, given separately as 100°. Roughly 50° each side of vertical, then it stops.
  • R2 — where R1 stops, R2 takes over and curves the wall downward. Its centre is a different point, chosen so that R2 is tangent to R1 at the handover — the two arcs meet with no kink, which is what makes the section look like one continuous curve rather than two arcs bolted together.
  • R3 — the same trick once more, lower down, tangent to R2, running to the foot of the wall where the sidewall meets the slab and the drainage detail.
  • R2' / R3' — the right-hand counterparts. The prime means the other side, and the two sides are not equal.
ParameterLeftRight
R16.98(single value)
R2 / R2'3.9593.756
R3 / R3'4.6094.414
angle_R1_arc100 (deg)

Note the shape of those numbers: R3 is larger than R2 on both sides, and each right-hand value is about 5% smaller than its left-hand twin. The asymmetry is not decorative. It exists because the shoulders are different widths, so the two walls have different amounts of room to get down to the same slab level. Any code that assumes the section is symmetric will produce something that looks completely correct and is wrong on one side by roughly 200 mm.

Offsetting these arcs outward by t_lining and t_shotcrete gives the lining and shotcrete outlines. The tangent-circle solvers in the original graph exist precisely to stitch R1–R2–R3 together tangentially — even though, as established above, their outputs were only ever wired to a Watch node.

Across all 33 section sheets, the values of R1, R2, R2', R3, R3', angle_R1_arc and t_lining are completely identical.

Comparing all 33 sheets, that seven-value combination has exactly one variant. The only thing that changes from section to section is t_shotcrete — seven values: 0.05, 0.06, 0.08, 0.09, 0.12, 0.16, 0.2. One arch skeleton for the whole tunnel; ground class changes the shotcrete thickness and the reinforcement pattern, nothing else.

So why was this "unmanaged"?

The road cross-section sheet. The dimension text is not numbers but variable names — shoulder_left, width_lane, t_pav, t_slab, t_filter. The drawing is already parametric on paper; nothing reads it that way.

Reason one: the graph's station split did not match reality

Exactly one Code Block handles longitudinal division, and this is all of it:

st..en..20000;

st is a constant 0. en is wired to Curve.Length of the alignment. In other words: uniform sampling of the entire alignment at 20 m intervals. Its output goes to List.Flatten and from there into the frame-generation chain.

The workbook, meanwhile, divides the tunnel into 33 sections, and those sections are not uniform at all.

Section lengthNumber of sections
8–15 m8
18–21 m9
24–42 m12
60 m1
96 m1
104 m2

Total length 892 m — the alignment runs from chainage 461 m to 1,353 m — and combining the start and end stations of 33 sections gives 34 unique boundaries. A 20 m uniform grid and 34 irregular boundaries were never going to line up. The graph assumed a tunnel may be sliced at even intervals; the actual design is sliced where the support pattern changes.

The ratio is the thing to look at. 892 m at 20 m spacing is 45 frames. The design needs 34. Those two numbers are close enough that nothing looks obviously wrong on screen, and far enough apart that almost every frame is in the wrong place. An 8 m section gets zero frames or one, depending on where the grid happens to fall. A 104 m section gets five frames it does not need, all with identical geometry. The error is invisible and total.

Reason two: the dimensions lived inside the graph

117 of the 159 Code Blocks carry numeric literals. There are 38 distinct values. They look like this:

3600+3600+2500-b;
725+200+4600;
725+625+200;
-1000+b;   b=655+250;

These are not injected from anywhere; they are sums typed by hand inside a node. And since the graph has zero nodes that read Excel, CSV or JSON, the workbook can change all it likes — the graph will never hear about it. Correcting one section dimension means hunting through 617 nodes for the right Code Block, and the group titles meant to help that hunt were still on their default name in 34 of 59 cases.

What this project was actually for

Not "model a tunnel in Dynamo." The geometry existed. The problem was that the geometry was disconnected from the design data. The working rule, written down on the first day of the reconstruction, became:

The workbook is the single source of truth. PDFs are secondary reference.
Build one shared section schema, and make the web viewer and the Dynamo graph both eat the same thing.

A source-priority order was fixed at the same time and did not change afterwards: (1) the original Dynamo graph, (2) Excel stations and dimensions, (3) CAD/DXF as optional audit only. That ordering is not obvious and it caused arguments later — it says that when the workbook and the original graph disagree about geometry, the graph wins, because the graph is what was actually built and reviewed. The workbook wins on dimensions. Untangling which of those two categories a given disagreement belonged to is, more or less, the subject of Part 3.

The unit of output is not a section but a frame. Give each of the 33 sections its own start and end frame and you get 33 × 2 = 66 frames; since one section's end coincides with the next section's start, the distinct station boundaries number 34. Those 66 frames became the atom shared by the viewer, the Dynamo graph and the Revit add-in.

It is worth dwelling on why 66 and not 34. Deduplicating to 34 would be tidier and would halve the data. It was rejected because a section boundary is two different things at once: the end of a support pattern and the start of the next one. At a boundary between a D-2 section and a P-1 section, the shotcrete thickness on the "end" frame and the "start" frame are different numbers at the same chainage. Collapse them and you have to decide which one wins, silently, forever. Keeping 66 frames means the model carries the discontinuity explicitly instead of resolving it by accident.

The workbook: 39 sheets

Deliberately the same picture as two figures ago. Sheet 10's tunnel section is the same PNG as sheet 1's — all 12 extracted TUNNEL_SECTION_REFERENCE images in the workbook, including the one on the '표준 Data' (standard data) sheet, hash to a single MD5. Many sheets, one picture.

TypeSheetnRole
Global definitiontunnel_info1Project and alignment data, coordinate ranges, default thicknesses, and the section split table (No / support Type / rock class / STA / distance from alignment start / Length)
Global definitioninfo1Source values for standard cross-sections per direction, R1/R2/R3, utility duct and barrier dimensions
Support pattern libraryDef_pattern_Type1Reinforcement spec library, by Type
Section sheetsT_sec_1T_sec_3333Per-section dimensions plus embedded drawings
Instructions"standard data format ==>"1How to use the hidden template sheets
Hidden templateDef_pattern_Type standard data1Master copy of the pattern sheet
Hidden templateT_sec_1 standard data1Master copy of the section sheet

The section split table on tunnel_info is the spine of the whole thing. Its columns — number, support type, rock class, station, distance from alignment start, length — are what turn "the ground is worse here" into "this stretch of tunnel is 18 m long and uses type D-2." Every frame in the model traces back to one row of that table. The first section sheet's start/end stations read 461 and 491; the thirty-third reads 1,323 and 1,353.

503 image anchors, 13–15 bitmaps

ItemCount
Drawing items (total)606
— embedded image anchors503
— shapes103
Sheets containing drawings37 / 39
Unique bitmaps in the workbook13–15

This is the first trap. "503 images" counts anchor instances, not drawings. There are only 13–15 source bitmaps; a handful of pictures repeated across 33 sheets is what produces 503 anchors.

RoleAnchorsSources
TUNNEL_SECTION_REFERENCE341
ROAD_CROSS_SECTION_REFERENCE341
UTILITY_LEFT / RIGHT_DETAIL_REFERENCE34 / 341 each
BARRIER_LEFT_DETAIL_REFERENCE341
R1_1 / R1_2_CYCLE_REFERENCE34 / 341 each
R2_R3_1 / 2_CYCLE_REFERENCE34 / 341 each
R2P_R3P_1 / 2_CYCLE_REFERENCE34 / 341 each
GROUTING_FOREPOLING_REFERENCE1141

34 anchors per role, but there are only 33 sections. One too many. The hidden template sheet carries the exact same drawing and role layout as a live section sheet — so scraping sheets by the T_sec_* pattern quietly admits a 34th, fake section.

A cross-check from a later audit, counted a different way and on a different day, put the barrier reference at 34 files with 1 unique hash — thirty-four copies of a byte-identical crop — and the whole-workbook reference-image census at 355 (tunnel 34, road 34, utility 68, barrier 34, support cycle 115). The two counts differ because they scope "reference image" differently; both agree on the structural fact that matters, which is that the number of distinct pictures is in the low teens and everything else is repetition.

That repetition is not a filing quirk. It is the single assumption that produced most of Part 3. If a picture is stamped 34 times and the numbers beside it change 33 times, the picture cannot be a scale drawing of any one of them. It is a symbol. Every attempt to measure a dimension off those pixels was doomed before it started — and it took a long time to establish that, because the pixels are perfectly measurable and the measurements are perfectly stable.

Inside one section sheet

Everything extracted from a single section sheet — 13 embedded images, from the full section down to the support cycles. One sheet is a small drawing set, not one drawing.

The left utility detail. Every dimension line carries an Excel variable name (l_barr_y3, left_utility_w, left_drain_dia) instead of a value.

All 33 section sheets share the identical extent A1:AG183, and cross-checking the cell address of every major label across all 33 gave zero mismatches. The usual nightmare — cell addresses drifting sheet to sheet — does not exist here. A different one does: a single sheet holds three different representations at once.

RegionColumnsCharacter
1. Human-facing layoutB–LLabels and values scattered between drawing images; portrait and landscape arrangements mixed
2. Machine-facing flattened mirrorAA–ADAA = index (0–156), AB = label, AC = [Start]/[Left]/[1-cycle], AD = [End]/[Right]/[2-cycle]
3. Pattern label columnAFBlock item names, [R1_1-Cycle] through [grouting]

Region 2 deserves a moment of appreciation. Someone on the design side built a machine-readable mirror of their own sheet, by hand, in columns AA to AD, 157 rows deep, with a start/end and left/right pair on each row. Nobody asked them to. It is the single most helpful thing in the workbook and it is invisible unless you scroll sideways past the drawings. It is also the reason the frame model has a start and an end value for every parameter: the mirror already assumed that structure.

The images sit in region 1, at fixed anchor ranges:

DrawingAnchor range
Road cross sectionE24:M38
Tunnel sectionE41:M59
Utility duct, left / right detailC64:F79 / H64:L80
Left barrier detailB90:D102
R1 1-cycle / 2-cycleA107:E121 / G106:K121
R2,R3 1-cycle / 2-cycleB126:E141 / G126:K141
Grouting and forepolingA166:E181

The cell map

The dimensions for each drawing sit in the cells directly below or beside it. There are 27 such parameter groups. Here is the whole map, not a sample — this table is what a machine needs and what the workbook never states:

#ParameterLabel cellValue cell(s)ValueMeaning
1t_pavB11C11 / D110.03Pavement thickness
2t_slabB12C12 / D120.25Slab thickness
3t_filterB13C13 / D130.15Filter layer thickness
4shoulder_leftD19D20 / D212.505Left shoulder
5width_laneE19E20 / E217.2Carriageway width
6shoulder_rightF19F20 / F211.005Right shoulder
7Superelevation [Main]B25C25 / D25−0.03Main carriageway crossfall
8Superelevation [R_Shoulder]B26C26 / D26−0.02Opposite shoulder crossfall
9dist_cr_ctB42B434.35Road centre to tunnel centre, horizontal
10h_cr_ctB44B45 / B46−0.2295Road centre EL to R1 centre EL
11R1B48C486.98Crown arch radius
12R2 / R2'B49C49 / D493.959 / 3.756Upper sidewall arch
13R3 / R3'B50C50 / D504.609 / 4.414Lower sidewall arch
14angle_R1_arcB54C54100Arc angle covered by R1
15_liningB57C57 / D570.3 / 0.02Lining thickness / overbreak
16_shotcreteB58C58 / D580.2 / 0.16Shotcrete thickness / overbreak
17half_sectionB60C601.04Half-section related dimension — meaning not established
18left_utility_wB81C810.3Left utility duct upper width
19left_utility_hB83C830.3Left utility duct height
20right_utility_wK81L810.2Right utility duct upper width
21shoulder_left_1E81F811.75Left gutter-zone width
22left_bottom_tE82F820.487Left lower thickness
23left_drain_diaB86C860.3Left drain pipe diameter
24barr_z4barr_z1E91–E94F91–F940.36 / 0.175 / 0.123 / 0Barrier step heights
25barr_y5barr_y1G90–K90G91–K910 / 0 / 0.15 / 0.036 / 0.12Barrier step widths
26Grouting, 4 specsB182–E182B183–E1831 / 0.1143 / 12 / 5Reinforcement dia, pipe dia, length, drilling angle
27Forepoling, 3 specsI182–K182I183–K1830 / 0 / 0Not applied in this section

Units are metres; angles are degrees. Two rows in that table were misread for weeks and only settled late, which is worth recording because the misreading was entirely reasonable:

  • D57 and D58 are overbreak, not an auxiliary thickness. The column headers are thickness(t_) and overbreak(ob_), so D57 = ob_lining = 0.02 m and D58 = ob_shotcrete = 0.16 m. An earlier reading treated them as a second, auxiliary thickness. Independent references put typical NATM overbreak at 15–20 cm, which matches 0.16 m and does not match any plausible "auxiliary thickness"; the earlier interpretation is recorded as an error.
  • half_section = 1.04 m is still unexplained. It exists in two places (C60 and the mirror at AC58) and is used zero times in the original graph. A later work plan put an explicit prohibition on it: do not execute any upper/lower bench split until the meaning of half_section is established. Guessing at a 1.04 m value and cutting the tunnel in half on the strength of it would have been the single most expensive guess available.

And the values do not originate in the sheet. The content of C48 is not a number:

=IF($G$3="up-direction", info!$B$5, info!$J$5)

The section sheet is a view that pulls from the info and tunnel_info sheets depending on carriageway direction. The AA–AD mirror then references that view again (AC46 = "=C48"). Support type is a lookup too:

G10 = INDEX(tunnel_info!$C$39:$C$969, MATCH($G$9, tunnel_info!$B$39:$B$969, 0))

On the first section sheet: 558 formulas against 428 literals. More than half the sheet is a computed result. A parser that reads cached values gets the right answer; a parser that reads formulas gets a dependency graph; a parser that reads one and reports the other is lying without knowing it.

One cell contains a sentence rather than a number, and it turned out to matter more than most of the numbers. B23 carries a note explaining the superelevation convention: [Main] runs from the road centre toward the shoulder on the lane side, and [Shoulder] applies only to the shoulder on the opposite side. Both C25 and C26 already contain signed values (−0.03 and −0.02). The sign was negated in code at least once, and the correction note that fixed it carries an unusually blunt guardrail: do not negate the Excel slope cells again, and do not fit the sign to the raster result. That second clause — do not choose the sign that makes the picture match — is the whole of Part 3 in nine words.

The pattern library, and one gap

The R1 support-cycle sheet: rock bolts arrayed radially around the arch. This is the pattern library the workbook is built on.

The grouting / forepoling cycle — circles arrayed over the crown. Each pattern is a separate embedded image with its own geometry.

The R2/R3 cycle sheet, drawn with no bolt or grouting array at all. The pattern set is not uniform across support classes.

Def_pattern_Type is laid out as rows = items, columns = Type. There are 15 Types — A, B-1, B-2, C-1, C-2, D-1, D-2, E-1, P-1, P-2, RP-1, RP-2, RP-3, WL-2, WL-3 — and all 15 are actually assigned somewhere across the 33 sections.

Seven definition blocks:

BlockItems definedRows
[R1_1-Cycle]diameter / length / transverse start offset / transverse end offset / transverse c-t-c / longitudinal start offset / longitudinal c-t-c7
[R1_2-Cycle]same seven7
[R2,R3_1-Cycle]same seven7
[R2,R3_2-Cycle]same seven7
[R2'R3'_1-Cycle]same seven7
[R2'R3'_2-Cycle]same seven7
[grouting]reinforcement dia / pipe dia / length / drilling angle / start-end discriminator / two transverse offsets / transverse c-t-c / shotcrete offset / longitudinal offset / longitudinal c-t-c11
Total53

53 rows total, 53 × 15 = 795 cells, all populated. The six-block structure is three regions (R1 / R2,R3 / R2'R3') × two excavation cycles, so bolts can be staggered between cycles — the second cycle's bolts sit in the gaps left by the first, which is standard practice and is why the library needs two sets of offsets rather than one set and a spacing.

Representative values across six of the fifteen Types:

ItemAB-2C-1E-1P-2WL-3
R1 rockbolt diameter00.0250.0250.02500
R1 rockbolt length034400
R1 longitudinal c-t-c0762400
Grouting reinforcement dia000011
Grouting length00001212
Grouting drilling angle000055

Read that table across and the design logic is legible without a single word of explanatory text. Type A — the best rock class — is zero across every item: unsupported, no bolts, no grout. B-2 through E-1 bolt the crown, and the longitudinal spacing stretches from 3 m to 24 m as the rock improves, which is another way of saying "at E-1 we are barely bolting at all." The bolt diameter never changes — 25 mm throughout — only the length and the spacing. And only P-1, P-2, WL-2 and WL-3 — the portal and weak-zone types — carry grouting values at all: 12 m pipes at a 5° look-out angle, which is a forward umbrella, not a radial bolt pattern.

The block comment next to the rockbolt sections reads, in effect, if no rockbolt is applied, set length = 0. So zero is not missing data. Zero is a design decision, stated as a convention in a comment, in one place, in Korean. Any importer that treats 0 as null gets this exactly backwards.

And there is a hole. The workbook's own filename announces that forepoling was added, and the section sheets do contain three forepoling spec cells — but Def_pattern_Type has no forepoling block at all (zero hits across every cell of the sheet). Forepoling bypasses the Type library and is typed directly into the section sheet. Steel ribs, meanwhile, return zero hits across the workbook's entire shared-string table. They simply are not there.

Late in the project a check against independent references pinned down what the numbers that are there mean: crown coverage around 120°, look-out 6°, transverse spacing 400 mm, pipe length 12–15 m with about 4 m of overlap between umbrellas. The workbook's own values — pipe Ø114.3 mm, 500 mm centres, 12 m grouting length, 5 m forepoling length — line up with that. The point is not that the numbers were validated. It is that nothing in the workbook says what they are numbers of, and it took an external reference to be sure.

The hidden sheet problem

The unhide dialog. The '표준 Data' (standard data) sheets that hold the design basis were hidden in the workbook — the data existed, it just was not visible.

How they were found: right-click on a sheet tab, 숨기기 취소 (Unhide). Nothing about the file's normal appearance suggested the sheets were there.

Both template sheets are marked hidden, and the workbook explains itself:

These sheets are the standard data sheets for the structure …
1) When using standard data, copy them so that the standard data is preserved …
3) The standard data may be deleted once the workbook is complete.

So they are masters you copy when creating a new section sheet, and leftovers you may delete when done. For a human this is harmless. For a machine it is not. It fails in four separate ways, and each one has a different fix.

1. The name is T_sec_1 standard data. A naming collision, not a content problem. Any rule of the form "every sheet matching T_sec_*" picks it up as item 34 in a list of 33 — and it sorts adjacent to the sheet it was copied from, so a spot check of the first few sheets will not catch it. Any fix based on names is fragile in both directions.

2. The content is nearly identical to a live sheet. Cell-by-cell comparison across the whole extent: 5,872 of 6,039 cells identical, only 167 different — 97.2% the same. This is what makes the first failure dangerous rather than merely annoying. If the template were visibly a template — empty cells, placeholder text, a big red banner — a human reviewing the parsed output would spot the extra section immediately. Instead the 34th section looks exactly like a real one. Every drawing is there. Every label is there. Every value is plausible.

3. The values are stale. Of those 167 differing cells, one is R1: the template says 6.982, all 33 live sheets say 6.98. Two millimetres. Nothing in the model complains about a 2 mm radius difference — no gate catches it, no visual comparison shows it, and the resulting geometry lofts perfectly. You would simply have a 34th frame, at a chainage that does not exist, with an arch radius nobody ever specified, extruded into a solid. That is the exact profile of a defect that survives to construction.

4. Hidden means invisible to humans and, by default, invisible to parsers. This is the compounding factor. The engineer opening the workbook does not see the template at all, so the person best placed to ask "why is there a 34th section?" is structurally unable to notice. Meanwhile the hidden state lives in workbook.xml as state="hidden" on the sheet entry — walk the sheet XML without reading it and the sheet walks right in.

The fix that actually held was none of the above. It was to select sheets by tab colour. The engineer had been marking live section sheets green all along — a convention nobody wrote down, visible in the workbook as a theme index. The adapter's default option became station_tab_theme: "9", and the problem disappeared, because the rule now matched the practice instead of guessing at it. The template sheet is not green. It never was.

Four reasons this structure is hard for a machine

1. Values live in cells, shape lives in bitmaps, and 33 sections share the bitmaps. The utility duct detail has parameter names — left_utility_w, left_bottom_t, left_drain_dia, l_barr_y3 — written as text on the picture. Which dimension line corresponds to which cell can only be inferred from position within the image. Worse: shotcrete thickness varies across seven values, yet all 33 sheets embed the identical section bitmap. The drawing is a symbol, not the section's real geometry. Measuring scale off those pixels is wrong in principle, not just in practice.

2. Some dimensions exist only on the drawing and in no cell at all. The left utility duct detail carries printed numbers like 50, 60, 125, 100, 500, 300. The values corresponding to 50 (0.05), 60 (0.06) and 125 (0.125) appear nowhere in any cell of that section sheet. Read only the cells and the duct section does not close. Some dimensions are parameters and some are drawing literals — and that distinction is declared nowhere in the workbook. Part 3 documents what happened when an instruction said "make the utility dimensions match Excel" and three of the four labels involved had no Excel cell to match.

3. A parameter name is not contained in a single cell. For lining and shotcrete, the column headers are thickness(t_) / overbreak(ob_) and the row labels are _lining / _shotcrete; the real names t_lining and ob_shotcrete have to be composed from header plus row label. The barrier is a 2D grid — rows barr_z4barr_z1, columns barr_y5barr_y1 — so identifying one value needs both names. And left/right is encoded three different ways at once: as a prefix on drawing labels (l_barr_y3 / r_barr_y3), inside the name itself in the human layout (left_utility_w), and by column in the mirror (AC = [Left] / AD = [Right]). The same value goes by three names. Some items are not even numeric — the start/end discriminator is a string enum.

4. The same value appears in up to three places. info / tunnel_info (source) → section sheet columns B–L (human) → section sheet AA–AD (mirror). Happily these are bound by formula, so they never disagree — I checked all of them. But the reader has to decide which representation is the contract, and if that decision is not written down, the next person reads a different one.

Stack 1 through 3 on top of 4 and a single question — "what is the left utility duct width for this section?" — has one candidate location in an image and three in cells.

That is why connecting data to geometry was not a mapping exercise. The workbook is a finished drawing set to a human, and to a machine it is a pile of numbers without coordinates and a pile of pictures without scale.

Twelve days in June: what actually got built

The narrative above is the shape of the problem. This section is the shape of the response, reconstructed from disk evidence alone — file timestamps, server logs and report contents — because no conversation log survives for this stretch. What follows describes what was made and what was checked, not what anybody asked for. Where a claim is an inference it is marked as one.

The survey covered every file in the project tree with a modification time between 18 June and 1 July: 9,760 files, reduced to 3,732 after removing browser profile seeds, Python caches and later archive copies.

DateFilesCharacterServer startsHTTP requests500s
06-1854Dissect the original .dyn; extract reference images000
06-19100Arc schema guard; first Dynamo CLI runtime attempt1660
06-201,868Full Excel dissection + registry generation141032
06-21184Viewer↔Dynamo schema sync; visual verification begins113214
06-2242Logs only — zero artifacts212000
06-23 – 06-250Gap
06-26131Vectorise the Excel images; user cutline introduced526368
06-27199Source cutline promoted to authority; structural trace role mapping3133411
06-28586Validation gates introduced en masse (498 JSON reports)162609
06-29568Gates converge to a full PASS (442 JSON reports)181997

06-18 — dissection, and the viewer's first contract

Everything created on the first day is reading and recording. Nothing is built: a geometry-flow analysis (10,095 B); a gap review (12,259 B); a 37,043-byte script that generates the next .dyn version programmatically; a node that reads the viewer's export back into Dynamo for comparison; three upstream dependency-tree dumps (35 KB / 36 KB / 24 KB); and the workbook's 15 embedded drawings, extracted.

The viewer notes written that day fix the contract that survived everything afterwards: the workbook as the data source, 33 T_sec_* sheets → 66 start/end frames, the three-tier source priority, and eleven named branches. The same document records three separate geometry rejections on that first day — a PDF-styled attempt, then a centre-based R123 attempt, then Excel-driven — and the conclusions it lands on are both negative: remove the synthetic Bezier side connection, and do not draw the outer arc as a forcibly closed profile. Two rejections before dinner on day one, both resolved by deleting something rather than adding something.

06-19 — the Dynamo runtime fails, and stays failed

An arc registry generated at 09:48 (129,413 B) extracts from the original graph: Arc 9, Intersect 12, Split 6, ByPatch 2, OffsetMany 2, ByLoft 11. Ten .dyn versions are saved between 08:14 and 11:21, and their names are the day's agenda — arc_source through runtime_sentinel, original_lineage_audit, lineage_watch_runtime_proof.

Then runtime verification fails. A CLI matrix with three variants and six load attempts leaves stdout files of about 82 bytes each. The verdict:

The Dynamo WPF CLI loaded v1_16 without stderr but did not evaluate it, and the expected sentinel JSON was never produced.

At 09:54 a validator is written and at 09:59 a manual execution checklist. That is the moment automated runtime proof is abandoned and handed to a human — and it is never recovered. Six weeks later the same gap is still open.

06-20 — the day the registry was built

1,868 files in one day — half of everything produced in June. Three phases.

Morning. Three .dyn versions saved simultaneously at 10:03; an oracle export of 35,991,540 bytes at 11:29; and at 11:52 a detail verifier whose check list is the first appearance of the dimensions that dominate Part 3 — Detail A stack 300 + 60 + 150 mm, lining-side offset 100 mm, D300 perforated pipe, PVC D100/D50 at 10.0 m centres, stage-6 fill 155 × 300 mm.

Its first run catches a viewer-side bug: a circle approximated with 54 samples missed the vertical start point, so the D300 pipe came out at 299.492 mm. Raising the sample count to 64 fixed it.

Midday. At 12:02:49 all 505 embedded images are extracted in one pass. At 12:19:52 the same 505 are re-placed under role names and the registry JSON is written: 1,677,115 bytes. Its headline counts are the ones quoted throughout this post — 39 sheets, 33 T_sec_*, 503 embedded images, 606 drawing items, 103 shapes — and it enforces five mandatory image roles per sheet. (The extractor counts 505 where the registry's headline says 503; the two anchors of difference are ones the registry does not classify. I have not reconciled them.)

The builder does not use a spreadsheet library: it opens the workbook as a zip and parses the package XML directly. That decision, made on the third working day, is why the same parsing logic could later be embedded inside a Python node running in Revit's bundled interpreter.

Afternoon and night.

VersionTimeWhat it added
v1_2512:34Node 00 reads the registry JSON
v1_2614:25ROAD_SURFACE / ROAD_LAYERS / SLAB / SIDEWALL / BARRIER / REINFORCEMENT / GROUTING / FOREPOLING branches
v1_2714:42The shared section schema file plus its two save endpoints
v1_2815:07Support cycles (R1 left/right, R2R3 left/right, R2'R3' left/right)
v1_29 – v1_33, v1_3615:41–17:24Slab lower contact, branch profile, Dynamo consumer contract, R23 slab contact anchor

A forensic detail: the server log shows /api/section-schema returning 404 between 14:30 and 14:34 — the endpoint being called before it existed — and v1_27, which introduces it, is saved at 14:42. The order of events is visible in the logs even though nobody wrote it down.

The counts fixed on this day did not change again: 33 sections, 66 frames, 34 unique station boundaries, GROUTING 42, FOREPOLING 66, REINFORCEMENT 390, support cycles active on 32 of 33 sections.

06-21 — the visual loop starts, and the .dyn line stops

Between 00:01 and 05:11, rendered comparison versions v110 through v179 pour out, and the filenames are the rejection history: slab_side_cleanutility_opening_focuscad_lineweight_cleanr23_contact_visiblesupport_barrier_audit.

Schema summaryTimeSizeBranches
v16904:3844,958 B39
v26614:08215,325 B61
v27115:14226,461 B63

v266 is a structural change, not an increment: it introduces section-geometry QA, six required branch groups, a first-frame anchor audit and an explicit Dynamo consumer contract. That is the moment the viewer's obligation to Dynamo became a document rather than an understanding.

And then, at 14:33 on 06-21, v1_39 is saved and the .dyn lineage stops. It never resumes in June. The branch count goes on to 83 and then 87 over the following week; the Dynamo side that is supposed to consume those branches is never updated. That is a real, unclosed gap, and one reason a runtime proof was never obtained.

06-22 — a day that left only logs

All 42 surviving files are server logs. No screenshots, no reports, no schema, no .dyn. Twenty-one server starts, 200 requests, zero 500s. The only evidence of what was done is 44 verify tokens in the URLs — among them drain-cradle, cad-dim-no-tags, orthogonal-slab-utility-contact, production-lines-no-tags.

Two themes appear here for the first time and never go away: separating production lines from diagnostic and tag lines, and forcing orthogonality. Both become formal gates six days later. And note drain-cradle — the object at the centre of Part 3's first case appears in a URL on 22 June, weeks before anyone questioned what it was.

06-26 — turning the pictures into lines

The busiest server day of the month: 52 starts. At 17:48 a user cutline trace overlay — a human draws the outline by hand and it becomes data. At 18:17 the linework extractor turns the PNG into segments. Measured output: left image 753 × 643 px, 74 segments (39 long), 69 geometry candidates; right image 761 × 636 px, 72 segments (39 long), 63 candidates.

At 23:06 the structural trace classifies each side into 15 production lines plus 1 reference line, and its description states the rule Part 3 is entirely about:

Text and dimension extension lines are excluded from production geometry.

Note the date. That principle was written down on 26 June. Part 3 documents three separate occasions after that date on which the same confusion was made again anyway — including once by a filter written specifically to prevent it.

Shortly after midnight, a full model dump of 174,674,788 bytes — the largest single artifact of the month.

06-27 — the hand-drawn outline becomes the authority

About twenty captures between 02:02 and 03:22, and the filenames are an argument being made: source_cutline_only_fallbackcutline_exact_onlyno_trim_proxyno_proxy_pointschema_source_locksubstitutes deleted one at a time until only the source outline is left.

At 03:43 the source cutline relation audit passes 132 side checks across 66 frames; at 03:50 the utility body opening branch audit passes 132 / 132. The fact fixed here — the source cutline is 22 points — holds through every later comparison. By 07:52 the schema summary is at v272 with 83 branches, up 20 from six days earlier.

06-28 — twelve gates in one day

498 JSON reports. Twelve new report families are created, and their first results are a catalogue of what was actually broken:

TimeFamilyFirst resultFirst failure mode
04:24parametric section relationships✗ 132Slab/R23 contact datum branch missing its lock lines
08:01parametric dimension bindings✗ 66Frame dimension keys missing
08:15dynamo consumer map✗ 112Required branch profile empty (FOREPOLING)
08:30dynamo profile payload✗ 330Polyline too short — length 1
13:45production profile payload✗ 112Production profile empty (FOREPOLING)
14:35excel source overlay contract✗ 924Missing dimension driver
15:45barrier dimensions1,320Barrier active-Y value mismatch
16:23production driver coverage✗ 396Production driver key missing
20:47viewer production display contract✗ 14Forbidden reference branch inside the production display

Most close the same night: driver coverage passes at 22:22, the barrier's 1,320 failures reach zero at 22:39, dimension bindings pass at 23:20. The scale explains why nothing here could be eyeballed — the first production-payload run covered 70 branches, 65,046 polylines, 444,450 points. At 17:04 the schema summary hits v424 with 87 branches and absorbs the results of fourteen validation families as fields inside itself. From that moment the viewer's state is the bundle of gate results.

06-29 — everything green, and one rule that outlived it

TimeFamilyFirst resultCore finding
00:22source production shape fidelity✗ 528Cutline, body and pocket match exactly (delta 0.0), but the opening's raw source delta is 109.27 mm and the drain's raw trace delta 220.19 mm
00:40utility control datum manifest✗ 264P18 / P19 / P20 / P21 formalised as control points
01:55production axis linework10 axis-aligned branches, 8,712 segments
02:05excel source dimension conflictsDimensions override the raw source
03:04orthogonality checkNo diagonal segments permitted
03:14slab tail R23 split✗ 132Missing source slab tail
07:17utility road slab datumroad_z_at(y) + t_pav + t_slab + bottom_t

The 02:05 rule is the heaviest thing decided in June:

When a traced dimension from an embedded Excel image disagrees with the Excel numeric label, production geometry follows the Excel numeric dimension, and the raw trace delta is retained for audit and reference only.

The measured consequence, in the same report: raw disagreement peaks at 109.27 mm on opening height and 178.38 mm on drain width, while the production dimension error is 4.5 × 10⁻¹³ mm on the opening and 0.0 mm on the drain. Both numbers are true simultaneously. That pairing — a perfect dimensional match sitting beside a large visual disagreement, neither hidden — is the fact Part 3 is built on.

Between 09:32 and 11:34 a save-and-reload loop runs seven times, each iteration identical: save the section schema, save the production profiles, reload the model (37 MB) — a formalised procedure by then, not a habit. At 11:00 the parametric relationships gate closes with its check list grown from 7 items to 11, the four additions all discovered in the previous 48 hours. At 13:45 the last four reports record pass and June ends.

What the tools say, in the order they were written

#CreatedToolProblem it was solving
106-26 18:17Excel linework extractorTurn a PNG into line segments at all
206-28 12:49Dimension-binding validatorCross-check that three containers' binding manifests agree with each other
306-28 16:34Driver-coverage validatorEvery non-empty branch must name its dimension drivers, and those keys must exist and be finite
406-28 20:28Barrier dimension validatorRebuild the barrier from Excel numbers to polyline coordinates and compare, 2.0 mm tolerance, 11-point stepped outline
506-29 03:04Orthogonality validatorOne question only: is any segment diagonal? Across 66 frames × 2 sides
606-29 03:31Normalised trace validatorAfter normalisation, does the trace still contain the reference line, and is the drain pocket closed?
706-29 11:33Display-contract rendererNot a validator — a display contract. Names the eight refs to draw and what to exclude

Read the order and you get the method: vectorise the picture (1) → prove dimensions are bound to geometry (2, 3) → rebuild members and compare (4) → enforce shape discipline (5, 6) → fix what may be shown at all (7). The last one is not a test. It is a decision about display, written as code, because by then it was clear that what appears on screen determines what gets believed.

What the server logs say

Eight days, 164 stderr logs = 164 server starts, 2,119 requests. GET /api/model accounts for 904 of them, mostly full rebuilds; the save endpoints total 132 calls, and that rhythm — change, save schema, save profiles, reload — is the skeleton of late June. Status codes: 200 = 1,840, 304 = 227, 500 = 41, 404 = 10. The 500s cluster on 06-27 (11), 06-28 (9), 06-26 (8) and 06-29 (7) — the server broke most often exactly while the source cutline was being made authoritative and the gates were being installed.

But the tracebacks are more interesting than the counts. Of 87 tracebacks, 82 are not code defects: 70 connection-aborted and 12 connection-reset, all of them the browser hanging up during a large /api/model response. The genuine code errors number four — three from getting the server's startup arguments in the wrong order, one unbound local, one undefined name. A month of gates and audits, and the actual crash log is four typos and a lot of impatient reloading. Meanwhile the cache-bust token in every log line matches its verify token: asset versioning was maintained without exception for the entire month.

What June fixed, and what it did not

DecisionFixed on
33 sheets / 66 frames / 34 station boundaries06-18 – 06-20
Source priority: original .dyn ① / Excel ② / CAD ③06-18
Excel origin registry (39 / 503 / 606 / 103, five mandatory roles)06-20 12:19
Shared section schema + save API06-20 14:42
Viewer → Dynamo consumer contract (6 branch groups)06-21 14:08
Branches 39 → 61 → 63 → 83 → 8706-21 → 06-28
The hand-marked 22-point cutline is the source authority06-27 03:43
Text and dimension lines excluded from production06-26 23:06
Excel numbers override the raw trace06-29 02:05
P18–P21 control points; drain pocket discipline (50 mm drop, 125 mm clearance)06-29
25 validation families all passing06-29 13:45

And the things June did not finish, which are more instructive:

  1. No Dynamo/Revit runtime proof. The runtime validation folder contains zero sentinel result files. Six manual checklists were written; no results exist for any of them. The documents end with the sentence Revit/Dynamo runtime execution is still required. Everything June produced is a web viewer and static validation. The actual Revit geometry was never verified, and this gap survives into August.
  2. The viewer's structural limits remain. It cannot execute Revit family instances, ProtoGeometry boolean or split operations, or DirectShape. Two branches are explicitly labelled Excel-dimension web proxies standing in for real family geometry.
  3. The structural trace comes from one section only. Two images from a single sheet, 15 production lines per side. The 66-frame gates apply that one section's trace to every frame.
  4. The .dyn line stopped at v1_39 on 06-21, while the branch count went on to 87 with nothing on the Dynamo side updated to consume it.
  5. v1_34 and v1_35 do not exist, and schema summaries are mostly missing (v169 → v266 → v267 → v271 → v272 → v373), so progress between 06-21 and 06-27 is not traceable. (Inference: numbers reserved, saves skipped. Not confirmed.)
  6. The barrier gate still contains an approximation. Its marker comparison uses a scale factor derived as barrier span divided by the sum of active Y values. It passes — but that scaling assumption is itself never tested.

What I could not verify

  • Steel rib specifications — zero hits in the workbook's shared strings. Not in the Type library, not in the section sheets.
  • Standard forepoling values — input cells exist but no definition block in Def_pattern_Type. All values are 0 on the first section, so I have no representative figure to offer.
  • Exactly which stretch of sidewall R2 versus R3 covers — inferable only from label position on the drawing; no explicit definition in any cell or document.
  • The original graph's runtime behaviour in Revit — static JSON analysis only. I never ran it. Nor did anyone else, in June or afterwards.
  • The precise meaning of half_section (1.04) — no cell comment, no drawing label, and zero uses in the original graph. Still open, and still blocking the bench-split work.
  • Which of the two identical tangent-solver Python nodes is the left side — they are the same text at nearly the same coordinates, and nothing distinguishes them.
  • What was actually produced on 06-22 — 42 server logs and 44 verify tokens, and no captures, reports, schemas or .dyn files at all. What was decided and what was rejected that day cannot be recovered.
  • Whether 06-23 to 06-25 and 06-30 were genuinely idle — only that this tree contains no files with those timestamps. Other drives were not checked.
  • What anyone asked for, in June — no conversation log survives. Filenames and verify tokens are the only proxy evidence, and they show what was made, not what was requested.
  • How the screen actually looked during the June gates — 06-28 produced 498 JSON reports and six PNGs. June's passes are JSON-based, and a wide stretch of them was never cross-checked against pixels.

What's next

Part 2 opens the graph itself: 159 code blocks, 137 numeric literals, 38 distinct values, and what it actually cost to find out where each one came from.


Tunnel automation series — nine parts.
Next: Dynamo Code Blocks and the Cost of 137 Hardcoded Values
This is Part 1. Each part links to the next.

댓글

이 블로그의 인기 게시물

Structural Analysis Workflow with Dynamo and Robot

Dynamo with the Gemini Vision API test(Nano Banana)

AU2024 Dynamo Sessions for AEC Automation Workflows