Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,6 @@ cython_debug/

dataset*/
.DS_Store
GEMINI.md
.vscode/
__*
5 changes: 5 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""LPY Tree Simulation package."""

from .color_manager import ColorManager

__all__ = ["ColorManager"]
Binary file removed __pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file removed __pycache__/__init__.cpython-38.pyc
Binary file not shown.
Binary file removed __pycache__/__init__.cpython-39.pyc
Binary file not shown.
Binary file removed __pycache__/helper.cpython-312.pyc
Binary file not shown.
Binary file removed __pycache__/helper.cpython-38.pyc
Binary file not shown.
Binary file removed __pycache__/helper.cpython-39.pyc
Binary file not shown.
Binary file removed __pycache__/stochastic_tree.cpython-312.pyc
Binary file not shown.
Binary file removed __pycache__/stochastic_tree.cpython-38.pyc
Binary file not shown.
Binary file removed __pycache__/stochastic_tree.cpython-39.pyc
Binary file not shown.
253 changes: 253 additions & 0 deletions base_lpy.lpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
"""
Shared L-System driver for Envy, UFO, and future trellis architectures.
Configure via extern variables passed to Lsystem(..., variables).
"""
import os
import sys
import time
import numpy as np
import random as rd

# Ensure repository root is importable regardless of working directory
CURRENT_DIR = os.path.dirname(__file__)
if CURRENT_DIR not in sys.path:
sys.path.append(CURRENT_DIR)

from stochastic_tree import Support, BasicWood, TreeBranch, BasicWoodConfig
from helper import *
from dataclasses import dataclass

# -----------------------------------------------------------------------------
# EXTERNALLY CONFIGURABLE PATHS
# -----------------------------------------------------------------------------
# Override these externs via Lsystem(..., variables) to point at a specific
# architecture without editing this shared file.
extern(
color_manager = None,
prototype_dict_path = "examples.Envy.Envy_prototypes.basicwood_prototypes",
trunk_class_path = "examples.Envy.Envy_prototypes.Trunk",
simulation_config_class_path = "examples.Envy.Envy_simulation.EnvySimulationConfig",
simulation_class_path = "examples.Envy.Envy_simulation.EnvySimulation",
axiom_pitch = 0.0,
axiom_yaw = 0.0,
)# Resolve extern-provided dotted paths so the rest of the script can operate on
# concrete classes/functions exactly like the Envy/UFO drivers do.
basicwood_prototypes = resolve_attr(prototype_dict_path)
Trunk = resolve_attr(trunk_class_path)
SimulationConfigClass = resolve_attr(simulation_config_class_path)
SimulationClass = resolve_attr(simulation_class_path)

simulation_config = SimulationConfigClass()
simulation = SimulationClass(simulation_config)
main_trunk = Trunk(copy_from = basicwood_prototypes['trunk'])

# Build trellis geometry and cadence numbers up front so callbacks stay simple.
trellis_support = Support(
simulation.generate_points(),
simulation_config.support_num_wires,
simulation_config.support_spacing_wires,
simulation_config.support_trunk_wire_point,
)
tying_interval_iterations = simulation_config.num_iteration_tie
pruning_interval_iterations = simulation_config.num_iteration_prune
main_trunk.tying.guide_target = trellis_support.trunk_wire

# Track parent/child relationships for tying/pruning decisions.
branch_hierarchy = {main_trunk.name: []}
enable_semantic_labeling = simulation_config.semantic_label
enable_instance_labeling = simulation_config.instance_label
enable_per_cylinder_labeling = simulation_config.per_cylinder_label

if (enable_instance_labeling or enable_per_cylinder_labeling) and color_manager is None:
raise ValueError(
"Instance labeling is enabled but no color_manager extern was provided."
)


def _get_energy_mat(branches, arch, _config):
return simulation.get_energy_mat(branches, arch)


def _decide_guide(energy_matrix, branches, arch, _config):
return simulation.decide_guide(energy_matrix, branches, arch)


def _tie(lstring, _config):
return simulation.tie(lstring)


def _prune(lstring, _config):
return simulation.prune(lstring)

# L-Py callbacks delegate to the Python helpers so architectures share logic.
def StartEach(lstring):
"""Proxy to shared tying preparation logic."""
start_each_common(lstring, branch_hierarchy, trellis_support, main_trunk)


def EndEach(lstring):
"""Proxy to shared tying/pruning orchestration logic."""
return end_each_common(
lstring,
branch_hierarchy,
trellis_support,
tying_interval_iterations,
pruning_interval_iterations,
simulation_config,
main_trunk,
getIterationNb,
_get_energy_mat,
_decide_guide,
_tie,
_prune,
)
generalized_cylinder = getattr(simulation_config, "use_generalized_cylinder", False)
# =============================================================================
# L-SYSTEM GRAMMAR DEFINITION
# =============================================================================
# This section defines the formal grammar for the tree growth simulation.
# The L-System uses modules (symbols) to represent different plant components
# and their growth behaviors.

# -----------------------------------------------------------------------------
# MODULE DECLARATIONS
# -----------------------------------------------------------------------------
# Define the vocabulary of symbols used in the L-System grammar.
# Each module represents a different type of plant component or operation.
module Attractors # Trellis support structure that guides branch growth
module grow_object # Growing plant parts (trunk, branches, spurs) with length/thickness
module bud # Dormant buds that can break to produce new branches
module branch # Branch segments in the L-System string
module WoodStart # Starting point of wood segments (used for tying operations)

# -----------------------------------------------------------------------------
# GLOBAL L-SYSTEM PARAMETERS
# -----------------------------------------------------------------------------
# Create a growth guide curve for the initial trunk development
trunk_guide_curve = create_bezier_curve(x_range = (-1, 1), y_range = (-1, 1), z_range = (0, 10), seed_value=time.time())

# -----------------------------------------------------------------------------
# AXIOM (STARTING STRING)
# -----------------------------------------------------------------------------
# The initial L-System string that begins the simulation.
# Starts with the trellis attractors, sets up the trunk guide curve,
# and initializes the trunk growth with proper orientation.
Axiom: Attractors(trellis_support)SetGuide(trunk_guide_curve, main_trunk.growth.max_length)[@GcGetPos(main_trunk.location.start)WoodStart(ParameterSet(type = main_trunk))&(axiom_pitch)/(axiom_yaw)grow_object(main_trunk)GetPos(main_trunk.location.end)@Ge]

# -----------------------------------------------------------------------------
# DERIVATION PARAMETERS
# -----------------------------------------------------------------------------
# Set the maximum number of derivation steps for the L-System
derivation length: simulation_config.derivation_length

# -----------------------------------------------------------------------------
# PRODUCTION RULES
# -----------------------------------------------------------------------------
# Define how each module type evolves during each derivation step.
# These rules control the growth, branching, and development of the tree.

production:

# GROW_OBJECT PRODUCTION RULE
# Handles the growth of plant segments (trunk, branches, spurs)
# Determines whether to continue growing, stop, or produce buds
grow_object(plant_segment) :
if plant_segment == None:
# Null object - terminate this branch
produce *
if plant_segment.length >= plant_segment.growth.max_length:
# Maximum length reached - stop growing this segment
nproduce *
else:
# Continue growing - update segment properties
nproduce SetContour(plant_segment.contour)
#Update internal state of the plant segment
plant_segment.grow_one()
#Update physical representation
if enable_semantic_labeling:
# Add color visualization if labeling is enabled -- Can this be moved to the start of building the segment?
r, g, b = plant_segment.info.color
plant_part = plant_segment.name.split("_")[0] # Get the part type (e.g., "trunk", "branch")
color_manager.set_unique_color((r,g,b), plant_part) # Ensure part type has a color assig
nproduce SetColor(r,g,b)
if enable_instance_labeling:
# Instance-level labeling (unique color per branch instance)
r, g, b = color_manager.get_unique_color(plant_segment.name)
nproduce SetColor(r,g,b)

#Produce internode segments (n cylinders per growth step)
n_cylinders = int(plant_segment.growth.growth_length / plant_segment.growth.cylinder_length)
for i in range(n_cylinders):
if enable_per_cylinder_labeling:
r, g, b = color_manager.get_unique_color(plant_segment.name, if_exists=False)
nproduce SetColor(r,g,b)
nproduce I(plant_segment.growth.cylinder_length, plant_segment.growth.thickness, plant_segment)
#Produce bud (after all cylinders in this growth step)
if plant_segment.pre_bud_rule(plant_segment, simulation_config):
for generated in plant_segment.post_bud_rule(plant_segment, simulation_config):
nproduce new(generated[0], *generated[1])

if should_bud(plant_segment, simulation_config):
# Age-based bud production for lateral branching
nproduce bud(ParameterSet(type = plant_segment, num_buds = 0))

if plant_segment.post_bud_rule(plant_segment, simulation_config):
for generated in plant_segment.post_bud_rule(plant_segment, simulation_config):
nproduce new(generated[0], *generated[1])

produce grow_object(plant_segment)

# BUD PRODUCTION RULE
# Controls bud break and branch initiation
# Determines when buds activate to produce new branches
bud(bud_parameters) :
if bud_parameters.type.is_bud_break(bud_parameters.num_buds):
# Bud break condition met - create new branch

new_branch = bud_parameters.type.create_branch()
if new_branch == None:
# Branch creation failed - terminate
produce *
# Register new branch in parent-child relationship tracking
branch_hierarchy[new_branch.name] = []
branch_hierarchy[bud_parameters.type.name].append(new_branch)
# Update branch counters
bud_parameters.num_buds+=1
bud_parameters.type.info.num_branches+=1

nproduce [
if generalized_cylinder:
nproduce @Gc
if hasattr(new_branch, 'curve_x_range'):
# Curved branches: set up custom growth guide curve
curve = create_bezier_curve(x_range=new_branch.curve_x_range, y_range=new_branch.curve_y_range, z_range=new_branch.curve_z_range, seed_value=rd.randint(0,1000))
nproduceSetGuide(curve, new_branch.growth.max_length)
# Produce new branch with random orientation and growth object
nproduce @RGetPos(new_branch.location.start)WoodStart(ParameterSet(type = new_branch))/(rd.random()*360)&(rd.random()*360)grow_object(new_branch)GetPos(new_branch.location.end)
if generalized_cylinder:
nproduce @Ge
produce ]bud(bud_parameters)

# -----------------------------------------------------------------------------
# GEOMETRIC INTERPRETATION (HOMOMORPHISM)
# -----------------------------------------------------------------------------
# Map abstract L-System modules to concrete 3D geometry for rendering.
# These rules define how the symbolic representation becomes visual.
homomorphism:

# Internode segments become cylinders with length and radius
I(a,r,o) --> F(a,r)
# Branch segments also become cylinders
S(a,r,o) --> F(a,r)

# -----------------------------------------------------------------------------
# ATTRACTOR VISUALIZATION
# -----------------------------------------------------------------------------
# Additional production rules for displaying trellis attractor points
production:
Attractors(trellis_support):
# Display enabled attractor points as visual markers
points_to_display = trellis_support.attractor_grid.get_enabled_points()
if len(points_to_display) > 0:
produce [,(3) @g(PointSet(points_to_display,width=simulation_config.attractor_point_width))]

Loading