marc_256 wrote: Wed Jan 14, 2026 6:30 pm
hi Joubarbe,
- i'm working to startup my 3d cad/cam and gaming company in 2026.
- what are your specialtys in pb ?
- where in france region do you live ?
- me, i'm good in 2d/3d math and graphics, but i need someone to help me with
communication stuff, as server/website/...
greetings,
marc
i had done some project for the small company using purely purebasic + win32 Api , including the MS SQL daily production workflow program , data backup utility , CNC gcode generator , ZWCAD / InteliCAD dll to extent autolisp to SQL server , graphic edge detector c/convertor ,and many more
below is the code review of the CNC GCODE generator by Claude :
NC Code Converter
DXF → Fargo 8035 G-Code Converter — Code Review & Analysis
Version 1.4.8.7 (Engine 0439a) · PureBasic · Three-Module Architecture
1. Executive Summary
This codebase is a production Windows desktop application written in PureBasic that reads AutoCAD DXF files and produces G-code programs for a Unity Prima SL18×12 laser cutter controlled by the Fargo 8035 CNC controller. The project spans five years of iterative patches (Oct 2019 – Nov 2024), accumulating 8,377 lines across three source modules.
Overall quality is solid for a specialist industrial tool: the geometry engine is mathematically sound, the layer-to-power-preset mapping is well-designed, and the optimizer successfully reduces air-travel between cuts. The most significant concerns are architectural (global mutable state for G-code output, file path hard-coding, and absence of error recovery in the conversion pipeline) rather than algorithmic.
2. Architecture & Module Breakdown
2.1 Three-Module Structure
Module Lines Responsibility
Fargor_GCODE_1487.pb 2,594 Main application: GUI, event handling, user workflow orchestration
Engine_DXF0439a.pb 5,060 DXF parser, viewport renderer, G-code generator, optimizer
Geometry_Math_0120.pb 723 Pure math: polar/Cartesian conversion, arc geometry, CCW detection, DDA rasterizer
2.2 Data Flow
• User opens a .dxf file → EE_DXF_LOAD_FILE() parses it into a linked list of DXF_Entity_obj structures inside DXF_CLASS_OBJ.
• _DXF_Entity_PreProcess() removes arcs/circles below the laser’s minimum feature size and pre-computes start/end points for the optimizer.
• EE_NC_OPTIMIZE() reorders entities using a nearest-neighbor greedy algorithm to minimize laser head travel.
• EE_GCODE_Init() / EE_GCODE_ByLayer() / EE_GCODE_DEInit() walk the optimized pointer list and build a single GCODE$ string inside *hDXF.
• The string is written to a hard-coded path: C:\PIM\<PIM_number>.PIM
3. G-Code Generation Logic
3.1 Header & Footer
The header emitted by EE_GCODE_Init() is:
%<PIM> N,MX,
G92X0Y0
(CALL 9100) ; WOOD -or- (PLCR1=1000) / (CALL 9000) for STEEL
G90
The footer emitted by EE_GCODE_DEInit() is:
(CALL 9110) ; WOOD -or- (CALL 9010) for STEEL
G90G00Z0
M30
3.2 Power Subroutine Mapping (Layer → CALL)
Material Layer(s) Description Power ON / OFF G90 appended
WOOD 0 Marking 9300 / 9400 Yes
WOOD 1, 15–19 2-pass cut 9301 / 9401 Yes
WOOD 2, 10–14 2-pass pulse cut 9302 / 9402 Yes
WOOD 6, 20–34, 45–52 1.5pt border/fast 9306 / 9406 Yes
STEEL 4, 5 Inner cut 9304 / 9404 Yes
STEEL 59–61 Border cut 9305 / 9405 Yes
3.3 Entity-Type Handling
• LINE: CALL_LINE() performs a proximity check (0.14 mm threshold). If the laser head is already within 0.14 mm of the line start, the laser stays on and the move is suppressed (avoiding burn-delay spots). Otherwise it fires a G00 rapid, turns the laser on, then outputs G01.
• ARC: CALL_ARC() uses CCWArcPt() to determine direction and emits G02 (CW) or G03 (CCW) with I/J center offsets. A safety guard degrades very long-radius arcs (r > 30,000 mm or perimeter-vs-radius out of range) to G01 lines to prevent Fargo controller calculation errors.
• CIRCLE: CALL_CIRCLE() always starts the circle at its 3 o’clock point (cx+r, cy) and uses G02 with I = −r, J = 0.
• LWPOLYLINE: Iterates vertices; bulge=0 dispatches to CALL_LINE(), any other valid bulge goes to CALL_ArcBulge() which reconstructs the arc center via TriPtARC() before emitting G02/G03.
• POLYLINE: Handles legacy POLYLINE entities as sequential straight G01 moves.
• SPLINE: Code is present but fully commented out. SPLINEs are silently skipped.
• TEXT/MTEXT: Also skipped — text entities are parsed and rendered on-screen but not converted to G-code.
3.4 Coordinate Transformation
All output coordinates are normalized to a product-origin system: every point is shifted by −Product_MIN_Bound before output, ensuring the G-code starts at (0,0). An optional 90° clockwise rotation swaps (x,y) → (y,−x) and adjusts arc start/end angles by −90°.
4. Path Optimizer
EE_GCODE_OPTIMIZE_ByLayer() implements a greedy nearest-neighbor traveling-salesman heuristic. For each entity it finds the unvisited entity whose start or end point is closest to the current head position, sets the isReverse flag if the end point is closer, and builds an Optimized_ENTITY_ptr list of raw pointers. Key properties:
• O(n²) in entity count. For typical die-cut boards (hundreds of entities per layer) this is acceptable; for very large drawings it could be slow.
• Optimization runs once at load time and is re-triggered if the user edits or deletes objects (G_FLAG_EDITED guard).
• LWPOLYLINE entities are intentionally excluded from reversal because their internal vertex winding cannot be safely reversed without re-parsing.
5. Geometry Engine (Geometry_Math_0120.pb)
This module is clean and self-contained. Notable functions:
• CCWArcPt(): Resolves which direction (CCW/CW) a DXF arc sweeps by computing the angular span and wrapping correctly across 0°/360°. Returns actual start and end Cartesian points needed for I/J output.
• TriPtARC(): Computes circumscribed circle from three points using the perpendicular bisector method. Used by CALL_ArcBulge().
• ArcPerimeter(): Correctly handles the wrap-around case where endAngle < startAngle (e.g., arc spanning 350° → 10°).
• Arc_DDA_Host(): A digital differential analyzer for on-screen arc rendering. Not used in G-code generation.
• CheckCCW3pt(): Cross-product winding test used by the optimizer and 3pt/4pt conversion.
6. Identified Bugs & Issues
6.1 Critical
• [BUG-01] Hard-coded output path: EE_GCODE_DEInit() always writes to C:\PIM\<name>.PIM. If that directory does not exist, the save silently falls through to ProcedureReturn 0 (FAIL) with no mkdir attempt. A user on a machine without C:\PIM\ gets only an “ERROR SAVE FILE” message box with no remedy offered.
• [BUG-02] NC_X_out$ / NC_Y_out$ are module-level globals: These two strings carry the “last output coordinate” across layer calls. If EE_GCODE_ByLayer() is called for multiple layers in sequence, the final coordinate of layer N leaks into the initialization state of layer N+1, potentially suppressing the first G00 move of the next layer.
6.2 Medium Severity
• [BUG-03] _StrD trailing-zero strip has an off-by-one edge case: The loop runs NbDecimals+1 times and stops when it encounters a ‘.’. If the value is a whole number (e.g., 10.000), all decimal digits and the decimal point are stripped correctly, but the loop exit condition depends on seeing ‘.’ as the last character rather than checking that it was removed, which could produce “10” rather than “10.”. In practice this is harmless for G-code parsers but is a latent fragility.
• [BUG-04] LWPOLYLINE bulge boundary condition: The check IF bulge = 0 Or bulge > 0.9999999999 Or bulge < -1 routes bulge values of exactly +1 or -1 (which are valid 180° semicircles) to the LINE branch instead of the arc branch. The comment acknowledges this but marks it as intentional; on actual 180° polyline arcs this will produce a chord instead of a semicircle.
• [BUG-05] Layer string comparison ambiguity: A comment in the code itself flags: “Layer ‘5’ also comes here? because string comparison fault (CutLayer$ >= ‘45’ And CutLayer$ <= ‘51’)”. String-based range comparisons on numeric layer names are lexicographic, not numeric. Layer ‘50’ > ‘45’ lexicographically is correct, but ‘50’ > ‘5’ is also true (because ‘0’ < end of string), so layer ‘5’ falls into the 45–51 range accidentally. The code has a workaround checking LL=2, but this is fragile.
6.3 Minor
• [BUG-06] POLYLINE lastPoint update references uninitialized variable: After a POLYLINE loop, lastPoint is updated from xx/yy, but xx/yy are local variables that are only set if the POLYLINE had at least one NextElement(). If a POLYLINE entity has exactly one vertex (degenerate), xx/yy are uninitialized at the assignment.
• [BUG-07] Duplicate commented-out EE_GCODE_DEInit: Lines 3054–3082 contain a complete commented-out copy of the procedure. This should be removed to reduce maintenance confusion.
• [BUG-08] SPLINE silently ignored: No warning is emitted when a SPLINE entity is encountered. Users whose DXF contains splines will get incomplete G-code with no indication of what was dropped.
7. Strengths
• Mature patch history with detailed changelogs — every version entry is traceable to a specific real-world CNC behavior.
• The proximity-based laser on/off logic (0.14 mm hysteresis) directly addresses the burn-delay hole problem and is physically motivated.
• Arc fallback to LINE for extreme radius values prevents a class of CNC controller overflow errors that would otherwise cause dangerous random moves.
• Small arc/circle removal threshold (< 0.2 mm perimeter) is well-calibrated to the physical laser spot size.
• Geometry_Math module is clean, reusable, and free of side effects.
• The coordinate normalization ensures G-code always starts at machine origin regardless of where the drawing is positioned in CAD space.
8. Recommendations
8.1 High Priority
• Fix BUG-01: Replace the hard-coded path with a configurable output directory (already stored in shanyu_LC.cfg). Add CreateDirectory_() before writing.
• Fix BUG-02: Move NC_X_out$, NC_Y_out$, NC_G_out$ from module globals into EE_GCODE_ByLayer’s Protected scope, or explicitly reset them at the start of each layer call.
• Fix BUG-05: Convert all layer range checks from string comparison to Val(CutLayer$) numeric comparisons.
8.2 Medium Priority
• Warn on SPLINE/TEXT entities: Before the silent continue, emit a warning message or count so the user knows geometry was skipped.
• Fix BUG-04: Change the bulge check to Abs(bulge) >= 0.9999999999 to route semicircles to the arc path.
• Remove duplicate dead code: Delete the commented-out copy of EE_GCODE_DEInit.
8.3 Low Priority / Future
• SPLINE support: The scaffold and the NURBS library include are already present. Enabling B-spline to polyline tessellation would add support for a common DXF entity type.
• Multi-machine support: The Fargo 8060 stub (line 3561+) is present but empty. Factoring out the power-subroutine table into a configuration structure would make adding new machine profiles straightforward.
• Optimizer upgrade: Replace the O(n²) nearest-neighbor scan with a k-d tree lookup for large drawings (> 1,000 entities per layer).
9. Module Inventory Summary
Procedure / SUB Lines Notes
EE_GCODE_Init ~20 Writes program header, G90, CALL 9000/9100
EE_GCODE_ByLayer ~480 Layer dispatcher + all entity type branches
EE_GCODE_DEInit ~40 Writes footer, saves .PIM file
EE_GCODE_OPTIMIZE_ByLayer ~100 Nearest-neighbor optimizer per layer
EE_NC_OPTIMIZE ~60 Calls optimizer for every layer
CALL_LINE ~80 G00+G01 line output with power state machine
CALL_CIRCLE ~55 G02 full-circle output
CALL_ARC ~95 G02/G03 arc with CCW detection
CALL_ArcBulge ~70 Bulge-to-arc G02/G03 via TriPtARC
_DXF_Entity_PreProcess ~110 Small feature removal + start/end point pre-calc
_DXF_PhaseEntity ~280 DXF ENTITIES section parser
CCWArcPt (Geometry_Math) ~35 Arc direction and endpoint resolver
TriPtARC (Geometry_Math) ~55 Three-point circumscribed circle
ArcPerimeter (Geometry_Math) ~25 Arc perimeter with wrap-around handling