Methods
lpy_treesim marries biologically inspired L-systems with rule-based care operations (tying, pruning, support placement) so you can synthesize orchard-ready tree geometries. This page explains how the major pieces interact.
Prototype-driven L-System
base_lpy.lpy is the only grammar we execute at runtime. Rather than handcode every rule, it reads extern variables that point to Python classes in examples/<TreeName>:
prototype_dict_pathImports the shared basicwood_prototypes mapping. Each entry contains a TreeBranch subclass plus its BasicWoodConfig, which exposes stochastic bending, bud spacing, lengths, and colors.
trunk_class_pathThe entry class that seeds the axiom. All other branches sprout from the prototypes configured on this trunk.
simulation_class_path/simulation_config_class_pathProvide training-system-specific behaviors (e.g., UFO trellis vs. Envy spindle). The config dataclass is serialized and handed to the runtime simulation via TreeSimulationBase.
During each derivation step the grammar delegates to your Python classes to decide when buds break, which prototype to spawn, and how to orient each new segment. Because everything happens inside Python, you can use NumPy, random sampling, and heuristics tailored to your target cultivar.
Tying, pruning, and supports
The base simulation loop interleaves three routines:
Derive: expand the L-system string using the prototype logic.
- Tie: after a configurable number of iterations (num_iteration_tie), the
simulation attaches branches to the virtual support wires generated by TreeSimulationBase.generate_points().
- Prune: after num_iteration_prune, branches that exceed thresholds (age,
curvature, or spacing) are removed. The decision functions live inside stochastic_tree.py and can be augmented via your prototypes.
All three phases reuse the ColorManager so instance labels remain stable even after pruning removes geometry.
Batch export pipeline
tree_generation/make_n_trees.py wraps the grammar in a CLI tool:
Builds a fresh Lsystem object per tree and injects the extern dictionary.
- Seeds Python’s RNG per tree so stochastic decisions differ while still being
reproducible when –rng-seed is provided.
Calls sceneInterpretation to convert the final string into a PlantGL scene.
- Writes the mesh using tree_generation.helpers.write, which emits ASCII PLY
with normals and color attributes.
- Dumps {namespace}_{tree_name}_{index:05d}_colors.json containing the mapping
from instance IDs to semantic roles (spur, branch, trunk, tie, etc.).
The naming scheme caps at 99,999 trees per run; start a new namespace if you need more.
Extending the system
To add a brand-new method (new pruning heuristic, new tie behavior), derive from the appropriate class in simulation_base.py or stochastic_tree.py and inject your class via the extern dictionary. Because all extern paths are strings, make_n_trees.py can consume any importable module without further code changes.
Hierarchy export and labeling
The generator can export both mesh geometry and per-instance/semantic labels. During derivation the base grammar collects color/material metadata which is available via ColorManager.export_mapping().
Outputs typically include:
*.ply: Mesh for each synthesized tree.*_metadata.json: Mapping from instance IDs/color codes to branch names.*_hierarchy.json: Parent-child branch hierarchy.
To force hierarchy export, set the appropriate flags in your simulation config (e.g., EnvySimulationConfig or UFOSimulationConfig) and pass an output directory:
python -m lpy_treesim.tree_generation.make_n_trees \
--tree_name Envy \
--num_trees 10 \
--output_dir ./dataset \
--rng-seed 42
Under the hood, each branch instance in basicwood_prototypes carries color and order metadata; the export step writes a hierarchy that maps branch names to their instances and materials so downstream tooling can reconstruct the tree.