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)
- Load
cfg/shapes.cfgandcfg/allgemein.cfg. - Create a new empty DXF document (R2018, millimeters).
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 viaNachbarIds, and captures their rotation, height, position and drehrichtung. This neighbor map drives almost all the geometric decisions downstream.- Row-by-row over the CSV: parse
TeileArt/TeileId/Planquadrat/Merkmale, resolve the block library file for thatTeileArtfromallgemein.cfg(cached per path so each library is read once), dispatch to a handler function namedhandle_<teileart normalized>(spaces/dots → underscores, lowercased — seearbeiten_mit_csv.normalize_func_name), and call it with aHandlerContext(lib/handler_context.py) bundlingmsp,teileid,merkmale,x,y,doc,lib_doc,symbols,strecken_nachbarn,config,config_allgemein. - 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(...)(inlib/Elemente/), - inspects
ctx.strecken_nachbarnto 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 viadoc.blocks.new(...)if that exact name doesn't already exist indoc.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,Planquadratcoordinate extraction,MerkmaleJSON 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— theHandlerContextdataclass-like container passed to every handler, plus acreate_context_from_rowfactory and rich__str__/to_dictfor debugging.utils.py—check_environment_var(reads a required env var or exits) andsetup_logger(timestamped file + console logger underPROJECT_LOG).
Configuration (cfg/)
allgemein.cfg— maps aTeileArt's family ([ILS],[Omniflo],[BT],[TEF]) to a block library filename underdata/block_libraries/; also holds[LOG]format settings. UnmappedTeileArtfalls back todata/blocks.dxf.shapes.cfg— one section per exactTeileArtstring (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/esoffsetfor Gefällestrecke,vario_abstandfor 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.