Artistic Alchemy

Objective: Artistic Alchemy

Welcome to Artistic Alchemy: Combining Transformations!

Our objective is to create a Python script that defines a function draw_transformed_element(t, x_offset, y_offset, scale_factor, rotation_angle) which draws a simple base shape (e.g., a square of base size 20). The main part of the script must call this function at least 5 times in a loop. In each iteration, you should vary the x_offset, y_offset, scale_factor, and rotation_angle using arithmetic progressions or direct calculations based on the loop counter (e.g., x_offset = i * 30, scale_factor = 1 + i * 0.2, rotation_angle = i * 15) to create a visually interesting pattern of translated, scaled, and rotated elements.

This micro-quest focuses on combining translation, scaling, and rotation transformations, by systematically varying function parameters within a loop, to create dynamic generative patterns.

Check Spec: See the Failing Tests

Before we start coding, let's run the tests to see the current state of the project and understand the engineering specification we need to meet.

Open your terminal, navigate to the project directory, and run pytest.

You should see output indicating that tests are failing because the functions are not yet implemented.

Test Results:

  • test_draw_transformed_element_logic
  • test_generate_artistic_pattern_calls_draw_element_correctly
  • test_generate_artistic_pattern_default_elements

Implement: main.py

Now, let's implement the solution by following the TODO comments in the main.py file.

We will focus on completing the draw_transformed_element and generate_artistic_pattern functions.

Step by step checklist:

  1. Inside draw_transformed_element, prepare the turtle for drawing at the target location and orientation by lifting the pen, moving to the specified offset coordinates, setting the absolute orientation, and then lowering the pen.
  2. Inside draw_transformed_element, calculate the actual side length for the square based on the global BASE_SIZE and the provided scale_factor.
  3. Inside draw_transformed_element, implement the logic to draw a square using the calculated side length. Remember a square has four equal sides and requires 90-degree turns.
  4. Inside draw_transformed_element, ensure the turtle's pen is lifted after the square is drawn.
  5. Inside generate_artistic_pattern, create a loop that iterates the specified number of times (using num_elements).
  6. Inside the loop in generate_artistic_pattern, calculate the x_offset for the current element based on the loop counter i (e.g., i * 30).
  7. Inside the loop in generate_artistic_pattern, calculate the y_offset for the current element based on the loop counter i (e.g., i * 20).
  8. Inside the loop in generate_artistic_pattern, calculate the scale_factor for the current element based on the loop counter i (e.g., 1 + i * 0.2).
  9. Inside the loop in generate_artistic_pattern, calculate the rotation_angle for the current element based on the loop counter i (e.g., i * 15).
  10. Inside the loop in generate_artistic_pattern, call the draw_transformed_element function, passing the turtle object and the calculated parameters for the current element.

The following documentation sections are going to be helpful:

  • Controlling Turtle's Direction: Absolute Headings
  • Moving the Turtle: Translation
  • Resizing Shapes: Scaling
  • Building Blocks: Functions
  • Artistic Alchemy: Combining Transformations
  • Quick Reference: Key Turtle & Math Functions

Validate: Run the Tests Again

You've implemented the required functions! Now, let's run the tests again to validate your work and ensure you've met all the specifications.

Open your terminal, navigate to the project directory, and run pytest.

If all tests pass, you've successfully completed the coding portion of this micro-quest!

Test Results:

  • test_draw_transformed_element_logic
  • test_generate_artistic_pattern_calls_draw_element_correctly
  • test_generate_artistic_pattern_default_elements All tests passed!

Debrief: Prepare for Code Review

Congratulations on passing the tests!

You have successfully implemented the logic to draw transformed elements and generate an artistic pattern by combining translation, scaling, and rotation within a loop.

Now, it's time to prepare for your code review with a mentor. Be ready to discuss:

  • How you used the draw_transformed_element function to encapsulate the drawing logic for a single element.
  • How you used the loop in generate_artistic_pattern to systematically vary the transformation parameters.
  • How the combination of translation, scaling, and rotation parameters creates the final visual pattern.
  • Any challenges you faced and how you overcame them.

Good luck with your code review!

Documentation

Python Turtle & Generative Art: Key Concepts

This document provides a quick reference for the Python concepts and turtle library commands used in the Generative Techniques blueprint.

1. Controlling Turtle's Direction: Absolute Headings

The turtle's heading determines the direction it faces.

  • turtle.setheading(angle): Sets the turtle's orientation to an absolute angle.
    • 0 degrees: East (right)
    • 90 degrees: North (up)
    • 180 degrees: West (left)
    • 270 degrees: South (down) This is different from turtle.left() or turtle.right(), which turn the turtle relative to its current direction.
import turtle
t = turtle.Turtle()

# Face East and draw
t.setheading(0)
t.forward(50)

# Face North and draw
t.setheading(90)
t.forward(50)

# Face West and draw
t.setheading(180)
t.forward(50)

Diagram showing a turtle at the origin with arrows indicating 0, 90, 180, 270 degrees and their corresponding directions (East, North, West, South).

2. Moving the Turtle: Translation

Translation involves moving a shape from one position to another without changing its orientation or size.

  • turtle.penup(): Lifts the pen, so the turtle moves without drawing.
  • turtle.goto(x, y): Moves the turtle to an absolute position (x, y) on the screen. The center of the screen is (0,0).
  • turtle.pendown(): Puts the pen down, so the turtle draws when it moves.
  • turtle.home(): Moves the turtle to the origin (0,0) and sets its heading to 0 degrees (East). Useful for resetting position.
import turtle
t = turtle.Turtle()

def draw_square(size):
    for _ in range(4):
        t.forward(size)
        t.left(90)

# Draw a square at the origin
draw_square(50)

# Translate and draw another square
t.penup()
t.goto(100, 50) # Move to new position
t.pendown()
draw_square(50)

# Reset position and orientation
t.penup()
t.home()
t.pendown()

GIF showing a square being drawn, then the turtle (pen up) moving to a new location, and an identical square being drawn there.

3. Resizing Shapes: Scaling

Scaling changes the size of a shape. This can be achieved by passing a size-modifying parameter to a drawing function.

  • Define a drawing function that accepts a size parameter.
  • Call the function with different size values to draw scaled versions of the shape.
import turtle
t = turtle.Turtle()

def draw_triangle(side_length):
    for _ in range(3): # Equilateral triangle
        t.forward(side_length)
        t.left(120)

# Draw a small triangle
draw_triangle(50)

# Reset position (optional, for clarity if drawing from same origin)
t.penup()
t.home() # Go to (0,0), face East
t.pendown()

# Draw a larger triangle
draw_triangle(100)

Image showing two equilateral triangles, one small and one large, both drawn from the same origin point (0,0). The smaller triangle is inside the larger one.

4. Rotation

4.1. Basic Rotation with Turtle Heading

For simple rotations where the shape is drawn relative to the turtle's current orientation:

  • Use turtle.setheading(angle) to set the turtle's absolute orientation before drawing.
  • Alternatively, use turtle.left(angle) or turtle.right(angle) for relative turns.
import turtle
t = turtle.Turtle()

def draw_rectangle(width, height):
    for _ in range(2):
        t.forward(width)
        t.left(90)
        t.forward(height)
        t.left(90)

# Draw a rectangle
t.setheading(0) # Base along x-axis (East)
draw_rectangle(100, 50)

# Reset, rotate, and draw again
t.penup()
t.goto(0,0)
t.pendown()
t.setheading(270) # Rotated 90 degrees clockwise (or -90)
draw_rectangle(100, 50)

Image showing a rectangle drawn horizontally (heading 0°), and an identical rectangle rotated 90 degrees clockwise (heading 270°), both originating from the same point.

4.2. Advanced Rotation with Trigonometry

For precise rotation of points around an origin (typically (0,0)):

  • Import the math module: import math
  • Convert degrees to radians: math.radians(degrees) (Trigonometric functions in math use radians).
  • Use math.cos(radians) and math.sin(radians) to calculate coordinates.
  • Rotation Formulas (for counter-clockwise rotation of point (x, y) around origin (0,0) by angle_rad):
    • new_x = x * math.cos(angle_rad) - y * math.sin(angle_rad)
    • new_y = x * math.sin(angle_rad) + y * math.cos(angle_rad)
import math

# Original point
x1, y1 = 100, 0
angle_deg = 60

# Convert angle to radians
angle_rad = math.radians(angle_deg)

# Calculate new coordinates
x2 = x1 * math.cos(angle_rad) - y1 * math.sin(angle_rad)
y2 = x1 * math.sin(angle_rad) + y1 * math.cos(angle_rad)

print(f"Original: ({x1}, {y1})")
print(f"Rotated by {angle_deg} degrees: ({x2}, {y2})")

Diagram showing a 2D coordinate plane with a point P and its rotation P' around the origin, illustrating the trigonometric relationship.

5. Building Blocks: Functions

Functions help organize code, make it reusable, and improve readability.

  • Defining a function:
    def function_name(parameter1, parameter2):
        # code block
        # ...
        return result # Optional: sends a value back
    
  • Functions that return values: Use the return statement to send a result back to the part of the code that called the function.
  • Calling a function: Use the function name followed by parentheses (), providing arguments if the function has parameters.
def calculate_area(length, width):
    """Calculates the area of a rectangle."""
    area = length * width
    return area # Send the calculated area back

def demonstrate_calculation():
    rect_length = 8
    rect_width = 5
    
    # Call the function and store the returned value
    calculated_area = calculate_area(rect_length, rect_width)
    
    # Use the returned value
    print(f"The area is: {calculated_area}") # Output: The area is: 40

6. Artistic Alchemy: Combining Transformations

Combine translation, scaling, and rotation within loops to create complex and interesting patterns.

  1. Create a drawing function for a base element, accepting transformation parameters (offset x, offset y, scale factor, rotation angle).

    # Example: BASE_SIZE = 20
    def draw_transformed_square(t, x_offset, y_offset, scale_factor, rotation_angle):
        t.penup()
        t.goto(x_offset, y_offset) # Translation
        t.setheading(rotation_angle) # Rotation
        t.pendown()
        
        side_length = BASE_SIZE * scale_factor # Scaling
        for _ in range(4):
            t.forward(side_length)
            t.left(90)
        t.penup()
    
  2. Use a loop to call this function multiple times, varying the parameters systematically based on the loop counter.

    import turtle
    # Assume draw_transformed_square and BASE_SIZE are defined
    
    pen = turtle.Turtle()
    pen.speed(0) # Fastest
    
    num_elements = 5
    for i in range(num_elements):
        # Vary parameters based on 'i'
        x = i * 30          # Translate x
        y = i * 20          # Translate y
        scale = 1 + (i * 0.2) # Increase scale
        angle = i * 15        # Increase rotation
    
        draw_transformed_square(pen, x, y, scale, angle)
    

GIF showing a sequence of squares being drawn, each progressively larger, more rotated, and shifted, creating a spiral or fanning pattern.

7. Quick Reference: Key Turtle & Math Functions

  • turtle.Turtle(): Creates a new turtle object.
  • turtle.Screen(): Gets the drawing screen object.
  • turtle.done(): Starts the turtle graphics event loop (keeps window open).
  • turtle.exitonclick(): Closes the window when clicked.
  • turtle.speed(speed): Sets the turtle's speed (0 is fastest).
  • turtle.hideturtle(): Makes the turtle cursor invisible.
  • turtle.pensize(width): Sets the pen thickness.
  • turtle.forward(distance): Moves the turtle forward.
  • turtle.backward(distance): Moves the turtle backward.
  • turtle.left(angle): Turns the turtle left by angle degrees (relative).
  • turtle.right(angle): Turns the turtle right by angle degrees (relative).
  • turtle.setheading(angle): Sets the turtle's absolute orientation (0=East, 90=North, 180=West, 270=South).
  • turtle.penup(): Lifts the pen.
  • turtle.pendown(): Lowers the pen.
  • turtle.goto(x, y): Moves the turtle to absolute coordinates (x, y).
  • turtle.home(): Moves turtle to (0,0) and sets heading to 0.
  • math.radians(degrees): Converts an angle from degrees to radians.
  • math.cos(radians): Returns the cosine of an angle in radians.
  • math.sin(radians): Returns the sine of an angle in radians.