Simulating potential futures for the perfect putt Building a robot that hits a golf ball straight is a solved problem; building one that understands the chaotic geometry of a mini-golf course requires a massive software upgrade. The challenge lies in moving beyond simple direct-line aiming to a system that can simulate the physics of banks, bounces, and curved surfaces. To accomplish this, the project pivots from basic trigonometry to a full-scale physics simulation. By creating a digital twin of the physical environment, the club can "think" through thousands of potential outcomes before the head even makes contact with the ball. This process demystifies the complex interaction between the ball's spin, surface friction, and the specific topography of the green. Prerequisites and technical foundations To implement a similar simulation-driven hardware project, you need a firm grasp of several core concepts: - **Python Programming**: For scripting the simulation logic and handling data from motion capture systems. - **Rigid Body Physics**: Understanding how objects interact, specifically how friction and restitution (bounciness) affect motion. - **Spatial Geometry**: Knowledge of coordinate transforms to align the physical world with the virtual one. - **Motion Capture Integration**: Familiarity with systems like OptiTrack to feed real-time positional data into your software. Key Libraries and Tools - MuJoCo: A high-performance physics engine used for robotics and biomechanical research. It handles the complex contact dynamics of the golf ball hitting walls. - **Stochastic Solvers**: Mathematical frameworks used to find optimal parameters through random but systematic searching. - Brilliant: An interactive learning platform used to master the underlying math and geometric transforms necessary for this build. Code Walkthrough: From scanning to simulation The implementation begins with environmental mapping. Since CAD files rarely match reality, a custom 3D scanner—using a constellation of reflective markers—traces the hole's geometry. This data is fed into MuJoCo. ```python Conceptualizing the simulation loop import mujoco def simulate_shot(club_angle, swing_speed): # Initialize the model with scanned course geometry model = mujoco.load_model_from_path("minigolf_hole.xml") data = mujoco.MjData(model) # Apply initial conditions to the virtual club data.qvel[club_joint] = swing_speed data.qpos[head_joint] = club_angle # Step the simulation until the ball stops or enters the hole for _ in range(MAX_STEPS): mujoco.mj_step(model, data) if ball_in_hole(data): return True return False ``` The real heavy lifting occurs during **precomputation**. Because simulating every possible shot in real-time is too slow, the system runs an exhaustive search of all speeds and angles when the ball is placed. This results in a lookup table of "scoring shots" that the club can reference instantly during a physical swing. Syntax Notes and Optimization When working with physics simulations, **Parameter Tuning** is the primary hurdle. You will encounter the "Genie Problem" where the solver maliciously complies with your code—like increasing friction to infinity because a stationary ball technically never misses its target. To combat this, ensure your cost functions reward the total path accuracy, not just the final destination. Additionally, offloading computation to a high-thread-count workstation (the "Beast Computer") is essential when processing tens of thousands of simulations within a few minutes. Practical Examples and Calibration The system's effectiveness is best seen on the "Wavy Hole," where the ball must traverse varying elevations. The physics engine accounts for **thermal expansion**; if the workshop warms up, the camera tripods expand, shifting the coordinate system. To fix this, use reference markers on the course to dynamically calculate a rotation matrix that realigns the simulation with the physical world every time the program runs. Tips and Gotchas - **Manual vs. Automated**: Sometimes a stochastic solver fails where human intuition succeeds. Building a manual GUI to tweak parameters (friction, damping) can save weeks of debugging. - **Contact Dynamics**: A golf ball doesn't just reflect off a wall; it skids and jumps. If your simulation looks like "garbage," check your contact friction settings in MuJoCo. - **Speed Sensitivity**: Lower speeds often exhibit chaotic behavior. If a shot is unreliable, increasing the swing velocity often stabilizes the ball's path by minimizing the impact of minor surface imperfections.
Brilliant
Companies
Stuff Made Here (5 mentions) uses Brilliant as a tool to demystify complex concepts, highlighting its usefulness in videos like "Baking brownies from the inside out" and "Precision guided launcher turns noobs into pros".
- Apr 15, 2026
- Jan 1, 2026
- Jun 2, 2025
- Mar 4, 2025
- Jan 24, 2025
Overview Most developers fall into the trap of over-engineering early in a project. We often reach for complex design patterns like Model-View-Controller (MVC) or the Command pattern because they feel like the professional way to build. However, as this exploration of the Data Validator CLI demonstrates, excessive abstraction can drown your logic in boilerplate. This guide focuses on identifying "pattern fatigue" and refactoring a class-heavy Python application into a streamlined, functional, and testable tool. We are looking at an interactive shell designed to load CSV files, filter data, and perform validations. While the original architecture used separate classes for every possible user command, we will strip away that complexity. By favoring functions over classes and Protocols over Abstract Base Classes (ABCs), we create a codebase that is easier to maintain and far less brittle. Prerequisites To follow this tutorial, you should have a solid grasp of Python (3.10+) fundamentals, including dictionaries, decorators, and basic typing. Familiarity with Pandas for data manipulation and Pytest for unit testing is highly recommended. You should also understand the concept of a CLI (Command Line Interface) and how interactive shells differ from standard script execution. Key Libraries & Tools * **Python**: The core programming language used for the entire application. * **Pandas**: Used for high-performance data manipulation and loading CSV files into memory. * **Pydantic**: Originally used for argument validation (later refactored for simplicity). * **Pytest**: Our primary testing framework for ensuring refactored logic remains sound. * **Typing Module**: Utilized for adding type hints, `Protocol`, and `Callable` definitions to improve code clarity. Code Walkthrough: From Classes to Functions The original code used a classic Command pattern where every command (e.g., `exit`, `import`, `merge`) was a separate class with an `execute` method. This created a massive amount of file-system noise. Here is how we simplify it. 1. Decoupling the Event System The project uses an event system to handle updates. Instead of nesting this inside a controller, we move it to a standalone module and simplify the logic. We add support for a "star" (`*`) listener, allowing one function to catch all events—perfect for a shell that just needs to print messages to the user. ```python events.py from typing import Any, Callable _event_listeners: dict[str, set[Callable]] = {} def register_event(event_name: str, listener: Callable[..., None]) -> None: if event_name not in _event_listeners: _event_listeners[event_name] = set() _event_listeners[event_name].add(listener) def raise_event(event_name: str, *args: Any, **kwargs: Any) -> None: listeners = _event_listeners.get("*", set()).union(_event_listeners.get(event_name, set())) for listener in listeners: listener(*args, **kwargs) ``` 2. Refactoring Commands to Functions There is no need for a `ShowFilesCommand` class when a simple function will do. By using a dictionary to map strings to functions, we eliminate the need for a complex Factory pattern. We also replace Pydantic models with direct validation calls to reduce the number of small, single-use classes. ```python commands/show_files.py from .model import Model from ..events import raise_event def show_files(model: Model) -> None: table_names = list(model.data_frames.keys()) message = f"Files present: {', '.join(table_names)}" raise_event("display_message", message) ``` 3. Implementing the Command Factory With commands now being functions, the factory becomes a simple registry. This is much easier to read and extend than a series of class registrations. ```python commands/factory.py from typing import Any, Callable from .exit import exit_app from .show_files import show_files CommandFunc = Callable[..., None] COMMANDS: dict[str, CommandFunc] = { "exit": exit_app, "files": show_files, } def execute_command(name: str, *args: Any) -> None: if name in COMMANDS: COMMANDSname ``` Syntax Notes: Protocols vs. ABCs One major change in this refactor is the move from Abstract Base Classes to Protocols. ABCs require explicit inheritance (nominal subtyping), which can make your code rigid. If you want to replace the Model with a different implementation, you must inherit from the ABC. Protocols, on the other hand, use structural subtyping (often called static duck typing). As long as an object has the required methods, it matches the protocol. This is cleaner and more Pythonic. ```python from typing import Protocol class Model(Protocol): def get_data(self, alias: str) -> Any: ... def delete_data(self, alias: str) -> None: ... ``` Practical Examples This refactored architecture is ideal for any CLI tool that manages state in memory. For instance, a local database explorer or a file conversion utility benefits from this "flat" structure. By keeping the main entry point as a "patching" area where you register events and initialize the shell, you keep the logic of individual commands isolated and easy to test. In a real-world scenario, you might extend this by: 1. **Adding a Logger**: Instead of just printing, have the event system send data to a logging service. 2. **Configuration Files**: Use TOML or JSON to define a list of files that should automatically load when the shell starts. 3. **Advanced Querying**: Integrate DuckDB to allow SQL-like queries directly on the loaded Pandas DataFrames. Tips & Gotchas * **Avoid Global Namespace Pollution**: Always wrap your startup code in a `if __name__ == "__main__":` block and a `main()` function. This prevents variables from leaking into the global scope and makes your code easier to import for testing. * **Relative vs. Absolute Imports**: When working within a package, use relative imports (`from . import module`). This allows you to rename folders or move the package without breaking every internal reference. * **The YAGNI Principle**: "You Ain't Gonna Need It." Don't build an MVC structure just because you might add a GUI later. Build the simplest version that works today. If you need a GUI tomorrow, the clean, functional code you wrote will be easy to adapt. * **Testing Output**: Use the `capsys` fixture in Pytest to capture `stdout`. This is the most reliable way to test that your shell is actually displaying the correct messages to the user.
Dec 20, 2024The Geometry of Impossible Vision Standard photography relies on a predictable cone of light. A Pinhole Camera operates by allowing light to pass through a single point, projecting an inverted image onto a sensor. This creates the perspective we consider natural: objects appear smaller as they move further away. But this fixed geometry limits our ability to see past obstructions. To break this, we have to rethink the physical path light takes before it hits a sensor. By moving a single-pixel sensor across a wide physical area and varying its angle, we can simulate optics that don't exist in nature, effectively allowing a machine to "wrap" its vision around a central object. Mechanical Precision and the Balancing Act Building a camera that moves through space requires solving extreme mechanical challenges. A linear gantry moving fast enough to capture an image would vibrate so violently it would destroy the data. The solution is a spinning arm. This design maintains a constant velocity, eliminating the sudden accelerations that cause blur. The hardware involves a Photo Detector on a cart that travels the length of the arm, counterbalanced by a steel weight to prevent the assembly from shaking itself apart at high RPMs. To keep the wires from twisting into a knot, a Slip Coupling handles the electrical connection between the stationary base and the rotating computer, while a high-resolution Encoder tracks the arm's position to within fractions of a degree. Amplifying the Ghost of a Signal Capturing light with a single pixel at ten thousand readings per second creates a massive signal-to-noise problem. Unlike a standard camera that can take a long exposure, each measurement here is essentially instantaneous. The initial signal from the Photo Diode is so weak it is virtually indistinguishable from background electronic noise. Solving this requires a high-end Trans-Impedance Amplifier to boost the signal by a factor of 10 million. Without this specialized hardware, the resulting images are just gray static. Even with it, the software must perform heavy lifting, using linear algebra to map every timed reading back to a specific coordinate in a 2D image. Orthographic and Reverse Perspective Once you control the sensor's position and angle, the "rules" of reality become optional. By keeping the sensor pointed perfectly straight at all times, the camera produces an orthographic projection. In this mode, perspective vanishes; a foam head 100 feet away appears exactly the same size as one inches from the lens. Taking it further, we can create reverse perspective by pointing the sensor inward as it moves outward. This makes distant objects loom larger than those in the foreground, a visual effect that contradicts every instinct of human biology but proves the flexibility of computational imaging. Seeing Around the Obstacle The ultimate goal is the "ring camera" configuration. By pathing the sensor in a circle and angling it toward a central point behind an obstruction, the camera effectively peers around the edges of a barrier. When capturing a human face behind a wall, the camera doesn't just see the person; it sees them from five different angles simultaneously and flattens that data into a single frame. The result is a surreal, unfolded map of a human head—an authentic, if slightly haunting, look at what happens when you remove the physical constraints of a traditional lens.
Dec 4, 2024The Allure of the Micro-Build It all starts with a single spark of curiosity. For some, it’s seeing how much power you can cram into a SFF case; for others, it's the sheer absurdity of riding a machine meant for someone a tenth your size. The journey began with a simple desire to downsize a standard bicycle, utilizing a Water Jet to slice 2D shapes out of metal sheets like a high-tech puzzle. This wasn't just about shrinking a frame; it was about reimagining mechanical interfaces. When you scale down, standard components like Freehubs suddenly become massive bottlenecks. You’re forced to machine custom parts just to ensure that when you stop pedaling, the wheels keep spinning. It's a classic engineering trade-off: simplicity vs. functionality. The Engineering Wall of Miniature Scale As the design shrunk to challenge the Guinness World Records mark of 8.4 cm, the technical debt started piling up. When a bike is smaller than a single wheel of its predecessor, you can't just scale the CAD file and hit print. Space becomes your most precious resource. I had to ditch traditional bike chains for a One-way Bearing to handle coasting in a fraction of the space. Even the tires required a custom 3D-printed mold and two-part polyurethane to survive the vertical loads of a full-grown human. At this scale, every millimeter matters because your hands and feet are constantly fighting for the same physical coordinates in space. Pulling Out the Five-Axis Big Guns When the goal shifted from merely beating the record to crushing it by 1.6 times, the manufacturing complexity skyrocketed. Three-axis milling—cutting laterally and vertically—wasn't enough to handle the intricate geometries needed to pack a drivetrain into a 5.3 cm footprint. I had to transition to a 5-axis Mill. This allows for machining at any angle without constantly repositioning the part, which is essential when you're integrating a belt drive system and a crankshaft into a frame that looks more like a piece of jewelry than a vehicle. It’s the ultimate expression of hands-on hardware expertise: using the most advanced tools to solve the most ridiculous problems. Geometry vs. The Human Body Building the machine is only half the battle; the real climax is the brutal learning curve of the ride. A tiny wheelbase means zero rotational stability. On the final iteration, nicknamed the "Tall Boy," the geometry became an active enemy. Because the cranks extended past the front wheel, every downward stroke threatened to tip the bike forward or twist it out from under me. It took weeks of practice and two terabytes of failure footage before the muscle memory finally clicked. In the end, the project wasn't just about a record—it was a lesson in persistence, showing that even the most impractical machine can be brought to life with enough iterations and a bit of pragmatic stubbornness.
Aug 9, 2024Overview: The Computational Nightmare of All-White Puzzles Solving an all-white puzzle is the ultimate hardware and software stress test. Without visual cues or image features, you are left with nothing but geometry. A 4,000-piece puzzle requires approximately 30 million physical comparisons if approached manually—a task that would take a human years. This tutorial breaks down the computer vision and algorithmic techniques required to automate this process, moving from raw pixel data to a fully realized locality-sensitive hashing (LSH) system for high-speed geometric matching. Prerequisites To implement these concepts, you should be comfortable with: * **Python**: The primary language for prototyping computer vision logic. * **Image Processing**: Understanding thresholding and pixel-to-contour conversion. * **Geometry**: Familiarity with polygons and curve fitting. * **Data Structures**: Specifically hashes and multi-dimensional coordinate systems. Key Libraries & Tools * **OpenCV**: Used for initial image acquisition and extracting the contour (edge) of the puzzle pieces. * **NumPy**: Essential for handling the 128-dimensional arrays used in the hashing phase. * **Puget Systems Workstations**: High-performance hardware used to parallelize the pre-processing tasks. * **Telecentric Lens**: A critical piece of optics that eliminates perspective distortion, ensuring the puzzle piece dimensions remain accurate across the frame. Code Walkthrough: From Pixels to Hashing 1. Shape Extraction and Edge Normalization First, we isolate the puzzle piece from the background using thresholding. Once we have a binary blob, we identify the perimeter and convert it into a polygon. The real challenge is finding the corners to split the perimeter into four distinct edges. ```python Conceptual approach for finding clean corners for corner in approximate_corners: # Grab small segments of the polygon on either side of the corner curve_a = fit_curve(polygon_segment_left) curve_b = fit_curve(polygon_segment_right) # The intersection of these curves is the 'true' geometric corner true_corner = intersect(curve_a, curve_b) # Split the polygon at the point closest to this intersection split_point = find_closest_point(polygon, true_corner) ``` 2. Locality-Sensitive Hashing (LSH) To avoid comparing every edge to every other edge (an $O(n^2)$ nightmare), we use Locality-Sensitive Hashing. We project 128-dimensional edge features into a hashed sequence. Similar shapes fall into the same "bucket," allowing for $O(1)$ lookup. ```python 128-dimensional projection (Simplified logic) def get_lsh_hash(edge_points, hyperplanes): # edge_points is a vector approximating the curve shape # hyperplanes define the boundaries in high-dimensional space hash_sequence = "" for plane in hyperplanes: if dot_product(edge_points, plane) > 0: hash_sequence += "1" else: hash_sequence += "0" return hash_sequence ``` Syntax Notes: High-Dimensional Space While we visualize geometry in 2D or 3D, the algorithm uses a **128-dimensional space**. In this context, a "line" becomes a **hyperplane**. The syntax for determining which side of a hyperplane a point falls on is a simple dot product. This mathematical efficiency is what allows the robot to identify potential matches in milliseconds rather than hours. Practical Examples: Hierarchical Assembly Once potential matches are identified, the software doesn't just guess. It employs a **recursive block-building strategy**. It finds valid 2x2 blocks by ensuring pieces share mutual neighbors. These 2x2 blocks are then treated as "super-pieces" to form 4x4 blocks, continuing until the entire grid is solved. This dramatically reduces the search space by eliminating local geometric matches that don't fit the global grid. Tips & Gotchas * **Garbage In, Garbage Out**: If your puzzle pieces have fuzzy edges from a dull die-cutter, your algorithm will fail. Use a laser cutter or a vibratory tumbler to ensure sharp, clean edges. * **Pre-processing Latency**: Extracting 4,000 piece shapes can take days. Use Python's multiprocessing to split the job into smaller tasks across all available CPU cores. * **Cumulative Error**: In physical assembly, small misalignments add up. Your robot needs a secondary camera to "nudge" pieces into place as the grid grows.
Dec 1, 2022The Infinite Complexity of a Simple Task Building a machine to perform a leisure activity sounds like a paradox, but for a hardware enthusiast, it's the ultimate stress test. Constructing a robot to solve a **5,000-piece puzzle**—specifically one painted entirely white—presents a gauntlet of mechanical and computational hurdles. While a human might spend years matching shapes by trial and error, a robot requires a level of precision that makes standard DIY projects look like child's play. The project demands a custom-built gantry, a vision system capable of sub-millimeter accuracy, and a way to handle thousands of physical objects without jamming. It's a classic case of "integration hell," where individual components work perfectly in isolation but clash when united. CoreXY and the Quest for Speed To move pieces across a massive table, weight is the enemy. Standard gantry designs often mount heavy motors on the moving axes, which limits acceleration and introduces vibration. The CoreXY belt arrangement solves this by keeping the heavy stepper motors stationary at the corners of the frame. This setup uses a complex, winding belt path where the motion of both motors coordinates the X and Y movement. Pulling the belts in opposite directions moves the cart, while pulling them in the same direction moves the entire beam. It allows for aggressive speed, though it requires significant tuning to prevent belt skipping when the high-torque motors bite into a fast move. Vision Precision Through Telecentricity Standard camera lenses are terrible for measurement because they introduce perspective distortion. If you look at a puzzle piece through a wide-angle lens, you see the sides and edges at an angle, making it impossible to calculate the true footprint. To fix this, the build utilizes a telecentric lens. This specialized optic views the world as if it were infinitely far away, looking through a straight "tube" of light. This ensures that the piece is the same size regardless of its distance from the lens and eliminates the parallax error that would otherwise tank the matching algorithm's accuracy. Software Calibration and Vibration Control Even with a solid frame, mechanical play can ruin the assembly. To counter this, an OptiTrack camera system tracks the gantry's position in real-time, allowing the software to correct for non-square motion on the fly. However, moving a heavy gantry at high speeds creates massive vibrations. The solution involves structural bracing with tensioned steel cables—turning the table legs into rigid, non-flexing supports. For the final version, the table is designed to be a hollow vacuum plenum, capable of sucking 5,000 pieces down to the surface through 30,000 tiny drill holes to ensure they don't wander during assembly. The Storage Nightmare Where do you put 5,000 pieces while the robot thinks? Making the table larger wasn't an option, so the design includes a series of motorized storage magazines. These use a clever multiplexing system where a single motor on a rail slides between 21 different lead screws, lowering platforms as pieces are stacked. This avoids the cost and wiring nightmare of 21 individual motors while providing a vertical storage solution for a problem that would otherwise take up 35 feet of height if stacked in a single column.
Aug 1, 2022The Quest for Supersonic Plastic Building a machine to outperform human biology is a classic engineering challenge, but doing so within the constraints of disc golf regulation adds a layer of beautiful complexity. The benchmark to beat stands at 89.5 miles per hour, a record set by Simon Lizotte. Surpassing this speed isn't just about raw power; it's about managing extreme mechanical stresses, aerodynamics, and the volatile nature of pneumatic energy. The result of this obsession is a forearm-mounted gauntlet that pushes the boundaries of material science and kinetic energy. Solving the Newton Problem: Recoil and Reaction When you accelerate a 175-gram disc to nearly 100 miles per hour in a fraction of a second, the reaction forces are violent. Isaac Newton dictates that for every action, there is an equal and opposite reaction. In a handheld launcher, this manifests as a kickback capable of fracturing a human arm. The initial design attempted to solve this through a counter-rotating mass system—two arms spinning in opposite directions to cancel out the torque. However, real-world physics revealed a secondary issue: the vertical offset of the arms. Because the arms weren't on the exact same plane, the opposing forces created a twisting torque comparable to the output of a Honda Civic. This necessitated a pivot to a single-arm system, prioritizing weight reduction and extreme skeletonization of the components to minimize the momentum transferred back to the user's arm. Overcoming Pneumatic Bottlenecks To drive the arm, standard pneumatic systems fall short. Most off-the-shelf valves are designed for consistent, low-speed flow—perfect for a factory assembly line, but useless for an explosive launch. The discovery of "tiny hole" syndrome in high-pressure regulators proved that even at 3,000 PSI, the volume of air reaching the cylinder was insufficient to achieve record-breaking speeds. The solution was a custom-engineered high-flow piston valve. By using a small, weak valve to trigger a much larger, high-volume piston, the system can dump a massive volume of air into the cylinder nearly instantaneously. This required moving away from regulated tanks to a dual-tank system where a secondary reservoir holds unregulated, high-pressure air ready for immediate discharge. It's the difference between a garden hose and a dam breaking. The G-Force Crisis and Material Failure As the launcher reached higher power levels, the discs began to fail in fascinating ways. At 0-to-60 acceleration happening in thousandths of a second, the disc golf disc experiences over 100 pounds of force. This causes the plastic to deform, effectively "squeezing" out of traditional mechanical grippers. Iterative testing showed that pushing the disc from behind caused it to buckle and fail. The engineering fix was a "pulling" mechanism that grips the front rim, stretching the disc during acceleration rather than compressing it. To handle these forces, the arm transitioned from 3D-printed polymers to aerospace-grade aluminum, Titanium, and carbon fiber. Even then, the centrifugal forces were so high they occasionally stripped the gripper assembly clean off its mounting rods. Aerodynamics vs. Raw Velocity Speed is only half the battle in disc golf. A disc is both a wing and a gyroscope. While the launcher successfully clocked speeds estimated up to 115 miles per hour—well past the human record—the distance didn't follow linearly. Standard discs are not designed for these velocities; they become aerodynamically unstable and undergo "high-speed turn," rolling over into the ground. To achieve true distance, the machine needs more than just exit velocity; it needs a massive increase in spin rate to provide gyroscopic stability. Without thousands of extra RPMs, the disc cannot maintain its flight angle, proving that in the world of DIY hardware, precision and stability are just as vital as raw, unbridled power. This project serves as a masterclass in the iterative process: identify the bottleneck, engineer a solution, and move to the next point of failure.
Mar 1, 2022