Skip to content

Commit 3cd69d1

Browse files
author
Gemini
committed
Finished refactoring UFO.lpy
1 parent 558c885 commit 3cd69d1

File tree

3 files changed

+350
-136
lines changed

3 files changed

+350
-136
lines changed

examples/UFO/UFO.lpy

Lines changed: 207 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -8,176 +8,249 @@ import time
88
from helper import *
99
import numpy as np
1010
import random as rd
11-
from examples.UFO.UFOconfigs import basicwood_prototypes, Trunk, Branch, TertiaryBranch, Spur, UFOSimulationConfig
11+
from examples.UFO.UFOconfigs import basicwood_prototypes, Trunk, Branch, TertiaryBranch, Spur, UFOSimulationConfig, generate_points_ufo
1212
from dataclasses import dataclass
1313

1414

1515
#init
16-
trunk_base = Trunk(copy_from = basicwood_prototypes['trunk'])
17-
time_count = 0
16+
main_trunk = Trunk(copy_from = basicwood_prototypes['trunk'])
1817

1918
# Create simulation configuration
2019
simulation_config = UFOSimulationConfig()
2120

22-
def generate_points_ufo():
21+
22+
23+
trellis_support = Support(generate_points_ufo(), simulation_config.support_num_wires, simulation_config.support_spacing_wires, simulation_config.support_trunk_wire_point)
24+
tying_interval_iterations = simulation_config.num_iteration_tie
25+
pruning_interval_iterations = simulation_config.num_iteration_prune
26+
main_trunk.tying.guide_target = trellis_support.trunk_wire
27+
28+
def StartEach(lstring):
2329
"""
24-
Generate 3D points for the UFO trellis wire structure.
30+
Initialize tying updates for trunk and branches at the start of each iteration.
31+
32+
This function is called at the beginning of each L-System derivation iteration
33+
to prepare the tree structure for potential tying operations. It ensures that
34+
both the trunk and all child branches are ready to participate in the tying
35+
process by updating their tying status.
2536

26-
Creates a linear array of wire attachment points along the x-axis at a fixed
27-
height (z) and depth (y). The points are spaced evenly within the configured
28-
x-range and used to construct the trellis support structure.
37+
The function performs two main tasks:
38+
1. Updates the trunk's tying status if it has a wire target and hasn't been updated
39+
2. Updates all child branches' tying status if they haven't been updated
2940

41+
Args:
42+
lstring: The current L-System string (not used in this function but required
43+
by L-Py's callback interface)
44+
3045
Returns:
31-
list: List of (x, y, z) tuples representing wire attachment points,
32-
where all points share the same y and z coordinates.
33-
34-
Configuration parameters used:
35-
- ufo_x_range: Tuple (min_x, max_x) defining the range of x coordinates
36-
- ufo_x_spacing: Spacing between consecutive x coordinates
37-
- ufo_z_value: Fixed z-coordinate (height) for all points
38-
- ufo_y_value: Fixed y-coordinate (depth) for all points
46+
None: Modifies global tree structures in-place
47+
48+
Note:
49+
This function relies on global variables: branch_hierarchy, trellis_support, main_trunk.
50+
It should be called automatically by L-Py at the start of each iteration.
3951
"""
40-
x = np.arange(
41-
simulation_config.ufo_x_range[0],
42-
simulation_config.ufo_x_range[1],
43-
simulation_config.ufo_x_spacing
44-
).astype(float)
45-
z = np.full((x.shape[0],), simulation_config.ufo_z_value).astype(float)
46-
y = np.full((x.shape[0],), simulation_config.ufo_y_value).astype(float)
47-
48-
pts = []
49-
for i in range(x.shape[0]):
50-
pts.append((x[i], y[i], z[i]))
52+
global branch_hierarchy, trellis_support, main_trunk
5153

52-
return pts
54+
# Update trunk tying status if it has a wire target and needs updating
55+
if trellis_support.trunk_wire and not main_trunk.tying.tie_updated:
56+
main_trunk.tie_update()
5357

58+
# Update tying status for all child branches that need it
59+
for branch in branch_hierarchy[main_trunk.name]:
60+
if not branch.tying.tie_updated:
61+
branch.tie_update()
62+
5463

64+
def EndEach(lstring):
65+
"""
66+
Perform tying and pruning operations at the end of each L-System iteration.
5567

56-
support = Support(generate_points_ufo(), simulation_config.support_num_wires, simulation_config.support_spacing_wires, simulation_config.support_trunk_wire_point)
57-
num_iteration_tie = simulation_config.num_iteration_tie
58-
num_iteration_prune = simulation_config.num_iteration_prune
59-
trunk_base.tying.guide_target = support.trunk_wire
60-
###Tying stuff begins
61-
68+
This function is the main orchestration point for the tree training simulation.
69+
It executes tying operations (assigning branches to trellis wires) and pruning
70+
operations based on configurable iteration intervals. The tying process uses
71+
energy-based optimization to find optimal branch-to-wire assignments.
6272

63-
def tie(lstring):
64-
for j,i in enumerate(lstring):
65-
if (i == 'WoodStart' and hasattr(i[0].type, 'tying')) and getattr(i[0].type.tying, 'tie_axis', None) is not None:
66-
if i[0].type.tying.tie_updated == False:
67-
continue
68-
curr = i[0]
69-
if i[0].type.tying.guide_points:
70-
i[0].type.tying.tie_updated = False
71-
i[0].type.tying.guide_target.add_branch()
72-
lstring, count = i[0].type.tie_lstring(lstring, j)
73-
74-
return True
75-
return False
76-
73+
The function performs the following sequence when tying is scheduled:
74+
1. Updates the trunk's guide target (ties trunk first)
75+
2. Calculates energy matrix for all branch-wire combinations
76+
3. Uses greedy optimization to assign branches to lowest-energy wires
77+
4. Updates guide targets for all assigned branches
78+
5. Performs actual tying operations in the L-System string
79+
6. Prunes old branches if pruning iteration is reached
7780

78-
def StartEach(lstring):
79-
global parent_child_dict, support, trunk_base
80-
if support.trunk_wire and trunk_base.tying.tie_updated == False:
81-
trunk_base.tie_update()
81+
Args:
82+
lstring: The current L-System string containing modules and their parameters
8283

83-
for i in parent_child_dict[trunk_base.name]:
84-
if i.tying.tie_updated == False:
85-
i.tie_update()
86-
87-
88-
def EndEach(lstring):
89-
global parent_child_dict, support, num_iteration_tie
90-
91-
tied = False
92-
if (getIterationNb()+1)%num_iteration_tie == 0:
93-
94-
if support.trunk_wire :
95-
trunk_base.update_guide(trunk_base.tying.guide_target) #Tie trunk one iteration before branches
96-
energy_matrix = get_energy_mat(parent_child_dict[trunk_base.name], support, simulation_config)
97-
decide_guide(energy_matrix, parent_child_dict[trunk_base.name], support, simulation_config)
98-
for branch in parent_child_dict[trunk_base.name]:
99-
branch.update_guide(branch.tying.guide_target)
100-
while tie(lstring):
101-
pass
102-
if (getIterationNb() + 1) % num_iteration_prune == 0:
103-
while pruning_strategy(lstring): # Prune branches until no more can be pruned
104-
pass
105-
return lstring
106-
107-
def pruning_strategy(lstring): #Remove remnants of cut
108-
cut = False
109-
for j,i in enumerate(lstring):
110-
if i.name == 'WoodStart' and i[0].type.info.age > simulation_config.pruning_age_threshold and i[0].type.tying.has_tied == False and i[0].type.info.cut == False:
111-
i[0].type.info.cut = True
112-
lstring = cut_from(j, lstring)
113-
return True
114-
return False
115-
116-
parent_child_dict = {}
117-
parent_child_dict[trunk_base.name] = []
118-
label = simulation_config.label
119-
#Tie trunk
120-
module Attractors
121-
module grow_object
122-
module bud
123-
module branch
124-
module WoodStart
125-
curve = create_bezier_curve(x_range = (-1, 1), y_range = (-1, 1), z_range = (0, 10), seed_val=time.time())
126-
Axiom: Attractors(support)SetGuide(curve, trunk_base.growth.max_length)[@GcGetPos(trunk_base.location.start)WoodStart(ParameterSet(type = trunk_base))&(270)/(0)grow_object(trunk_base)GetPos(trunk_base.location.end)]
84+
Returns:
85+
The modified L-System string after tying and pruning operations
86+
87+
Note:
88+
This function relies on global variables and is called automatically by L-Py
89+
at the end of each derivation iteration. Tying occurs every tying_interval_iterations
90+
iterations, while pruning occurs every pruning_interval_iterations iterations.
91+
"""
92+
global branch_hierarchy, trellis_support, tying_interval_iterations
93+
94+
current_iteration = getIterationNb() + 1 #GetIterationNb is an L-Py function
95+
96+
# Check if this is a tying iteration
97+
if current_iteration % tying_interval_iterations == 0:
98+
# Tie trunk to its target wire first (one iteration before branches)
99+
if trellis_support.trunk_wire:
100+
main_trunk.update_guide(main_trunk.tying.guide_target)
101+
102+
# Calculate energy costs for optimal branch-to-wire assignments
103+
branches = branch_hierarchy[main_trunk.name]
104+
energy_matrix = get_energy_mat(branches, trellis_support, simulation_config)
105+
106+
# Perform greedy optimization to assign branches to wires
107+
decide_guide(energy_matrix, branches, trellis_support, simulation_config)
108+
109+
# Update guide targets for all assigned branches
110+
for branch in branches:
111+
branch.update_guide(branch.tying.guide_target)
112+
113+
# Execute tying operations in the L-System string
114+
while tie(lstring, simulation_config):
115+
pass # Continue until no more tying operations are possible
116+
117+
# Check if this is also a pruning iteration
118+
if current_iteration % pruning_interval_iterations == 0:
119+
# Prune branches until no more can be pruned
120+
while prune(lstring, simulation_config):
121+
pass
122+
123+
return lstring
124+
125+
126+
127+
branch_hierarchy = {}
128+
branch_hierarchy[main_trunk.name] = []
129+
enable_color_labeling = simulation_config.label
130+
# =============================================================================
131+
# L-SYSTEM GRAMMAR DEFINITION
132+
# =============================================================================
133+
# This section defines the formal grammar for the UFO tree growth simulation.
134+
# The L-System uses modules (symbols) to represent different plant components
135+
# and their growth behaviors.
136+
137+
# -----------------------------------------------------------------------------
138+
# MODULE DECLARATIONS
139+
# -----------------------------------------------------------------------------
140+
# Define the vocabulary of symbols used in the L-System grammar.
141+
# Each module represents a different type of plant component or operation.
142+
143+
module Attractors # Trellis support structure that guides branch growth
144+
module grow_object # Growing plant parts (trunk, branches, spurs) with length/thickness
145+
module bud # Dormant buds that can break to produce new branches
146+
module branch # Branch segments in the L-System string
147+
module WoodStart # Starting point of wood segments (used for tying operations)
148+
149+
# -----------------------------------------------------------------------------
150+
# GLOBAL L-SYSTEM PARAMETERS
151+
# -----------------------------------------------------------------------------
152+
# Create a growth guide curve for the initial trunk development
153+
trunk_guide_curve = create_bezier_curve(x_range = (-1, 1), y_range = (-1, 1), z_range = (0, 10), seed_val=time.time())
154+
155+
# -----------------------------------------------------------------------------
156+
# AXIOM (STARTING STRING)
157+
# -----------------------------------------------------------------------------
158+
# The initial L-System string that begins the simulation.
159+
# Starts with the trellis attractors, sets up the trunk guide curve,
160+
# and initializes the trunk growth with proper orientation.
161+
Axiom: Attractors(trellis_support)SetGuide(trunk_guide_curve, main_trunk.growth.max_length)[@GcGetPos(main_trunk.location.start)WoodStart(ParameterSet(type = main_trunk))&(270)/(0)grow_object(main_trunk)GetPos(main_trunk.location.end)]
162+
163+
# -----------------------------------------------------------------------------
164+
# DERIVATION PARAMETERS
165+
# -----------------------------------------------------------------------------
166+
# Set the maximum number of derivation steps for the L-System
127167
derivation length: simulation_config.derivation_length
128168

169+
# -----------------------------------------------------------------------------
170+
# PRODUCTION RULES
171+
# -----------------------------------------------------------------------------
172+
# Define how each module type evolves during each derivation step.
173+
# These rules control the growth, branching, and development of the tree.
174+
129175
production:
130-
#Decide whether branch internode vs trunk internode need to be the same size.
131-
grow_object(o) :
132-
if o == None:
176+
177+
# GROW_OBJECT PRODUCTION RULE
178+
# Handles the growth of plant segments (trunk, branches, spurs)
179+
# Determines whether to continue growing, stop, or produce buds
180+
grow_object(plant_segment) :
181+
if plant_segment == None:
182+
# Null object - terminate this branch
133183
produce *
134-
if o.length >= o.growth.max_length:
184+
if plant_segment.length >= plant_segment.growth.max_length:
185+
# Maximum length reached - stop growing this segment
135186
nproduce *
136187
else:
137-
nproduce SetContour(o.contour)
138-
o.grow_one()
139-
if label:
140-
r, g, b = o.info.color
188+
# Continue growing - update segment properties
189+
nproduce SetContour(plant_segment.contour)
190+
plant_segment.grow_one()
191+
if enable_color_labeling:
192+
# Add color visualization if labeling is enabled
193+
r, g, b = plant_segment.info.color
141194
nproduce SetColor(r,g,b)
142-
if 'Spur' in o.name:
143-
produce I(o.growth.growth_length, o.growth.thickness, o)bud(ParameterSet(type = o, num_buds = 0))@O(o.growth.thickness*simulation_config.thickness_multiplier)grow_object(o)
195+
if 'Spur' in plant_segment.name:
196+
# Spur branches: produce short shoots with terminal buds
197+
produce I(plant_segment.growth.growth_length, plant_segment.growth.thickness, plant_segment)bud(ParameterSet(type = plant_segment, num_buds = 0))@O(plant_segment.growth.thickness*simulation_config.thickness_multiplier)grow_object(plant_segment)
144198
else:
145-
nproduce I(o.growth.growth_length, o.growth.thickness, o)
146-
if np.isclose(o.info.age % o.bud_spacing_age, 0, atol=simulation_config.bud_age_tolerance):
147-
nproduce bud(ParameterSet(type = o, num_buds = 0))
148-
produce grow_object(o)
149-
#produce I(o.growth_length, o.thickness, o)bud(ParameterSet(type = o, num_buds = 0))grow_object(o)
150-
151-
bud(t) :
152-
if t.type.is_bud_break(t.num_buds):
153-
new_object = t.type.create_branch()
154-
if new_object == None:
199+
# Regular branches: produce internode segment
200+
nproduce I(plant_segment.growth.growth_length, plant_segment.growth.thickness, plant_segment)
201+
if np.isclose(plant_segment.info.age % plant_segment.bud_spacing_age, 0, atol=simulation_config.bud_age_tolerance):
202+
# Age-based bud production for lateral branching
203+
nproduce bud(ParameterSet(type = plant_segment, num_buds = 0))
204+
produce grow_object(plant_segment)
205+
206+
# BUD PRODUCTION RULE
207+
# Controls bud break and branch initiation
208+
# Determines when buds activate to produce new branches
209+
bud(bud_parameters) :
210+
if bud_parameters.type.is_bud_break(bud_parameters.num_buds):
211+
# Bud break condition met - create new branch
212+
new_branch = bud_parameters.type.create_branch()
213+
if new_branch == None:
214+
# Branch creation failed - terminate
155215
produce *
156-
parent_child_dict[new_object.name] = []
157-
parent_child_dict[t.type.name].append(new_object)
158-
#Store new object somewhere
159-
t.num_buds+=1
160-
t.type.info.num_branches+=1
216+
# Register new branch in parent-child relationship tracking
217+
branch_hierarchy[new_branch.name] = []
218+
branch_hierarchy[bud_parameters.type.name].append(new_branch)
219+
# Update branch counters
220+
bud_parameters.num_buds+=1
221+
bud_parameters.type.info.num_branches+=1
161222

162-
if hasattr(new_object, 'curve_x_range'):
223+
if hasattr(new_branch, 'curve_x_range'):
224+
# Curved branches: set up custom growth guide curve
163225
import time
164-
curve = create_bezier_curve(x_range=new_object.curve_x_range, y_range=new_object.curve_y_range, z_range=new_object.curve_z_range, seed_val=time.time())
165-
nproduce[@GcSetGuide(curve, new_object.growth.max_length)
226+
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_val=time.time())
227+
nproduce[@GcSetGuide(curve, new_branch.growth.max_length)
166228
else:
229+
# Straight branches: use default orientation
167230
nproduce [
168-
nproduce @RGetPos(new_object.location.start)WoodStart(ParameterSet(type = new_object))/(rd.random()*360)&(rd.random()*360)grow_object(new_object)GetPos(new_object.location.end)@Ge]bud(t)
169-
170-
171-
I(s,r,o) --> I(s,r+o.growth.thickness_increment, o)
172-
_(r) --> _(r+o.growth.thickness_increment)
231+
# Produce new branch with random orientation and growth object
232+
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)@Ge]bud(bud_parameters)
233+
234+
# -----------------------------------------------------------------------------
235+
# GEOMETRIC INTERPRETATION (HOMOMORPHISM)
236+
# -----------------------------------------------------------------------------
237+
# Map abstract L-System modules to concrete 3D geometry for rendering.
238+
# These rules define how the symbolic representation becomes visual.
173239

174240
homomorphism:
175241

242+
# Internode segments become cylinders with length and radius
176243
I(a,r,o) --> F(a,r)
244+
# Branch segments also become cylinders
177245
S(a,r,o) --> F(a,r)
178246

179-
production:
180-
Attractors(support):
181-
pttodisplay = support.attractor_grid.get_enabled_points()
182-
if len(pttodisplay) > 0:
183-
produce [,(3) @g(PointSet(pttodisplay,width=simulation_config.attractor_point_width))]
247+
# -----------------------------------------------------------------------------
248+
# ATTRACTOR VISUALIZATION
249+
# -----------------------------------------------------------------------------
250+
# Additional production rules for displaying trellis attractor points
251+
production:
252+
Attractors(trellis_support):
253+
# Display enabled attractor points as visual markers
254+
points_to_display = trellis_support.attractor_grid.get_enabled_points()
255+
if len(points_to_display) > 0:
256+
produce [,(3) @g(PointSet(points_to_display,width=simulation_config.attractor_point_width))]

0 commit comments

Comments
 (0)