Part.Load to Part.Dwg
A detailed trace of how Part.Load(filename) reads a file and delivers a populated
Part.Dwg (Drawing2) to the caller.
The same pipeline handles .geo, .dxf, .dwg, .iges, and .fx files;
the path taken depends solely on the file extension.
Part.Load (filename) API layer Src/API/Part/Part.cs:44
└── FX.LoadPart (file) Core layer Src/Core/FX.cs:1246
├── FX.LoadDrawing (file) Src/Core/FX.cs:1010
│ ├── GEOReader.Load() Src/Core/IO/GeoReader.cs:68
│ ├── DXFReader.Load() Src/Core/IO/DXFReader.cs
│ └── IGESReader.Load2D() Src/Core/IO/IGS/IGSIn.cs
├── Part.LoadFX (file) Src/Core/Part/Part.cs:990
│ └── ZipArchive.GetObject("Data\Dwg")
└── new Part { Dwg = dwg }
└── Part.Dwg setter Src/Core/Part/Part.cs:94
1. Step 1 — API Entry Point (Part.Load)
File: Src/API/Part/Part.cs — line 44
public static Part Load (string filename) {
if (!filename.IsFile ())
throw new APIException (EResult.FileNotFound) { File = filename };
return LoadPart (() => FX.LoadPart (filename), filename);
}
Part.Load is a thin wrapper in the public API layer.
It verifies the file exists, then delegates to the internal FX.LoadPart via a lambda.
If FX.LoadPart returns null or throws, LoadPart converts the exception to an
APIException with the code InvalidFile (or VersionMismatch).
The returned internal Flux.Part object is wrapped in the public Flux.API.Part class before
being handed back to the caller.
2. Step 2 — Format Dispatch (FX.LoadPart)
File: Src/Core/FX.cs — line 1246
public static Part LoadPart (string file, Part.EFXLoadFlags fxFlags = Part.EFXLoadFlags.Full,
Dictionary<string, object> keys = null) {
if (!ExistsRead (file)) return null;
Part part = null; Model3 model = null;
var allKeys = MakeIOKeys; // fresh dictionary for this load
if (keys != null) foreach (var kv in keys) allKeys[kv.Key] = kv.Value;
Drawing2 dwg = LoadDrawing (file, allKeys); // ← try 2D first
if (dwg == null) {
model = LoadModel (file, allKeys); // ← try 3D surface/sheet metal
if (model == null) {
switch (ext) {
case ".fx": case ".fxdiag":
return Part.LoadFX (file, fxFlags); // ← native format
// .pdg, .3d, .geo2, .mesh … handled separately
}
}
}
part ??= new Part { Dwg = dwg }; // ← Drawing2 assigned here
part.Info.Unit = DefaultUnit;
Part.LoadSettingsFile (Path.ChangeExtension (file, ".settings"), allKeys);
part.SetInfo (allKeys); // ← copies keys → Part.Info
part.Info.Name = Path.GetFileNameWithoutExtension (file);
if (ext == ".geo") DoGeoBNCImport (file, part);
part.IsDirty = false;
return part;
}
The logic is:
| Extension | Path taken |
|---|---|
|
|
|
|
|
|
|
|
2.1. The IOKeys Dictionary
MakeIOKeys creates a Dictionary<string, object> that travels through the entire
load pipeline.
Loaders write metadata into it as they parse:
// GEOReader writes into it via LoadDrawingInfo():
allKeys["Thickness"] = 1.5;
allKeys["MatlName"] = "St37";
allKeys["Comments"] = "…";
allKeys["Author"] = "…";
After loading, part.SetInfo(allKeys) reads all these keys back into Part.Info:
part.SetInfo (allKeys);
// equivalent to:
part.Info.Thickness = (double)allKeys["Thickness"];
part.Info.MatlName = (string)allKeys["MatlName"];
// …and so on for every known key
This is the mechanism by which thickness and material stored inside a .geo or .dxf file
end up in Part.Info after loading.
3. Step 3 — 2D Drawing Loaders (FX.LoadDrawing)
File: Src/Core/FX.cs — line 1010
LoadDrawing routes to the correct reader based on the file extension and returns a
fully populated Drawing2, or null if the extension is not a 2D format.
public static Drawing2 LoadDrawing (string file, Dictionary<string, object> keys = null, …) {
string ext = Path.GetExtension (file).ToLower ();
// Return null immediately for non-2D extensions:
if (ext != ".geo" && ext != ".dxf" && ext != ".wzg" && ext != ".geox"
&& ext != ".igs" && ext != ".iges") return null;
using var stm = File.OpenRead (file);
Drawing2 dwg = null;
switch (ext) {
case ".geo": case ".wzg":
dwg = new GEOReader (stm, keys).Load ();
break;
case ".geox":
dwg = LoadGEOX (stm, keys); // ZIP with GEO + INI (pre-bent state)
break;
case ".dwg":
file = DWGtoDXF (file); // convert via ODA/Teigha first
dwg = new DXFReader (stm, keys).Load ()
.SetLayerUses (IO.LayerUses)
.CleanupLayerNames (false);
break;
case ".dxf":
dwg = new DXFReader (stm, keys).Load ()
.SetLayerUses (IO.LayerUses)
.CleanupLayerNames (false);
break;
case ".igs": case ".iges":
dwg = new IGESReader (stm).Load2D ();
// optional stitching pass if StitchThreshold > 0
break;
}
UpdateTextLayers (); // route text entities to correct layer
dwg.FitSmallSegsToArcOrLine (); // optional segment simplification
return dwg;
}
Two post-processing steps run on every Drawing2 regardless of format:
| Step | What it does |
|---|---|
|
Re-assigns all |
|
When |
3.1. 3a — GEO Loader (GEOReader)
File: Src/Core/IO/GeoReader.cs
GEOReader owns a Drawing2 field that it populates as it reads the file section by section.
readonly Drawing2 mDwg = new (); // created once at construction time (line 637)
readonly List<Point2> mPts = new (); // coordinate table indexed by point-id
The Load() method walks the file line by line looking for section markers:
public Drawing2 Load () {
for (;;) {
string s = GetLine (); if (s == "#~EOF") break;
if (s.StartsWith ("#~")) {
int n = s[2..].ToInt ();
switch (n) {
case 1: LoadDrawingData (); break; // version, scale
case 3: LoadPartData (); break; // comments
case 11: LoadDrawingInfo (); break; // material, thickness, …
case 30: LoadExtraKeys (); break; // misc key-value pairs
case 31: LoadPoints (); break; // build mPts[] table
case 33: StartContour (); break; // begin a pline contour
case 331:
case 32:
case 332: LoadSegments (); break; // add segs → mDwg
case 37: mBendData = GetBlock (6); break; // store raw bend data
case 371: LoadBendlines (); break; // build E2Bendline → mDwg
case 36: LoadBevelAndFormAttributes (); break;
case 38: LoadBendAttributes (); break;
}
}
}
// Post-processing:
if (!mScale.EQ (1)) mDwg.Entities.ForEach (a => a.XForm (Matrix2.Scaling (mScale)));
if (mRotateHorizontal) mDwg.RotateHorizontal (true);
if (mDwg.Bendlines.Any () && mDwg.Bendlines.All (a => a.SeqNo > 0))
mDwg.AreBendsSequenced = true;
AssignLayers ();
if (miDeleteOverlaps) mDwg.RemoveDuplicates ();
return mDwg;
}
3.1.1. Section-by-section: what ends up in Drawing2
| Section | Handler | What it does to Drawing2 / keys |
|---|---|---|
|
|
Reads version and sets |
|
|
Writes |
|
|
Writes |
|
|
Writes miscellaneous |
|
|
Builds the |
|
|
Notes whether the upcoming segments form a closed or open contour
( |
|
|
Reads |
|
(stores raw block) |
Saves the raw 6-line bend-data block in |
|
|
Uses |
3.1.2. Layer assignment (post-load)
After the file is fully parsed, AssignLayers() maps entity colours to semantic layer roles:
| Entity colour | Mapped to | Meaning |
|---|---|---|
Red ( |
|
Forming tool center-point |
Red (pline/other) |
|
Forming tool footprint |
Yellow (dotted line) |
|
Evaporation marking |
Yellow (solid) |
|
Laser marking |
Cyan ( |
|
Marking point |
Cyan (pline) |
|
Auxiliary geometry |
Other |
|
Standard cut geometry |
This colour-to-layer mapping is why a GEO file uses colour codes (0–7) to indicate
semantic intent, while internally Flux tracks intent via Layer2.EUse.
3.2. 3b — DXF Loader (DXFReader)
File: Src/Core/IO/DXFReader.cs
readonly Drawing2 mDwg = new (); // created at construction time
DXFReader.Load() scans the DXF ENTITIES section, parsing group codes into entity objects:
| DXF entity type | Created as |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Open segments from separate entities are stitched together automatically
during DXFReader.Load() according to the StitchThreshold key.
After DXFReader.Load() returns, LoadDrawing calls two chained methods:
dwg = new DXFReader (stm, keys).Load ()
.SetLayerUses (IO.LayerUses) // map layer names → EUse roles
.CleanupLayerNames (false); // deduplicate layer names
SetLayerUses walks Drawing2.Layers and assigns Layer2.EUse by matching layer
names against the configured LayerUse[] rules (exact name match first, then
wildcard/regex match, then default: Aux if name starts with _, else Std).
3.3. 3c — IGES 2D Loader
File: Src/Core/IO/IGS/IGSIn.cs
When loading a flat 2D outline from an IGES file (rare — only done if the IGES contains
no solids), IGESReader.Load2D() extracts curve entities and builds E2Pline objects.
An optional stitching pass is run based on the StitchThreshold key.
4. Step 4 — Native Format (.fx) Path (Part.LoadFX)
File: Src/Core/Part/Part.cs — line 1004
When FX.LoadPart encounters .fx (or .fxdiag, .fxlantek), it bypasses LoadDrawing
entirely and calls Part.LoadFX directly:
public static Part LoadFX (Stream stm, EFXLoadFlags flags) {
var zip = new ZipArchive (stm, ZipArchiveMode.Read);
Part part = new ();
part.Info = (PartInfo)zip.GetObject (@"Data\Info", part);
if (flags.HasFlag (EFXLoadFlags.Dwg))
part.Dwg = (Drawing2)zip.GetObject (@"Data\Dwg", part); // ← Dwg assigned
if (flags.HasFlag (EFXLoadFlags.Model))
part.Model = (Model3)zip.GetObject (@"Data\Model", part);
if (flags.HasFlag (EFXLoadFlags.FoldTech)) part.FoldTech = …;
if (flags.HasFlag (EFXLoadFlags.BendTech)) part.BendTech = …;
if (flags.HasFlag (EFXLoadFlags.CutTech)) part.CutTech = …;
part.IsDirty = false;
return part;
}
zip.GetObject(@"Data\Dwg", part) reads the ZIP entry Data\Dwg, strips the
4-byte AU wrapper header, then runs the AU binary deserializer to reconstruct a
Drawing2 object graph from scratch — no text parsing, no geometry building.
The resulting object is identical in structure to one built by GEOReader or DXFReader.
EFXLoadFlags controls which entries are loaded:
| Flag | Entry loaded |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
All of the above (default) |
|
|
5. Step 5 — Drawing2 Assignment (Part.Dwg setter)
File: Src/Core/Part/Part.cs — line 94
Both paths (2D loaders and FX deserialization) end with:
part.Dwg = dwg; // triggers the setter below
public Drawing2 Dwg {
get => mDwg;
set {
if (value == null) CutTech = null; // nulling Dwg also clears CutTech
// Disconnect old drawing
if (mDwg != null) {
mDwg.Owner = null;
mDwg.PUnwatch (OnSubPropChanged);
}
// Connect new drawing
if ((mDwg = value) != null) {
mDwg.Owner = this; // back-link: Drawing2 knows its Part
mDwg.PWatch (OnSubPropChanged); // Part observes Drawing2 changes
// Scan text entities for known robot/automation keys
foreach (var t in mDwg.Entities.OfType<E2Text> ()
.SelectMany (a => a.Text.Split ('\n'))) {
int n = t.IndexOf (':'); if (n == -1) continue;
string key = t[..n].ToUpper ().Trim ();
string kval = t[(n + 1)..].Trim ();
if (sKnownKeys.Contains (key)) Info.Dict[key] = kval;
}
}
}
}
Side effects when a Drawing2 is assigned:
| Side effect | Why |
|---|---|
|
Cut-tech references contours inside the drawing by identity. When the drawing is replaced, those references are invalid, so the existing cut-tech must be discarded. |
|
The drawing needs a back-reference to its part for certain operations (e.g. thickness lookup, material lookup, bend annotation). |
|
The part subscribes to drawing change events so it can clear cached values (bounds, area, etc.) when the geometry changes. |
E2Text scan → |
DXF files often encode part metadata in text entities using |
The known automation keys scanned from text entities:
static readonly HashSet<string> sKnownKeys = [
"GRIPPER", "SEPARATOR", "LIGHTTABLE", "PARTCONVEYOR", "PICKPALLETPOS",
"DROPPALLETPOS", "DROPPALLET", "DROPINCLINED", "DROPQTY", "DROPPITCH",
"DROPPART2SHIFT", "JAWGRIPPER", "GRIPPERPOSF", "GRIPPERPOSR",
BDepositExpert.JAWDROPSIMPLE,
];
6. Drawing2 After Load
Regardless of the source format, the resulting Drawing2 has the same structure:
Drawing2
│
├── Entities (AList<Entity2>)
│ ├── E2Pline[] closed or open contours (outer edge, holes, slots, arcs)
│ ├── E2Bendline[] bend marks (angle, radius, k-factor, punch/die names)
│ ├── E2Text[] text annotations
│ ├── E2Point[] point markers
│ ├── E2Spline[] spline curves (DXF only; may be discretized to E2Pline)
│ ├── E2Dimension[] dimension annotations (DXF only)
│ └── E2Insert[] block references (DXF only)
│
├── Layers (AList<Layer2>)
│ Each layer carries:
│ ├── Name string
│ ├── Color Color32
│ ├── LType ELType (Continuous, Dash, Dot, DashDot)
│ └── Use Layer2.EUse ← semantic role assigned after load
│
├── Blocks (List<Block2>) block definitions (DXF only)
│
├── FormInfos forming tool positions (GEO bevel/form geometry)
│
└── Owner → Part back-reference set by the Dwg setter
6.1. Layer2.EUse — Semantic Layer Roles
EUse is the most important property on a layer.
It is assigned after load and controls how the drawing is processed for CAM:
| EUse | Meaning |
|---|---|
|
Normal cut geometry — outer edge, holes, slots |
|
Auxiliary geometry — not cut, used for reference only |
|
Laser marking (engraving without cutting through) |
|
Evaporation marking (very shallow laser pass) |
|
Center point of a forming tool (GEO import) |
|
Footprint outline of a forming tool (GEO import) |
|
Generated cut-tooling paths (added by |
|
Informational text entities not to be cut |
|
Forced outer boundary (red-bordered nesting override in GEO) |
7. Complete Data Flow Summary
Part.Load (filename)
│ Src/API/Part/Part.cs:44
│ file-exists check → APIException if missing
▼
FX.LoadPart (file)
│ Src/Core/FX.cs:1246
│ creates allKeys (IOKeys dictionary)
│
├── [.geo / .dxf / .dwg / .igs]
│ FX.LoadDrawing (file, allKeys)
│ Src/Core/FX.cs:1010
│ │
│ ├── GEOReader (file, allKeys).Load ()
│ │ Src/Core/IO/GeoReader.cs:68
│ │ mDwg = new Drawing2()
│ │ LoadDrawingInfo() → allKeys["Thickness"], allKeys["MatlName"], …
│ │ LoadPoints() → mPts[] coordinate table
│ │ LoadSegments() → E2Pline, E2Text, E2Point → mDwg.Entities
│ │ LoadBendlines() → E2Bendline → mDwg.Entities
│ │ AssignLayers() → colour → Layer2.EUse mapping
│ │ return mDwg
│ │
│ ├── DXFReader (stm, allKeys).Load ()
│ │ Src/Core/IO/DXFReader.cs
│ │ mDwg = new Drawing2()
│ │ parse ENTITIES section group codes
│ │ LINE/ARC/CIRCLE → E2Pline → mDwg.Entities
│ │ TEXT/MTEXT → E2Text → mDwg.Entities
│ │ stitch open segments
│ │ .SetLayerUses() → layer-name → Layer2.EUse mapping
│ │ .CleanupLayerNames()
│ │ return mDwg
│ │
│ └── UpdateTextLayers() + FitSmallSegsToArcOrLine()
│ return dwg
│
│ part = new Part { Dwg = dwg } ← Part.Dwg setter fires
│ part.SetInfo (allKeys) ← allKeys → Part.Info.Thickness, MatlName, …
│ return part
│
└── [.fx]
Part.LoadFX (file, flags)
Src/Core/Part/Part.cs:990
zip = new ZipArchive (stm)
part.Info = zip.GetObject ("Data\Info") AU deserialize → PartInfo
part.Dwg = zip.GetObject ("Data\Dwg") AU deserialize → Drawing2
part.Model, BendTech, FoldTech, CutTech … (if flags allow)
part.IsDirty = false
return part
Part.Dwg setter (for both paths above)
Src/Core/Part/Part.cs:94
mDwg.Owner = this ← Drawing2 back-reference
mDwg.PWatch (…) ← Part subscribes to Drawing2 change events
scan E2Text → Info.Dict ← robot/automation keys extracted
8. Key Files
| File | Responsibility |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|