@@ -8,176 +8,249 @@ import time
88from helper import *
99import numpy as np
1010import 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
1212from 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
2019simulation_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
127167derivation 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+
129175production:
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
174240homomorphism:
175241
242+ # Internode segments become cylinders with length and radius
176243I(a,r,o) --> F(a,r)
244+ # Branch segments also become cylinders
177245S(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