Research-Stack/5-Applications/text-to-cad/models/mobile_microgripper.py

219 lines
8.4 KiB
Python

"""
Mobile Microgripper (MMG) - Claw-like robotic gripper for cell spheroid bioassembly
Based on: Purdue University research (APL Bioengineering, 2026)
Reference: https://phys.org/news/2026-04-mobile-microgrippers-cells.html
Design:
- Two-arm claw structure connected by hinge
- Magnetic field control (biocompatible)
- Force-sensing with real-time adjustment
- Handles fragile cell spheroids (3D cell clumps)
- Microscopic scale
Integration with Research Stack:
- Buckyball-MOF QCA composite materials
- FAMM frustration physics for assembly control
- Magnetic field steering (MoonRF-adapted phased arrays)
"""
import build123d as bd
import trimesh
import numpy as np
import math
import json
from typing import List, Tuple, Optional
# Microgripper Geometry Parameters (microscopic scale, scaled up for CAD visualization)
SCALE_FACTOR = 1000.0 # Scale from micrometers to millimeters for visualization
ARM_LENGTH = 200.0 / SCALE_FACTOR # 200 μm arm length
ARM_WIDTH = 30.0 / SCALE_FACTOR # 30 μm arm width
ARM_THICKNESS = 15.0 / SCALE_FACTOR # 15 μm arm thickness
HINGE_RADIUS = 10.0 / SCALE_FACTOR # 10 μm hinge radius
HINGE_WIDTH = 40.0 / SCALE_FACTOR # 40 μm hinge width
GRIPPER_GAP_OPEN = 100.0 / SCALE_FACTOR # 100 μm open gap
GRIPPER_GAP_CLOSED = 20.0 / SCALE_FACTOR # 20 μm closed gap
# Magnetic Control Parameters
MAGNETIC_COATING_THICKNESS = 5.0 / SCALE_FACTOR # 5 μm Fe3O4 coating
MAGNETIC_FIELD_STRENGTH = 1.2 # Tesla (from buckyball-MOF spec)
# Force-Sensing Parameters
SENSOR_THICKNESS = 10.0 / SCALE_FACTOR # 10 μm piezoresistive sensor
SENSOR_LOCATION = 0.8 # 80% along arm from hinge
class MicrogripperGeometry:
"""Geometry parameters for mobile microgripper."""
def __init__(self):
self.arm_length = ARM_LENGTH
self.arm_width = ARM_WIDTH
self.arm_thickness = ARM_THICKNESS
self.hinge_radius = HINGE_RADIUS
self.hinge_width = HINGE_WIDTH
self.gap_open = GRIPPER_GAP_OPEN
self.gap_closed = GRIPPER_GAP_CLOSED
self.magnetic_coating = MAGNETIC_COATING_THICKNESS
self.sensor_thickness = SENSOR_THICKNESS
self.sensor_location = SENSOR_LOCATION
def create_arm(geometry: MicrogripperGeometry, angle_offset: float = 0.0) -> list:
"""Create a single gripper arm with hinge and magnetic coating - returns list of shapes."""
shapes = []
# Arm body (rectangular prism)
arm_body = bd.Box(
length=geometry.arm_length,
width=geometry.arm_width,
height=geometry.arm_thickness
)
arm_body = arm_body.move(bd.Location((-geometry.arm_length/2, 0, 0)))
shapes.append(arm_body)
# Add hinge (cylinder)
hinge = bd.Cylinder(
radius=geometry.hinge_radius,
height=geometry.hinge_width,
align=bd.Align.CENTER
)
hinge = hinge.rotate(axis=bd.Axis((0, 0, 0), (0, 1, 0)), angle=90)
hinge = hinge.move(bd.Location((0, 0, 0)))
shapes.append(hinge)
# Add magnetic coating (thin layer on arm surface)
coating = bd.Box(
length=geometry.arm_length,
width=geometry.arm_width,
height=geometry.magnetic_coating
)
coating = coating.move(bd.Location((-geometry.arm_length/2, 0, geometry.arm_thickness/2)))
shapes.append(coating)
# Add force sensor (piezoresistive strip)
sensor_length = geometry.arm_length * 0.2
sensor_x = -geometry.arm_length * geometry.sensor_location
sensor = bd.Box(
length=sensor_length,
width=geometry.arm_width * 0.8,
height=geometry.sensor_thickness
)
sensor = sensor.move(bd.Location((sensor_x, 0, geometry.arm_thickness/2 + geometry.magnetic_coating)))
shapes.append(sensor)
# Rotate all shapes if angle_offset is specified
if angle_offset != 0:
shapes = [s.rotate(axis=bd.Axis((0, 0, 0), (0, 0, 1)), angle=angle_offset) for s in shapes]
return shapes
def create_microgripper(geometry: MicrogripperGeometry, grip_state: str = "open") -> bd.BuildPart:
"""Create complete mobile microgripper with two arms."""
# Calculate arm angles based on grip state
if grip_state == "open":
arm_angle = math.degrees(math.atan(geometry.gap_open / (2 * geometry.arm_length)))
elif grip_state == "closed":
arm_angle = math.degrees(math.atan(geometry.gap_closed / (2 * geometry.arm_length)))
else:
arm_angle = 0.0
with bd.BuildPart() as gripper:
# Left arm (rotated -arm_angle)
left_arm_shapes = create_arm(geometry, angle_offset=-arm_angle)
for shape in left_arm_shapes:
bd.add(shape)
# Right arm (rotated +arm_angle)
right_arm_shapes = create_arm(geometry, angle_offset=+arm_angle)
for shape in right_arm_shapes:
bd.add(shape)
# Add magnetic control coils (simplified representation)
# Four coils for phased array steering (MoonRF-adapted)
coil_radius = 5.0 / SCALE_FACTOR
coil_positions = [
(-geometry.arm_length * 0.3, geometry.gap_open/2, geometry.arm_thickness/2 + 5.0/SCALE_FACTOR),
(-geometry.arm_length * 0.3, -geometry.gap_open/2, geometry.arm_thickness/2 + 5.0/SCALE_FACTOR),
(geometry.arm_length * 0.3, geometry.gap_open/2, geometry.arm_thickness/2 + 5.0/SCALE_FACTOR),
(geometry.arm_length * 0.3, -geometry.gap_open/2, geometry.arm_thickness/2 + 5.0/SCALE_FACTOR),
]
for pos in coil_positions:
coil = bd.Cylinder(radius=coil_radius, height=geometry.hinge_width, align=bd.Align.CENTER)
coil = coil.rotate(axis=bd.Axis((0, 0, 0), (0, 1, 0)), angle=90)
coil = coil.move(bd.Location(pos))
bd.add(coil)
return gripper
def gen_part():
"""Generate the Mobile Microgripper CAD model for text-to-cad."""
geometry = MicrogripperGeometry()
# Create microgripper in open state
gripper = create_microgripper(geometry, grip_state="open")
return gripper
if __name__ == "__main__":
print("Generating Mobile Microgripper CAD Model...")
print(f"Scale Factor: {SCALE_FACTOR}x (μm → mm)")
print(f"Arm Length: {ARM_LENGTH/SCALE_FACTOR:.1f} μm")
print(f"Arm Width: {ARM_WIDTH/SCALE_FACTOR:.1f} μm")
print(f"Hinge Radius: {HINGE_RADIUS/SCALE_FACTOR:.1f} μm")
print(f"Open Gap: {GRIPPER_GAP_OPEN/SCALE_FACTOR:.1f} μm")
print(f"Closed Gap: {GRIPPER_GAP_CLOSED/SCALE_FACTOR:.1f} μm")
print(f"Magnetic Coating: {MAGNETIC_COATING_THICKNESS/SCALE_FACTOR:.1f} μm")
print(f"Sensor Thickness: {SENSOR_THICKNESS/SCALE_FACTOR:.1f} μm")
gripper = gen_part()
print(f"Created CAD model")
# Export geometry as JSON for physics simulation
output_json = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/mobile_microgripper.json"
geometry_data = {
"type": "mobile_microgripper",
"scale_factor": SCALE_FACTOR,
"dimensions": {
"arm_length_um": ARM_LENGTH,
"arm_width_um": ARM_WIDTH,
"arm_thickness_um": ARM_THICKNESS,
"hinge_radius_um": HINGE_RADIUS,
"hinge_width_um": HINGE_WIDTH,
"gap_open_um": GRIPPER_GAP_OPEN,
"gap_closed_um": GRIPPER_GAP_CLOSED
},
"magnetic_control": {
"coating_thickness_um": MAGNETIC_COATING_THICKNESS,
"field_strength_tesla": MAGNETIC_FIELD_STRENGTH,
"coil_count": 4,
"control_method": "phased_array_magnetic_field"
},
"force_sensing": {
"sensor_thickness_um": SENSOR_THICKNESS,
"sensor_location_fraction": SENSOR_LOCATION,
"sensor_type": "piezoresistive"
},
"integration": {
"buckyball_mof_composite": True,
"famm_frustration_control": True,
"moonrf_phased_array": True
},
"references": [
"Purdue University APL Bioengineering (2026)",
"BUCKYBALL_MOF_QCA_SPEC.md",
"BUCKYBALL_FAMM_TORSIONAL_FLUID.md",
"ENERGY_EXTRACTION_COMPRESSION_BUCKYBALL.md"
]
}
with open(output_json, 'w') as f:
json.dump(geometry_data, f, indent=2)
print(f"Exported geometry JSON to: {output_json}")
print("\nMobile Microgripper CAD model generation complete!")
print("Note: STEP export requires skill tool. Use: ./.venv/bin/python skills/cad/scripts/gen_step_part models/mobile_microgripper.py")