Files
plant2dxf/CLAUDE.md
T
2026-07-09 11:04:38 +02:00

8.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this project does

plant2dxf reads a RuleDesigner CSV export describing all components of a plant/conveyor system (Kreisel, Gefällestrecke, VarioFörderer, Omniflo, etc.) and generates a DXF drawing of the plant by placing and assembling DXF blocks from library files. Positions come from the CSV's Planquadrat column (X:<val> Y:<val>, in mm); component-specific properties come from the Merkmale JSON column; connections between components come from NachbarIds.

Full functional documentation (CSV format, config format, per-element-type handler behavior, ezdxf usage patterns) lives in doc/plant2dxf_dokumentation.md — read it before making non-trivial changes to handler logic, it is much more detailed than this file.

Commands

All commands are run via the .bat scripts in bin/ (Windows only; this project has no .sh counterparts). Scripts call setenv.bat first, which sets PROJECT, PROJECT_BIN, PROJECT_CFG, PROJECT_DOC, PROJECT_LIB, PROJECT_DATA, PROJECT_WORK, PROJECT_TEST, PROJECT_LOG and creates missing data/, work/, log/, testdata/ folders.

bin\install_py.bat        REM create .venv and pip install lib/requirements.txt (no-op if .venv exists)
bin\manage_interpreter.bat activate   REM activate .venv (used internally by other scripts)
bin\run_unittests.bat     REM run all *_tests.py under lib\Elemente via unittest discover
bin\plant2dxf.bat -f <csv-datei> [-c shapes.cfg] [-l blocks.dxf] [-o output.dxf] [-v]

run_unittests.bat runs python -m unittest discover -t "%PROJECT%" -s lib\Elemente -p "*_tests.py" from the project root — tests must be discovered from repo root or the lib.* imports break. To run a single test file/case directly (with venv already active):

python -m unittest lib.Elemente.Kreisel_tests -v
python -m unittest lib.Elemente.Kreisel_tests.TestKreiselModell.test_from_merkmale -v

Dependencies (lib/requirements.txt): ezdxf==1.4.1, svg.path==7.0, pydantic>=2.0.0, plus ruff and black (line-length 88, target py312 — see pyproject.toml).

Architecture

Processing pipeline (lib/plant2dxf.py)

  1. Load cfg/shapes.cfg and cfg/allgemein.cfg.
  2. Create a new empty DXF document (R2018, millimeters).
  3. arbeiten_mit_csv.get_nachbar_information(csv_path) does a full pre-pass over the CSV to build a neighbor map (strecken_nachbarn): for every Gefällestrecke/VarioFörderer it resolves which Kreisel/Eckrad/angetriebene Kurve/other VarioFörderer are attached via NachbarIds, and captures their rotation, height, position and drehrichtung. This neighbor map drives almost all the geometric decisions downstream.
  4. Row-by-row over the CSV: parse TeileArt/TeileId/Planquadrat/Merkmale, resolve the block library file for that TeileArt from allgemein.cfg (cached per path so each library is read once), dispatch to a handler function named handle_<teileart normalized> (spaces/dots → underscores, lowercased — see arbeiten_mit_csv.normalize_func_name), and call it with a HandlerContext (lib/handler_context.py) bundling msp, teileid, merkmale, x, y, doc, lib_doc, symbols, strecken_nachbarn, config, config_allgemein.
  5. Save the DXF to the output path.

Handler dispatch has special-cased overrides: any TeileArt starting with "Omniflo" or "TEF" is routed to handle_omniflo regardless of exact name, and "ILS 2.0 Kreisel*" variants all route to handle_ils_2_0_kreisel.

Handler functions (lib/plant2dxf.py)

One handle_* function per TeileArt, e.g. handle_ils_2_0_kreisel, handle_ils_2_0_gefaellestrecke, handle_ils_2_0_variofoerderer, handle_ils_2_0_eckrad, handle_ils_2_0_kurve_angetrieben, handle_bt___beladung/handle_bt___entladung, handle_omniflo. Each handler:

  • builds a Pydantic element object via <ElementClass>.from_merkmale(...) (in lib/Elemente/),
  • inspects ctx.strecken_nachbarn to determine how many/which neighbors it's connected to (0, 1, or 2 Kreisel/Eckrad, direct-to-Kreisel vs. via-Stahlband, connected to an angetriebene Kurve needing a Motor-/Umlenkstation),
  • builds a deterministic, parameter-encoded block name (e.g. Ils_2.0_Gefaellestrecke_{laenge}_{hoehe}_{hat_umlenk}_{hat_motor}_{tefkurve}_...) and only constructs the block geometry via doc.blocks.new(...) if that exact name doesn't already exist in doc.blocks — this is the project's block-deduplication/caching strategy, and reusing it correctly for any new variant is essential to avoid duplicate block definitions,
  • inserts the resulting block (or raw lines for simple cases) into the modelspace with msp.add_blockref(...).

This dispatch/handler structure — and especially the block-name-encodes-geometry-variant caching pattern — is the main thing to understand before adding a new TeileArt or modifying connection logic.

Element classes (lib/Elemente/)

Pydantic models with geometry/business logic, one per component type: Kreisel, Gefaellestrecke, VarioFoerderer, Angetriebene_Kurve, Eckrad, Bt_element, Omniflo. Each has a <Name>_tests.py sibling with unittest cases (discovered by run_unittests.bat). VarioFoerderer.py and Gefaellestrecke.py contain the most complex logic (motor/Umlenkstation placement, AS/ES element insertion, rotation across one/two connected Kreisel) — see doc/VarioFoerderer_Refactoring.md for the design rationale (constants, enums, dataclasses extracted from what was previously an 820-line method).

Helper modules (lib/)

  • arbeiten_mit_csv.py — CSV parsing, Planquadrat coordinate extraction, Merkmale JSON parsing, the neighbor-analysis pass (get_nachbar_information), TeileArt → handler-name normalization.
  • block_methoden.py — importing blocks from a library doc into the target doc, block rotation/mirroring (turn_two_blocks_left), reading insert color/layer from a library block, attribute helpers.
  • as_es_methoden.py — AS-/ES-Element placement logic for Gefällestrecke (these depend on CSV height-comparison fields and Kreisel connection topology — flagged in the docs as needing review if RuleDesigner's CSV format changes).
  • handler_context.py — the HandlerContext dataclass-like container passed to every handler, plus a create_context_from_row factory and rich __str__/to_dict for debugging.
  • utils.pycheck_environment_var (reads a required env var or exits) and setup_logger (timestamped file + console logger under PROJECT_LOG).

Configuration (cfg/)

  • allgemein.cfg — maps a TeileArt's family ([ILS], [Omniflo], [BT], [TEF]) to a block library filename under data/block_libraries/; also holds [LOG] format settings. Unmapped TeileArt falls back to data/blocks.dxf.
  • shapes.cfg — one section per exact TeileArt string (must match the CSV column value exactly), listing library block names to place (items), their offsets/rotations relative to the computed position, and type-specific parameters (e.g. asoffset/esoffset for Gefällestrecke, vario_abstand for VarioFörderer, winkel_* angle constants, Omniflo Sivas-number overrides).

Both are read fresh per run; there's no schema validation beyond what configparser and the handler code assume, so a TeileArt typo silently falls through to "no handler found" (logged as a warning, row skipped) rather than erroring.

Known fragility (documented in doc/plant2dxf_dokumentation.md)

The AS/ES-element and height-comparison logic in as_es_methoden.py, lib/plant2dxf.py (handle_ils_2_0_gefaellestrecke, handle_ils_2_0_variofoerderer), lib/Elemente/Gefaellestrecke.py (rotation_mit_zwei_verbunden), and lib/Elemente/VarioFoerderer.py (vario_verbuden_am_kreisel) all assume the current RuleDesigner CSV column set and semantics. If the CSV export format changes, these are the first places to check.

Directories not under version control

data/, work/, log/ are gitignored (generated/input working data, log files with timestamped names). testdata/ holds committed CSV+DXF fixture pairs used for manual/reference comparison, separate from the lib/Elemente/*_tests.py unittest suite.