Ideation

Initial Concept

The architecture section got extremely lucky; Sergio had a clear vision that made it easy to rally everyone and quickly get started. He had already considered making something with a similar mechanism to the hang printer or the suspended cameras in stadiums for his final project, so this was a natural opportunity to test some of the concepts.

Essentially, our design is a “claw machine” where the claw / grabber is suspended by cables (not unlike the hanging 3D printer). This grabber can then pick up blocks in its space and stack them or arrange however the user wants.

Workstreams and Early Planning

We next began reducing the machine into more contained modules to start dividing responsibilities up within our team. The main modules we identified were:

  • Frame
  • Pulleys & Motors
  • Blocks & Grabber
  • Code
  • Integration

Those with distinct interests or backgrounds became leads for their respective modules (Xdd really had no choice but to lead the code team), and afterwards others filled in based off interests and common availability.

Everyone was asked to take some pictures and share some documentation and updates as they progressed in their module, and then final documentation was assigned to only the most skilled and trusted members of the group ;)

Timeline

Our group set an aggressive timeline to ensure adequate time for debugging:

  • 11/11: Initial meeting + team assignments
  • 11/13: Initial CAD designs for system complete, parts list drafted
  • 11/14: Frame complete, parts list v2
  • 11/16: Integration components complete, begin assembly, initial code draft
  • 11/18: Assembly complete, begin debugging

Now, go forth and build!

Frame

Frame Design

The frame size and design forced decisions for literally every other module, so it was important for them to nail down the design early.

Based on our first brainstorming session, we knew that the frame would be built in the shape of a cube with equal length edges. To ensure a balance between ease of transportation and space to easily attach and manipulate components, the team settled on roughly 32in side lengths.

Components

Our team used alluminum extrusions to build the frame.

As you can see, they started out MUCH longer than 32in, so the first task was to cut them down to size using the circular saw in the architecture shop.

Next we needed a mechanism to join the extrusions at each corner. For this, 3D printed braces that would cover all extrusions at the the corners and added spaces for screws for attachment points.

Assembly

Assembling the frame involved screwing the braces to the extrusions while ensuring everything stayed aligned. To aid in the alignment, our team used metal triangles to ensure perfect right angles around the cube.

Final assembly took around a half hour and was finished on 11/14. Right on schedule!

Base

In the spirit of spiral development, and to ensure consistency regardless of what surface we set up on, the frame team also decided to build a base. They designed and 3D printed 4 triangle "feet" to rest the frame on. They use a press fit to slide into the grooves on the extrusion. For an actual floor, we covered an MDF sheet in vinyl. The feet have slots for the mdf, so they hold the base in place and don't allow for any movement.

Pulleys

We started off designing how to connect the stock plastic pulleys to a 20mm x 20mm aluminum extrusion. After this first iteration, we realized that metal slot nuts would be a more secure way to connect to the aluminum extrusion, and that a diagonal orientation of the pulley will allow for a bigger 'play area'. The frame team also obtained 60mm x 20mm extrusions instead for the frame for better stability. Onward to the next iteration!

The next pulley connector design iteration oriented the pulleys diagonally, connected to a 60mm x 20mm aluminum extrusion through slot nuts, and had larger surface area for stability. Alas, the overall frame was rotated to fit other components, the 3D-printed axis did not fit, and the plaster idler pulleys themselves did not move smoothly, so another redesign was in order.

Our final iteration saw us procuring metal pulleys that spun more reliably and grabbed on to the cables better. We tried different materials and ultimately settled on metal connector pieces for better strength.

Trying out metal! There is a laser cutter that could cut metal at the N51 woodshop, so we fabricated our pulley connector pieces there. And voila!

Our winch design from sketch to realization was a tumultuous process as well. Our initial design for a connector did not fit, thus it was back to the drawing board.

After a few iterations and multiple 13-hour prints, we finally printed our multiple spools of winches!

Blocks

In conceptualizing the building blocks (literally) of our project, we wanted to mass produce blocks that could snap onto each other through small magnets embedded on their faces. This was designed in tandem with a 'grabber' acting as a 'claw', powered by electromagnets. We 3D printed those blocks and embedded small magnets on the surfaces.

However the issue was that the magnets on the blocks would stick to the electromagnetic grabber even when it was not activated, i.e. even when we do not intend for the blocks to be picked up. We thus decided to pivot to using metal pieces instead of magnets for the blocks.

We cut little round metal pieces to embed within our 3D-printed blocks. In the same file, we also cut out metal plates with the intent of using it as weights to stack on top of the grabber, to stabilize its motion.

Not all our 3D printing went smoothly - pictured here is a mountain of failed 3D prints. Many more to go and mass produce after tweaking 3D printing settings such as infill.

We also tried a few different iterations of block designs, and settled on the chamfered round edged version on the right, for user-friendliness and also for ease of spray-painting, so that we could apply the desired finish and colors to the finished product.

Our final prototype of blocks combined with the round metal inset, which fit perfectly into our custom designed 3D-printed blocks.

Naturally, though, we couldn't just leave them white. That would be boring! So after confirming the design, we decided to add a pop of color.

Integration

Assembly

Initial Assembly

Thanks to our early brainstorming and coordination, attaching the winches to the frame was relatively straightforward; the winch team had taken the dimensions of the frame and build in easy attachments to the frame. Because there was only one way for the winch to fit and attach, this removed a lot of guesswork.

Adding the pulleys at the top was a different story. We did our best to get the cables that would hold the grabber to be the same length, but it was impossible to make the loops when connecting to the grabber exactly the same size. The same issue applied when trying to hang the pulleys at the exact same height. These slight inconsistencies combined with the fact that we had four tether points (as opposed to three) meant that there was always a little tension in at least one cable and that there was a fair amount of wobble in the grabber.

To minimize the tension issue, we had cut steel sheets in the same shape as the grabber to add some weight. Unfortunately, they were not heavy enough. For testing, we resorted to taping metal blocks to the grabber. It wasn't pretty, but it was effective!

We also moved from trying to add additional connection points to the cables around the “corners” of the grabber to instead binding the cables together in the middle. The former resulted in a more stable looking grabber, but one that was prone to tipping in a way that the magnet would often face at different angles. The latter was more wobbly, almost like a claw machine in an arcade, and while we initially thought these wobbles were a problem, it ultimately resulted in more consistent performance from the magnet in picking things up. And that's why our grabber rocks a ponytail!

Here's the whole process sped up:

Magnet

The magnet needed a separate connection from the motors. While the motors used thick data wires, we needed to manually solder together a thin wire for the magnet.

Again, in concept this was easy. In practice, we were moving things around constantly and the wire broke a couple times, meaning we needed to re-solder it.

There were a couple other issues with the magnet.

Early on, we thought it was far weaker than we needed. We had a variety of 3D print infils on the blocks, meaning different weights, and struggled to have it consistently pick up any but the lightest blocks.

This issue was compounded with sporadic surface contact. When testing, the magnet wasn’t consistently making full contact with the metal discs we inserted in the blocks. This meant that it would sometimes lose connection with the block even while the magnet was still turned on. On the other hand, when it did make a full connection on the lightest blocks, they would often remain magnetized even when the magnet was turned off. We spent a long time testing combinations of covering part of the discs with tape on different weight blocks until we finally got some that mostly worked.

...until we figured out it was a code issue! Once we fixed the timing for turning the magnet on and off, issues were gone!

Additional Constraints

Some final finishing elements were cable cross braces to stabilize the entire frame and a backdrop to hide a lot of the wiring (and add some design flare)

The cross braces were made by crimping cable loops and then using screw holes that could be slotted into the frame

The backdrop was made with another sheet of MDF and painted with our machine's logo. It was then screwed into the back.

The finished product looks pretty slick!

Code

The examples, library, and setup from Jake was quite complete, so we just need to focus on converting 3D coordinates to cable lengths. This involves two parts: calculate the L2 distances between grabber's anchoring positions and pulleys positions and converting linear distance/length to rotation rev counts, taking spool radius and gear ratio into account.

Below is the code piece to do all conversions, where block names are some measurement constants. cell_to_rev_all function accepts a position in [x, y, z] and returns the corresponding rev (relative to starting position) for all 4 motors.

def dist(p1, p2): return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2 + (p1[2] - p2[2]) ** 2) def plus(p1, p2): return [p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2]] def cell_to_pos(p): return [p[0] * UNIT_SIZE, p[1] * UNIT_SIZE, p[2] * UNIT_SIZE] def pos_to_wire_len(p): return [ dist(WIRE_A_POS, plus(p, PULLY_OFFSET_A)), dist(WIRE_B_POS, plus(p, PULLY_OFFSET_B)), dist(WIRE_C_POS, plus(p, PULLY_OFFSET_C)), dist(WIRE_D_POS, plus(p, PULLY_OFFSET_D)) ] def length_to_rev(len): return rev def length_to_rev_all(len_all): return [ length_to_rev(len_all[0]), length_to_rev(len_all[1]), length_to_rev(len_all[2]), length_to_rev(len_all[3]) ] def cell_to_rev_all(p): return length_to_rev_all(pos_to_wire_len(cell_to_pos(p))) revs = cell_to_rev_all(points)

Since we are using the planner in provided library, we actually need the revs relative to original position:

def sys_graph(time: int): cell_coords = planner.on_new_control_point(time) rev_all = cell_to_rev_all(cell_coords) return [ INITIAL_REV_ALL[0] - rev_all[0], INITIAL_REV_ALL[1] - rev_all[1], INITIAL_REV_ALL[2] - rev_all[2], INITIAL_REV_ALL[3] - rev_all[3] ] maxl.use_graph(sys_graph)

Next step, we compose the grabber for automatic pick-drop. To prevent knocking things off, the grabber should only move horizontally when it's up in the sky. Therefore, the sequence is:

1) Move to above feeding position;

2) Drop to feeding position;

3) Activate electromagnet;

4) Rise upward;

5) Move to above dropping position;

6) Drop to dropping position;

7) Deactivate electromagnet;

8) Rise upward;

In the code below, positions is a list of positions loaded from local file indicating cube dropping positions. planner.goto_and_await moves the grabber, and hbridge.set_bridge set the current value going to electromagnet, 2.0 for activation, 0 for deactivation.

jog_rate = 4 draw_rate = 2.5 await planner.goto_and_await([INITIAL_CELL[0], INITIAL_CELL[1], TOP_UNIT], draw_rate) await planner.goto_and_await([FEEDER_CELL[0], FEEDER_CELL[1], TOP_UNIT], draw_rate) try: for pos in positions: await planner.goto_and_await(FEEDER_CELL, jog_rate) await hbridge.set_bridge(2.0) print("pick") await planner.goto_and_await([FEEDER_CELL[0], FEEDER_CELL[1], TOP_UNIT], draw_rate) await planner.goto_and_await([pos[0], pos[1], TOP_UNIT], draw_rate) await planner.goto_and_await(pos, draw_rate) print("drop") await hbridge.set_bridge(0) await planner.goto_and_await([pos[0], pos[1], TOP_UNIT], jog_rate) await planner.goto_and_await([FEEDER_CELL[0], FEEDER_CELL[1], TOP_UNIT], draw_rate) except: await hbridge.set_bridge(0)

Live control with keyboard interacts with MAXL differently, since we cannot "plan" the desired movement from user. Therefore, Jake helped me set up MAXLOneDOF to individually control velocity along each axis in planner, and I set up a listener to call the functions from keyboard. Since hbridge.set_bridge is an async function, we have to create an async task mag_checker to listen to a flag magnet_status

x_dof = MAXLOneDOF(maxl, max_accel, max_rate) y_dof = MAXLOneDOF(maxl, max_accel, max_rate) z_dof = MAXLOneDOF(maxl, max_accel, max_rate) async def mag_checker(): global magnet_status while True: if magnet_status: await hbridge.set_bridge(2.0) else: await hbridge.set_bridge(0) await asyncio.sleep(0.1) asyncio.create_task(mag_checker()) def on_press(key): global magnet_status try: if (hasattr(key, 'char') and key.char is not None): command = key.char.upper() match command: case 'W': y_dof.goto_velocity(-1.6) case 'S': y_dof.goto_velocity(1.6) case 'A': x_dof.goto_velocity(-1.6) case 'D': x_dof.goto_velocity(1.6) case 'Q': z_dof.goto_velocity(1.6) case 'E': z_dof.goto_velocity(-1.6) elif key == keyboard.Key.space: print("space") magnet_status = not magnet_status except Exception as error: print("An exception occurred:", error) pass def on_release(key): print(key) x_dof.goto_velocity(0) y_dof.goto_velocity(0) z_dof.goto_velocity(0) def start_keyboard(): with keyboard.Listener(on_press=on_press, on_release=on_release) as listener: listener.join() listener_thread = threading.Thread(target=start_keyboard, daemon=True) listener_thread.start() def sys_graph(time: int): # first get pts from the planner cell_coords = planner.on_new_control_point(time) cell_coords[0] = x_dof.on_time_step(time, cell_coords[0]) cell_coords[1] = y_dof.on_time_step(time, cell_coords[1]) cell_coords[2] = z_dof.on_time_step(time, cell_coords[2]) rev_all = cell_to_rev_all(cell_coords) return [ INITIAL_REV_ALL[0] - rev_all[0], INITIAL_REV_ALL[1] - rev_all[1], INITIAL_REV_ALL[2] - rev_all[2], INITIAL_REV_ALL[3] - rev_all[3] ] maxl.use_graph(sys_graph)

Elements

Text

This is bold and this is strong. This is italic and this is emphasized. This is superscript text and this is subscript text. This is underlined and this is code: for (;;) { ... }. Finally, this is a link.


Heading Level 2

Heading Level 3

Heading Level 4

Heading Level 5
Heading Level 6

Blockquote

Fringilla nisl. Donec accumsan interdum nisi, quis tincidunt felis sagittis eget tempus euismod. Vestibulum ante ipsum primis in faucibus vestibulum. Blandit adipiscing eu felis iaculis volutpat ac adipiscing accumsan faucibus. Vestibulum ante ipsum primis in faucibus lorem ipsum dolor sit amet nullam adipiscing eu felis.

Preformatted

i = 0;

while (!deck.isInOrder()) {
    print 'Iteration ' + i;
    deck.shuffle();
    i++;
}

print 'It took ' + i + ' iterations to sort the deck.';

Lists

Unordered

  • Dolor pulvinar etiam.
  • Sagittis adipiscing.
  • Felis enim feugiat.

Alternate

  • Dolor pulvinar etiam.
  • Sagittis adipiscing.
  • Felis enim feugiat.

Ordered

  1. Dolor pulvinar etiam.
  2. Etiam vel felis viverra.
  3. Felis enim feugiat.
  4. Dolor pulvinar etiam.
  5. Etiam vel felis lorem.
  6. Felis enim et feugiat.

Icons

Actions

Table

Default

Name Description Price
Item One Ante turpis integer aliquet porttitor. 29.99
Item Two Vis ac commodo adipiscing arcu aliquet. 19.99
Item Three Morbi faucibus arcu accumsan lorem. 29.99
Item Four Vitae integer tempus condimentum. 19.99
Item Five Ante turpis integer aliquet porttitor. 29.99
100.00

Alternate

Name Description Price
Item One Ante turpis integer aliquet porttitor. 29.99
Item Two Vis ac commodo adipiscing arcu aliquet. 19.99
Item Three Morbi faucibus arcu accumsan lorem. 29.99
Item Four Vitae integer tempus condimentum. 19.99
Item Five Ante turpis integer aliquet porttitor. 29.99
100.00

Buttons

  • Disabled
  • Disabled

Form