Advertisement
j0h

3D_Etch-A-Sketch

j0h
Jun 28th, 2025 (edited)
325
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.77 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. import os
  3. import re
  4. import serial
  5. import subprocess
  6. from time import sleep
  7. import pyautogui
  8.  
  9. # CONFIG
  10. PORT = "/dev/ttyACM0"
  11. BAUD = 115200
  12. OUTPUT_FILE = "lego.scad"
  13. BRICK_MODULE_FILE = "brick.scad" # the model brick
  14. placed_bricks = set()
  15.  
  16.  
  17. X_STEP = 16   # half of 32 mm
  18. Y_STEP = 8     # half of 16 mm
  19. Z_STEP = 9.6  # full height
  20.  
  21. # Global position tracker [x, y, z]
  22. current_position = None
  23. current_rotation = 0 # R value modulo 4 (i.e. 0, 1, 2, 3)
  24.  
  25. # Rather than including brick.scad, lets just write it.
  26. def brick_scad():
  27.     with open('b.scad', 'w') as f:
  28.         f.write('// brick.scad\nw = 8; \n h = 9.6; \n module lego_brick(studs=4){ \n $fn = 80; \n width = 8 * studs; \n cube([width,16,9.6]); \n for (xpos=[4 : 8 : width-4]){ \n translate([xpos,4,1.7]) cylinder(h=9.6,d=4.8); \n translate([xpos,12,1.7]) cylinder(h=9.6,d=4.8); \n } \n } \n')
  29.  
  30.  
  31. # this addresses an issue in open scad around load large files, by ensuring the output file is always blank
  32. def lego_scad():
  33.     with open('lego.scad','w') as f:
  34.         f.write('use <brick.scad>;\n')
  35.  
  36. brick_scad()
  37. lego_scad()
  38.  
  39. def launch_openscad():
  40.     subprocess.Popen(["openscad", OUTPUT_FILE])
  41.     sleep(2)
  42.  
  43. #def update_scad_with_brick(x, y, z, rotation_deg):
  44. def update_scad_with_brick(x, y, z):
  45.     #brick_key = (x, y, z, rotation_deg)
  46.     brick_key = (x, y, z)
  47.     if brick_key in placed_bricks:
  48.         print(f"[SKIP] Brick already placed at {brick_key}")
  49.         return
  50.  
  51.     placed_bricks.add(brick_key)
  52.     #brick_line = f"rotate([0,0,{rotation_deg}]) translate([{x}, {y}, {z}]) lego_brick(4);\n"
  53.     brick_line = f"translate([{x}, {y}, {z}]) lego_brick(2);\n"
  54.     try:
  55.         with open(OUTPUT_FILE, "a") as f:
  56.             f.write(brick_line)
  57.     except Exception as e:
  58.         print("Error writing brick to SCAD file:", e)
  59.         return
  60.  
  61.     print(f"[SCAD] Added brick at {brick_key}")
  62.     pyautogui.press("f5")
  63.     sleep(0.2)
  64.  
  65. '''
  66. def update_scad_with_brick(x, y, z, rotation_deg):
  67.    brick_line = f"rotate([0,0,{rotation_deg}]) translate([{x}, {y}, {z}]) lego_brick(4);\n"
  68.  
  69.    try:
  70.        with open(OUTPUT_FILE, "a") as f:
  71.            f.write(brick_line)
  72.    except Exception as e:
  73.        print("Error writing brick to SCAD file:", e)
  74.        return
  75.  
  76.    print(f"Added brick at x={x} y={y} z={z} rotation={rotation_deg}")
  77.    pyautogui.press("f5")
  78.    sleep(0.2)
  79. '''
  80. def main():
  81.     global current_position, current_rotation
  82.  
  83.     try:
  84.         # Ensure SCAD file starts with module include
  85.         if not os.path.exists(OUTPUT_FILE):
  86.             with open(OUTPUT_FILE, "w") as f:
  87.                 f.write(f'use <{BRICK_MODULE_FILE}>;\n')
  88.  
  89.         ser = serial.Serial(PORT, BAUD, timeout=1)
  90.         print("Listening to Teensy on", PORT)
  91.  
  92.         launch_openscad()
  93.         last_positions = [0, 0, 0, 0, 0]  # X, Y, Z, R, V
  94.  
  95.         while True:
  96.             line = ser.readline().decode(errors="ignore").strip()
  97.  
  98.             if line:
  99.                 print("RAW:", line)  # Log all input for debugging
  100.  
  101.             if line.startswith("X:"):
  102.                 match = re.match(r"X:\s*(-?\d+), Y:\s*(-?\d+), Z:\s*(-?\d+), R:\s*(-?\d+), V:\s*(-?\d+)", line)
  103.  
  104.                 if match:
  105.                     new_positions = list(map(int, match.groups()))
  106.                     if new_positions != last_positions:
  107.                         print(f"[ENCODER UPDATE] X={new_positions[0]} Y={new_positions[1]} Z={new_positions[2]} R={new_positions[3]} V={new_positions[4]}")
  108.                         # Compute real-world position
  109.                         x_real = new_positions[0] * X_STEP
  110.                         y_real = new_positions[1] * Y_STEP
  111.                         z_real = new_positions[2] * Z_STEP
  112.                         #angle = (new_positions[3] % 4) * 90
  113.                         #brick_key = (x_real, y_real, z_real, angle)
  114.                         brick_key = (x_real, y_real, z_real)
  115.  
  116.                         if brick_key not in placed_bricks:
  117.                             print(f"[AUTO-DRAW] Placing brick at {brick_key}")
  118.                             #update_scad_with_brick(x_real, y_real, z_real, angle)
  119.                             update_scad_with_brick(x_real, y_real, z_real)
  120.                         else:
  121.                             print(f"[SKIP] Brick already placed at {brick_key}")
  122.  
  123.                         last_positions = new_positions
  124.                         current_position = new_positions[0:3]
  125.                         current_rotation = new_positions[3] % 2 # becuase only only 0,90 mater. its a regular block.
  126.                
  127.                 else:
  128.                     print("[WARN] Failed to parse encoder line:", line)
  129.             '''
  130.            elif line == "E#0 pressed":
  131.                print("[BUTTON] E#0 (View) pressed")
  132.            elif line == "E#1 pressed":
  133.                print("[BUTTON] E#1 (Z) pressed")
  134.            elif line == "E#2 pressed":
  135.                print("[BUTTON] E#2 (X) pressed")
  136.            elif line == "E#3 pressed":
  137.                if current_position is None:
  138.                    print("[SKIP] No position data yet ignoring button press")
  139.                    continue
  140.                print("[BUTTON] E#3 (Y / PLACE) pressed  Drawing brick")
  141.  
  142.                x_real = current_position[0] * X_STEP
  143.                y_real = current_position[1] * Y_STEP
  144.                z_real = current_position[2] * Z_STEP
  145.                angle = current_rotation * 90
  146.  
  147.                print(f"[DRAW] Brick @ X:{x_real} Y:{y_real} Z:{z_real} Angle:{angle}")
  148.                update_scad_with_brick(x_real, y_real, z_real, angle)
  149.  
  150.            elif line == "E#4 pressed":
  151.                print("[BUTTON] E#4 (Rotate) pressed")
  152.            '''
  153.  
  154.     except KeyboardInterrupt:
  155.         print("\nInterrupted by user. Exiting.")
  156.         try:
  157.             if ser and ser.is_open:
  158.                 ser.close()
  159.                 print("[INFO] Serial port closed.")
  160.         except Exception as e:
  161.             print(f"[WARN] Error closing serial port: {e}")
  162. if __name__ == "__main__":
  163.     main()
  164. '''
  165. Ok this is a fully functional 3D drawing program, but its still buggy,
  166. the IO is a teensy 4.0, with 4 rotary encoders, the IO can easily overload
  167. both the i2C bus it is on, and the program Openscad, which it uses to draw the 3D
  168. models.
  169.  
  170. Need to make improvements, but it do work. ... provided I don't alter the functions of
  171. the serial commands the MCU provides. a fresh build of open scad can help
  172. performance, by adjusting the rendering properties to manifold, and by increasing the
  173. number of objects / RAM openscad uses to process objects.
  174.  
  175. There is also a bug in openscad, where manifold only re-renders objects,
  176. this means, dont load large files initially, but reloading them is fine.
  177. Still waiting for gooners at openscad to fix the issue.
  178. '''
  179.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement