[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Use improved L-systems to draw more fractals in Python

[A fern drawn in Python by using an L-system]

The post Use L-systems to draw fractals in Python explained how to use L-systems in Python to draw fractals. Often L-systems also include the [ and ] characters. Then the program is drawing an L-system script and it reaches the [ character, it pushes the current position and direction onto a stack. When it reaches a ] character, the program pops those values off of the stack. This allows the fractal to return to a previously saved position.

To allow the program to have a little more flexibility, I also added a length scale that the program can use to make each segment shorter than the previous one. This is mostly useful for trees, but it sometimes produces some interesting results for other curves.

I also added an initial direction for the fractal so you can make trees and ferns start pointing upward. You can probably achieve a similar result by beginning the axiom with some - or + characters, but this is slightly cleaner.

get_l_system_paths

The following code shows the new get_l_system_paths method that generates the fractal's points.

def get_l_system_paths(depth, theta, axiom, ignore, rules, dtheta, initial_theta, length_scale): '''Apply an L-system.''' # Build the final script. script = axiom for i in range(depth): new_script = '' for ch in script: if ch in rules: new_script += rules[ch] else: new_script += ch script = new_script # print(script) # Create the points. p1 = (0, 0) length = 100 paths = [] # A list of paths path = [p1] # The current path theta = initial_theta waypoints = [] # Places we will go back to wayangles = [] # Angles for the waypoints waylengths = [] for ch in script: if ch in ignore: pass elif ch == '-': theta -= dtheta elif ch == '+': theta += dtheta elif ch == '[': waypoints.append(path[-1]) wayangles.append(theta) waylengths.append(length) elif ch == ']': if len(path) > 1: paths.append(path) p1 = waypoints.pop() path = [p1] theta = wayangles.pop() length = waylengths.pop() else: x = p1[0] + length * math.cos(theta) y = p1[1] + length * math.sin(theta) p1 = (x, y) path.append(p1) length *= length_scale if len(path) > 1: paths.append(path) # Return the points. return paths

The method starts much as before, using the L-system's axiom and rules to build the final script. Things get more interesting when it's time to generate the fractal's points.

Because the fractal can now jump back to a previously saved location, the result cannot be a single polyline. To handle that, the method creates a paths list to hold one or more paths. (The fractals drawn with the previous example still work, they just use one path.)

The code also creates the waypoints, wayangles, and waylengths lists to hold saved positions.

Next, the code loops through the script's characters as before. It's very similar to the previous version except when it reaches a [ or ] character.

When the code reaches a [ character, it saves the current point, angle, and length in the waypoint lists.

When the code reaches a ] character, it first checks the current path. If that path contains at least two points, the method adds that path to the paths list. The code then pops the position, angle, and length values off of their lists and restores them. It also adds the restored point to a new path.

If the code encounters a character that isn't in the ignore list (see the previous example) and isn't one of the special characters -, +, [, or ], the code draws a segment. It then multiples the current length by the length scale factor to shorten the next segment if desired. (Just set that parameter to 1 if you don't want to scale the segments.)

After it has processed the script, the method again checks the length of the current path and, if it contains at least two points, the code adds it to the paths list.

Finally, the method returns the paths.

Other Changes

The program requires a couple of other changes. First, the draw method needs to be modified slightly to draw multiple paths. Second, the fit_curves_to_canvas method must be changed to fit a group of paths to the canvas instead of working with a single path. Those changes are relatively straightforward so they aren't shown here. Download the example to see the details.

Conclusion

[A tree drawn in Python by using an L-system] Including the [ and ] characters lets L-systems save a position, draw, and then return to the saved position. Probably the simplest use of this is the following system, which draws the tree shown on the right.

    Rule:F: F[-F][+F]
    Axiom:F
    Dtheta:90°
    Initial Angle:45°
    Length Scale:0.75

The single rule basically means: Draw forward and save the position. Then turn left, recursively draw another level of the fractal, and return to the saved position. Then turn right, recursively draw another level of the fractal, and return to the saved position.

[A fern drawn in Python by using an L-system] The fern fractal shown on the right is generated by this system:

    Rule:X: F+[[X]-X]-F[-FX]+X
    Rule:F: FF
    Axiom:X
    Ignore:X
    Dtheta:-25°
    Initial Angle:-50°
    Length Scale:1
You can read more about this fern on Wikipedia.

Download the example to see additional details and to experiment with the program.

© 2024 Rocky Mountain Computer Consulting, Inc. All rights reserved.