Posts Tagged M2

Frank-O-Squid Configuration

My old Thing-O-Matic has new life as the Frank-O-Squid at Squidwrench Galactic HQ, with all the original Makerbot electronics replaced by an Azteeg X3 controller. Over the last several weeks I’ve coaxed it into doing most of the right things at the proper speeds & feeds, so we can now move on to actually making stuff:

Frank-o-Squid in action

Frank-o-Squid in action

The warping on that little digital caliper thumbwheel holder show that I don’t have the tiny-object slowdown settings quite correct, but it’s getting close.

The Marlin firmware is on GitHub. I intended to set it up so that pulling changes from upstream Marlin would be easy, but totally blundered something along the way. I’ll eventually plug the changes from Configuration.h, Configuration_adv.h, and pins.h into a clean branch and start over, but, for now, we’re slowly diverging from consensus reality.

Although the platform still has the Z-min switch over on the right edge, neither the firmware nor Slic3r pay any attention to it. A stub in the startup G-Code sequence does a head fake toward the switch, but doesn’t actually probe it.

I scrapped the original craptastic Makerbot ATX power supply and replaced it with Makergear’s huge 12 V laptop brick that powered the original M2 platform, so the thermal switches on the extruder no longer do anything useful; it’s running bare, pretty much like all other 3D printers.

The Slic3r configuration exports thusly:

# generated by Slic3r 1.0.0RC1 on Mon Mar  3 07:48:29 2014
avoid_crossing_perimeters = 0
bed_size = 105,120
bed_temperature = 100
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_fan_speed = 100
bridge_flow_ratio = 1
bridge_speed = 40
brim_width = 1.0
complete_objects = 0
cooling = 1
default_acceleration = 0
disable_fan_first_layers = 1000
duplicate = 1
duplicate_distance = 6
duplicate_grid = 1,1
end_gcode = ;---- end.gcode starts ----\n; TOM 286 - Al plates + Geared extruder\n; Ed Nisley - KE4ZNU - January 2014\n; Marlin with tweaks for Azteeg X3 with thermocouple\n;- inhale filament blob\nG91\nG1 E-5 F900\nG90\n;- turn off heaters\nM104 S0         ; extruder head\nM140 S0         ; HBP\n;- move to eject position\nG0 Z115 F1000   ; home Z to get nozzle away from object\n;G92 Z115      ; reset Z\nG1 X0 F6000     ; center X axis\nG1 Y35          ; move Y stage forward\n;---- end.gcode ends ----
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extruder_offset = 0x0
extrusion_axis = E
extrusion_multiplier = 0.95
extrusion_width = 0.50
fan_always_on = 0
fan_below_layer_time = 1
filament_diameter = 2.95
fill_angle = 45
fill_density = 0.15
fill_pattern = honeycomb
first_layer_acceleration = 0
first_layer_bed_temperature = 100
first_layer_extrusion_width = 0.50
first_layer_height = 0.25
first_layer_speed = 10
first_layer_temperature = 210
g0 = 0
gap_fill_speed = 30
gcode_arcs = 0
gcode_comments = 0
gcode_flavor = reprap
infill_acceleration = 0
infill_every_layers = 2
infill_extruder = 1
infill_extrusion_width = 0.50
infill_first = 1
infill_only_where_needed = 1
infill_speed = 50
layer_gcode =
layer_height = 0.25
max_fan_speed = 100
min_fan_speed = 35
min_print_speed = 10
min_skirt_length = 3
notes =
nozzle_diameter = 0.4
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0.50
perimeter_speed = 30
perimeters = 1
post_process =
print_center = 0,0
raft_layers = 0
randomize_start = 1
resolution = 0.05
retract_before_travel = 0.0
retract_layer_change = 0
retract_length = 0.75
retract_length_toolchange = 10
retract_lift = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 30
rotate = 0
scale = 1
skirt_distance = 2
skirt_height = 1
skirts = 1
slowdown_below_layer_time = 30
small_perimeter_speed = 50%
solid_fill_pattern = rectilinear
solid_infill_below_area = 5
solid_infill_every_layers = 0
solid_infill_extrusion_width = 0.50
solid_infill_speed = 150%
spiral_vase = 0
standby_temperature_delta = -5
start_gcode = ;---- start.gcode begins ----\n; TOM 286 - Al plates + Geared extruder + Zmin platform sense\n; Ed Nisley - KE4ZNU - January 2014\n; Marlin with tweaks for Azteeg X3 with thermocouple\n;\n; Set initial conditions\nG21                 ; set units to mm\nG90                 ; set positioning to absolute\n;----------\n; Begin heating\nM104 S[first_layer_temperature]         ; extruder head\nM140 S[first_layer_bed_temperature]	; start bed heating\n;----------\n; Home axes\nG28 X0 Y0 Z0\nG92 X-53.5 Y-58.5 Z114.5\n;----------\n; Initial nozzle wipe to clear snot for Z touchoff\nG1 X0 Y0 Z3.0 F1000     ; pause at center to build confidence\nG4 P1000\nG1 Z10                  ; ensure clearance\nG1 X39 Y-58.0 F1000    ; move to front, avoid wiper blade\nG1 X55                  ; to wipe station\nG1 Z6.0                 ; to wipe level\nM116                    ; wait for temperature settling\nG1 Y-45 F500            ; slowly wipe nozzle\n;-----------------------------------------------\n; Z platform height touchoff\n; Make sure the XY position is actually over the switch!\n; Home Z downward to platform switch\n; Compensate for 0.05 mm backlash in G92: make it 0.05 too low\nG1 X56.0 Y8.2 F5000\nG1 Z4.0 F1000     ; get over build platform switch\n;G1 Z0 F50                    ; home downward very slowly\n;G92 Z1.45                    ; set Z-min switch height\nG1 Z6.0 F1000                ; back off switch to wipe level\n;-----------------------------------------------\n; Prime extruder to stabilize initial pressure\nG1 X55 Y-45 F5000   ; set up for wipe from rear\nG1 Y-58.0 F500      ; wipe to front\nG91                 ; use incremental motion for extrusion\nG1 F100               ; set decent rate\nG1 E10              ; extrude enough to get good pressure\nG1 F2000            ; set for fast retract\nG1 E-1.0            ; retract\nG90                 ; back to absolute motion\nG1 Y-45 F1000       ; wipe nozzle to rear\n;----------\n; Set up for Skirt start in right front corner\n; Compensate for Z backlash: move upward from zero point\nG1 X40 Y-40 F5000\nG1 Z0.0 F1000     ; kiss platform\nG1 Z0.2 F1000       ; take up Z backlash to less than thread height\n;G92 E1.0            ; preset to avoid huge un-Reversal blob\n;G1 X0 Y0\n;---- start.gcode ends ----
start_perimeters_at_concave_points = 1
start_perimeters_at_non_overhang = 1
support_material = 0
support_material_angle = 0
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0.50
support_material_interface_extruder = 1
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_pattern = honeycomb
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 0
temperature = 210
thin_walls = 1
threads = 2
toolchange_gcode =
top_infill_extrusion_width = 0.50
top_solid_infill_speed = 50%
top_solid_layers = 3
travel_speed = 150
use_firmware_retraction = 0
use_relative_e_distances = 0
vibration_limit = 0
wipe = 0
z_offset = 0

All of that should become three TOM286 - Default sub-profiles.

The Pronterface configuration looks like this:

set port /dev/ttyUSB0
set monitor True
set last_bed_temperature 100.0
set last_temperature 210.0
set baudrate 115200
set temperature_abs 210
set xy_feedrate 5000
set z_feedrate 1000
set build_dimensions 110.00x120.00x117.00+0.00+0.00+0.00+0.00+0.00+0.00
set extruders 1
set slic3rintegration True
set tempgauges True
set preview_extrusion_width 0.4
set e_feedrate 100
set last_extrusion 3
set last_file_path /home/ed/Documents/Thing-O-Matic/Calibration/Thread Thickness
set recentfiles ["/home/ed/Documents/Thing-O-Matic/Calibration/Thread Thickness/Caliper Thumbwheel Holder.gcode", "/home/ed/Documents/Thing-O-Matic/Calibration/Thread Thickness/Thinwall Open Box.gcode", "/home/ed/Documents/Thing-O-Matic/Calibration/Thread Thickness/Platform Level.gcode", "/home/ed/Documents/Thing-O-Matic/Calibration/Circle Diameter Calibration/Small Circle Cal - M2 0.2 mm.gcode", "/home/ed/Documents/Thing-O-Matic/Calibration/Circle Diameter Calibration/Small Circle Cal - TOM.gcode"]

As you can see, it’s all running from a directory on my old laptop. The next step involves migrating everything to a dedicated PC next to the printer, so nobody else need worry about this stuff…

About these ads

, , ,

6 Comments

Sewing Machine Lights: LED Strip Mount Solid Models

Mary’s Sears Kenmore Model 158 sewing machine arm has a flat rear surface and a plastic plate on the front, so double-sided adhesive foam tape can hold a straight mount in place; we rejected putting strips under the arm to avoid snagging on the quilts as they pass by. So, with LEDs in hand, these are the mounts…

LED strip lights must have strain relief for their wires, as our Larval Engineer discovered the hard way on her longboard ground lighting project, and I wanted nice endcaps to avoid snagging on the fabric, so the general idea was a quarter-round rod with smooth endcaps and a hole to secure the wire. Some experiments showed that the acrylic (?) LED encapsulation directed the light downward, thus eliminating the need for a shade.

So, something like this will do for a first pass:

LED Strip Light Mount - bottom view

LED Strip Light Mount – bottom view

The overall dimensions for the LED mounts:

  • Length: N x 25 mm, plus endcap radii
  • Front-to-back width: 10 mm to allow for strip variation and 1 mm protection
  • Top-to-bottom height: 12 mm to fit double-sided foam sticky squares
  • Wire channels: 3 mm diameter or square cross-section

If there’s not enough light, I think a double-wide mount with two parallel LED strips would work.

After a bit of screwing around with additive endcaps that produced catastrophically non-manifold solid models, I figured out the proper subtractive way to build the mounts: the endcaps actually define the overall shape of the mount.

Start by placing a pair of spheroids, with radii matching the strip dimensions, so that their outer poles match the desired overall length:

Strip Light Mount - end cap spheroids - whole

Strip Light Mount – end cap spheroids – whole

The north/south poles must face outward, so that the equal-angle facets along the equators match up with what will become the mount body: rotate the spheroids 90° around the Y axis. The centers lie at the ends of the LED segments; the model shown here has a single 25 mm segment.

Then hack off three quadrants:

Strip Light Mount - end cap spheroids

Strip Light Mount – end cap spheroids

That leaves two orange-segment shapes that define the endcaps:

Strip Light Mount - end caps - shaped

Strip Light Mount – end caps – shaped

Here’s the key step that took me far too long to figure out. Shrinkwrapping the endcaps with the hull() function finesses the problem of matching the body facets to the endcap facets:

Strip Light Mount - end caps - hull

Strip Light Mount – end caps – hull

Model the wire channels as positive volumes that will be subtracted from the mount. The Channels layout shows both channels separated by a short distance:

Strip Light Mount - positive wire channels

Strip Light Mount – positive wire channels

The horizontal hexagons started as squares, but that looked hideous on the rounded endcaps.

Seen from the bottom, the mount starts like this:

Strip Light Mount - no wiring channels

Strip Light Mount – no wiring channels

Position and subtract a wire channel:

Strip Light Mount - visible wire channel

Strip Light Mount – visible wire channel

Which leaves the final solid model as a single, manifold object:

Strip Light Mount - complete

Strip Light Mount – complete

The module generating the mount takes three parameters: the number of LED segments and two string variables that determine whether to punch a channel in each endcap. Instantiate the module three times with suitable parameters to get a trio of LED mounts, all laid out for 3D printing:

Strip Light Mount - build layout

Strip Light Mount – build layout

They built just exactly like those models would suggest; the M2 produces dependable results.

The OpenSCAD source code:

// LED Strip Lighting Brackets for Kenmore Model 158 Sewing Machine
// Ed Nisley - KE4ZNU - February 2014

Layout = "Strip";			// Build Show Channels Strip

//- Extrusion parameters must match reality!
//  Print with 2 shells and 3 solid layers

ThreadThick = 0.20;
ThreadWidth = 0.40;

HoleWindage = 0.2;			// extra clearance

Protrusion = 0.1;			// make holes end cleanly

AlignPinOD = 1.70;			// assembly alignment pins: filament dia

inch = 25.4;

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);

//----------------------
// Dimensions

Segment = [25.0,10.0,3.0];		//  size of each LED segment

WireChannel = 3.0;				// wire routing channel

StripHeight = 12.0;				// sticky tape width
StripSides = 8*4;

DefaultLayout = [1,"Wire","NoWire"];

EndCap = [(2*WireChannel + 1.0),Segment[1],StripHeight];	// radii of end cap spheroid
EndCapSides = StripSides;

CapSpace = 2.0;						// build spacing for endcaps
BuildSpace = 1.5*Segment[1];		// spacing between objects on platform

//----------------------
// Useful routines

module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes

  Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);

  FixDia = Dia / cos(180/Sides);

  cylinder(r=(FixDia + HoleWindage)/2,
           h=Height,
           $fn=Sides);
}

module ShowPegGrid(Space = 10.0,Size = 1.0) {

  RangeX = floor(100 / Space);
  RangeY = floor(125 / Space);

	for (x=[-RangeX:RangeX])
	  for (y=[-RangeY:RangeY])
		translate([x*Space,y*Space,Size/2])
		  %cube(Size,center=true);

}

//-- The negative space used to thread wires into the endcap

module MakeWireChannel(Which = "Left") {

	HalfSpace = EndCap[0] * ((Which == "Left") ? 1 : -1);

	render(convexity=2)
	translate([0,EndCap[1]/3,0])
		intersection() {
			union() {
				cube([2*WireChannel,WireChannel,EndCap[2]],center=true);
				translate([-2*EndCap[0],0,EndCap[2]/2])
					rotate([0,90,0]) rotate(180/6)
						PolyCyl(WireChannel,4*EndCap[0],6);
			}
			translate([HalfSpace,0,(EndCap[2] - Protrusion)]) {
				cube(2*EndCap,center=true);
			}
		}
}

//-- The whole strip, minus wiring channels

module MakeStrip(Layout = DefaultLayout) {

	BarLength = Layout[0] * Segment[0];				// central bar length

	hull()
		difference() {
			for (x = [-1,1])						// endcaps as spheroids
				translate([x*BarLength/2,0,0])
					resize(2*EndCap) rotate([0,90,0]) sphere(1.0,$fn=EndCapSides);
			translate([0,0,-EndCap[2]])
				cube([2*BarLength,3*EndCap[1],2*EndCap[2]],center=true);
			translate([0,-EndCap[1],0])
				cube([2*BarLength,2*EndCap[1],3*EndCap[2]],center=true);
		}

}

//-- Cut wiring channels out of strip

module MakeMount(Layout = DefaultLayout) {

	BarLength = Layout[0] * Segment[0];

	difference() {
		MakeStrip(Layout);
		if (Layout[1] == "Wire")
			translate([BarLength/2,0,0])
				MakeWireChannel("Left");
		if (Layout[2] == "Wire")
			translate([-BarLength/2,0,0])
				MakeWireChannel("Right");
	}
}

//- Build it

ShowPegGrid();

if (Layout == "Channels") {
	translate([ EndCap[0],0,0]) MakeWireChannel("Left");
	translate([-EndCap[0],0,0]) MakeWireChannel("Right");
}

if (Layout == "Strip") {
	MakeStrip(DefaultLayout);
}

if (Layout == "Show") {
	MakeMount(DefaultLayout);
}

if (Layout == "Build") {

	translate([0,BuildSpace,0]) MakeMount([1,"Wire","Wire"]);		// rear left side, vertical
	translate([0,0,0]) MakeMount([5,"Wire","NoWire"]);				// rear top, across arm
	translate([0,-BuildSpace,0]) MakeMount([6,"NoWire","Wire"]);	// front top, across arm
}

The original design doodles, which bear a vague resemblance to the final mounts:

LED Strip Light Mounts - Original Design Sketches

LED Strip Light Mounts – Original Design Sketches

The little snood coming out of the top would hide a wire going through a hole drilled in the capital-S of “Sears” on the front panel, but I came to my senses long before implementing that idea…

,

2 Comments

Thing-O-Matic Y-axis Idler Support Bracket: Oops

The STL file from CampbellsBot’s Y-Axis Idler Support Bracket printed without incident (admittedly, on the M2):

Thing-O-Matic Y-axis Idler Support Bracket

Thing-O-Matic Y-axis Idler Support Bracket

Come to find out that Makerbot changed the spacing between the Y-axis rod and the idler bolt, so it doesn’t fit the TOM286. I could fire up the Token Windows Box, install Sketchup, modify the model, rebuild and clean up the STL, and try again, but it’s easier to just give up. The TOM286 has worked fine so far, so maybe this isn’t really needed.

Ah, well, it’s another show-n-tell doodad…

, ,

4 Comments

Chocolate Molds: Positives Ready

After all the height map tweaking, Slic3r duplicated the Tux and SqWr STL positive models, distributed them on the platform, and the small molds printed out easily enough:

Tux SqWr positive molds - as built

Tux SqWr positive molds – as built

The larger pin plate wasn’t quite as successful. Despite what this might look like, that’s the same black PLA as the smaller molds:

Mold peg plate - repaired

Mold peg plate – repaired

I used 10% infill density, which was structurally good enough for a very light slab, but it left large gaps near the side walls that the top fill didn’t quite cover. Part of the problem was that the walls, being cylindrical sections, kept overhanging toward the inside, leaving the top fill nothing to grab around the nearly tangential perimeter. I think printing the slab upside-down, with the top surface against the platform, would solve that problem and also produce a glass-smooth surface under the positive molds.

I took the easy way out by troweling JB KwikWeld epoxy into the holes, smoothing it, and sanding the surfaces more-or-less smooth. That should suffice to cast the negative mold in silicone over everything, but it sure ain’t pretty:

Mold plate with Tux SqWr positives in place

Mold plate with Tux SqWr positives in place

The molds are just sitting on their pegs and haven’t been taped in place; the lower-left Tux appears to be making a break for freedom.

The Mighty Thor will do the silicone negative mold… and the further I stay away from the chocolate tempering & pouring process, the better it’ll be for all parties concerned.

 

,

1 Comment

Sewing Machine Bulb: LED Replacement Doodle

Mary wants more light directly around the needle of her Kenmore Model 158 sewing machine, as the existing light (a 120 V 15 W incandescent bulb tucked inside the end housing) casts more of a diffuse glow than a directed beam:

Kenmore Model 158 Sewing Machine - lamp

Kenmore Model 158 Sewing Machine – lamp

The end cap fits snugly around the bulb, but I thought a pair of 10 mm white LEDs, mounted side-by-side and aimed downward at the cover plate, would work. Of course, plugging a pair of white LEDs into a 120 VAC socket won’t work, but some judicious rewiring and a new 12 V DC wall wart will take care of that.

The bulb has a dual-contact bayonet base, with both pins isolated from the shell and connected to the non-polarized (!) line cord through the power switch. I didn’t know it was called a BA15d base, but now I do.

A 12 V automotive brake/taillight bulb (type 1157, I think) pulled from the Big Box o’ Bulbs has a slightly different pin arrangement that keys the filaments (which are not isolated from the shell) to the surrounding reflector:

BA15d Bayonet Bulb Bases - 120V vs. 12V pins

BA15d Bayonet Bulb Bases – 120V vs. 12V pins

So I conjured a mockup to see if it would fit, using 2-56 screws to mimic whatever hardware might be practical:

BA15d Bulb - LED Adapter

BA15d Bulb – LED Adapter

The solid model shows how it all fits together:

Sears Lamp LED Adapter - Show view

Sears Lamp LED Adapter – Show view

The two tiny ruby-red pins represent filament snippets in alignment holes, barely visible in real life:

LED holder parts

LED holder parts

I glued those pieces together, using a tiny machinist’s square as a jig to keep them perpendicular:

LED holder clamping

LED holder clamping

Some random 10 mm LEDs served for testing:

BA15d Bulb - 10 mm LEDs

BA15d Bulb – 10 mm LEDs

It actually fit pretty well, ignoring the fact that the LEDs point 90° from the intended direction (so I could see how the holes came out inside the pivot, honest), and lit up the area quite well, but it’s such a delicate affair that removing the entire socket and replacing it with a dedicated metal bracket / heatsink for two high-power SMD LEDs will be better.

The OpenSCAD source code:

// Adapter for LEDs in Sears sewing machine lamp socket
// Ed Nisley - KE4ZNU - January 2014

Layout = "Show";		// Build Show LEDTab LEDPlate ShellMount

//- Extrusion parameters must match reality!
//  Print with 2 shells and 3 solid layers

ThreadThick = 0.20;
ThreadWidth = 0.40;

HoleWindage = 0.2;			// extra clearance

Protrusion = 0.1;			// make holes end cleanly
Gap = 2.0;					// spacing between Show parts

AlignPinOD = 1.70;			// assembly alignment pins: filament dia

inch = 25.4;

//----------------------
// Dimensions

//-- LED mounting plate

LEDDia = 10.0;				// LED case OD
LEDFlangeOD = 10.7;

LEDPlateThick = 2.0;		// mounting plate thickness
LEDMargin = 2.0;

LEDSpaceOC = LEDDia + LEDMargin;		// LED center-to-center distance (single margin between!)

LEDTabLength = 15.0;		// base to screw hole center

LEDTabThick = 4.0;			// tab with hole for mounting screw
LEDTabScrewOD = 2.0;
LEDTabWidth = (3.0*2) + LEDTabScrewOD;

LEDMountHeight = 25.0;		// estimated mounting screw centerline to bottom of LEDs

//-- Lamp base adapter
//		hard inch dimensions!

ShellOD = 0.600 * inch;				// dia of metallic shell
ShellOAL = 0.66 * inch;				//  ... total length
ShellInsert = 7/16 * inch;			//  ... length engaging socket

ShellSides = 4*4;

BulbOD = 0.75 * inch;				// glass bulb
BulbLength = 1.14 * inch;

InsulOD = 0.485 * inch;				// insulating stub around contact pins
InsulThick = 0.070 * inch;			//  ... beyond end of shell

ContactOD = 2.0;					// contact holes through base (not heads)
ContactOC = 0.300 * inch;			//  ... center-to-center spacing

BayonetOD = 0.080 * inch;			// bayonet pin diameter
BayonetOffset = 0.125 * inch;		// from end of metal base

LampOAL = InsulThick + ShellOAL + BulbLength;
echo(str("Overall Length: ",LampOAL));

//-- Miscellany

//----------------------
// Useful routines

module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes

  Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);

  FixDia = Dia / cos(180/Sides);

  cylinder(r=(FixDia + HoleWindage)/2,
           h=Height,
           $fn=Sides);
}

module ShowPegGrid(Space = 10.0,Size = 1.0) {

  Range = floor(50 / Space);

	for (x=[-Range:Range])
	  for (y=[-Range:Range])
		translate([x*Space,y*Space,Size/2])
		  %cube(Size,center=true);
}

//-- Tab for screw mounting LED holder
//		AddLength remains below Z=0 for good union

module LEDTab() {

	difference() {
		linear_extrude(height=LEDTabThick)
			hull() {
				circle(d=LEDTabWidth);
				translate([LEDTabLength/2,0,0])
					square([LEDTabLength,LEDTabWidth],center=true);
			}
		translate([0,0,-Protrusion])
			rotate(180/6)
				PolyCyl(LEDTabScrewOD,(LEDTabThick + 2*Protrusion),6);
		for (i=[-1,1])
			translate([LEDTabLength/2,i*LEDTabWidth/4,LEDTabThick/2])
				rotate([0,90,0]) rotate(180/4)
					PolyCyl(AlignPinOD,(LEDTabLength/2 + Protrusion),4);
	}
}

//-- Plate holding LEDs

module LEDPlate() {

	difference() {
		union() {
			linear_extrude(height=LEDPlateThick)
				hull() {
					for (i=[-1,1])
						translate([i*LEDSpaceOC/2,0,0])
							circle(d=(LEDDia + 2*LEDMargin));
					translate([0,(LEDFlangeOD/2 + LEDTabWidth/2),0])
						square([LEDTabThick,LEDTabWidth],center=true);
				}
		}
		for (i=[-1,1])
			translate([i*LEDSpaceOC/2,0,-Protrusion])
				rotate(180/12)
					PolyCyl(LEDDia,(LEDPlateThick + 2*Protrusion),12);
		for (i=[-1,1])
			translate([0,(i*LEDTabWidth/4 + LEDFlangeOD/2 + LEDTabWidth/2),3*ThreadThick]) rotate(180/4)
				PolyCyl(AlignPinOD,(LEDTabLength/2 + Protrusion),4);

	}
}

//-- Bulb shell mounting adapter

module ShellMount() {

	difference() {
		union() {
			cylinder(r1=InsulOD/2,r2=ShellOD/2,h=(InsulThick + Protrusion),$fn=ShellSides);
			translate([0,0,InsulThick])
				cylinder(r=ShellOD/2,h=(LampOAL - LEDMountHeight + LEDTabWidth/2),$fn=ShellSides);
		}

		translate([0,ShellOD,(InsulThick + BayonetOffset)])		// bayonet pin hole
			rotate([90,0,0]) rotate(180/4)
				PolyCyl(BayonetOD,2*ShellOD,4);

		translate([0,ShellOD,(InsulThick + LampOAL - LEDMountHeight)])		// LED mount screw hole
			rotate([90,0,0])
				PolyCyl(LEDTabScrewOD,2*BulbOD,6);

		translate([0,0,(InsulThick + ShellOAL + LampOAL/2)])		// slot for LEDTab mount
			cube([2*ShellOD,(LEDTabThick + 2*Protrusion),LampOAL],center=true);

		for (i=[-1,1])											// contact pin holes
			translate([i*ContactOC/2,0,-Protrusion])
				rotate(180/6)
					PolyCyl(ContactOD,2*LampOAL,6);
	}

}

//- Build it

ShowPegGrid();

if (Layout == "LEDTab")
	LEDTab();

if (Layout == "LEDPlate")
	LEDPlate();

if (Layout == "ShellMount")
	ShellMount();

if (Layout == "Show") {
	LEDPlate();
	translate([-LEDTabThick/2,(LEDFlangeOD/2 + LEDTabWidth/2),(LEDTabLength + LEDPlateThick + Gap)])
		rotate([0,90,0])
			LEDTab();
	for (i=[-1,1])
#	translate([0,(i*LEDTabWidth/4 + LEDFlangeOD/2 + LEDTabWidth/2),(LEDPlateThick + Gap/4)])
		rotate(180/4)
		cylinder(r=AlignPinOD/2,h=Gap/1,$fn=4);		// fake the pins

	translate([0,(LEDFlangeOD/2 + LEDTabWidth/2),(LampOAL - LEDTabWidth/2)])
		rotate([0,180,0]) rotate(90)
			ShellMount();
}

if (Layout == "Build") {
	translate([0,LEDDia,0])
		LEDPlate();

	translate([-10,-(LEDMargin + LEDTabWidth),0])
		rotate(-90)
			LEDTab();

	translate([10,-(LEDMargin + LEDTabWidth),0])
		ShellMount();
}

The original doodles for the bulb dimensions and adapter layout:

Bulb dimensions - adapter doodles

Bulb dimensions – adapter doodles

,

4 Comments

Chocolate Molds: Software Stack

This derives directly from the cookie cutter / press stack, so check that series for more background and explanation. Some height map thoughts and preliminary doodling led up to this.

We start with a tiny grayscale image file that defines the height of each point in the mold:

Tux

Tux

Feed that file into a Bash script:

./MakeMold.sh Tux.png

And a corresponding STL file pops out:

Tux positive mold - solid model - oblique

Tux positive mold – solid model – oblique

The MakeMold Bash script orchestrates the whole thing:

#!/bin/bash
DotsPerMM=3.0
MapHeight=5
ImageName="${1%%.*}"
rm ${ImageName}_* ${ImageName}-positive.stl
echo Normalize and prepare grayscale image...
convert $1 -type Grayscale -depth 8 -trim +repage -flip +set comment ${ImageName}_prep.png
echo Create PGM files...
convert ${ImageName}_prep.png -compress none ${ImageName}_map.pgm
convert ${ImageName}_prep.png -white-threshold 1 -compress none ${ImageName}_plate.pgm
echo Create height map data files...
ImageX=`identify -format '%[fx:w]' ${ImageName}_map.pgm`
ImageY=`identify -format '%[fx:h]' ${ImageName}_map.pgm`
echo Width: ${ImageX} x Height: ${ImageY}
cat ${ImageName}_map.pgm   | tr -s ' \012' '\012' | tail -n +5 | column -x -c $((8*$ImageX)) > ${ImageName}_map.dat
cat ${ImageName}_plate.pgm | tr -s ' \012' '\012' | tail -n +5 | column -x -c $((8*$ImageX)) > ${ImageName}_plate.dat
echo Create mold positive...
time openscad -D fnPlate=\"${ImageName}_plate.dat\" \
-D fnMap=\"${ImageName}_map.dat\" -D Height=$MapHeight \
-D ImageX=$ImageX -D ImageY=$ImageY -D DotsPerMM=$DotsPerMM \
-o ${ImageName}-positive.stl MoldPositive.scad

The first convert normalizes the grayscale file and produces a PNG file in a standard format.

The next two convert operations translate that PNG file into uncompressed PGM files with the data as ASCII text required by OpenSCAD’s surface() function. It’s not in the proper format, however, so a few lines of Bash-fu rearrange the data into DAT files; the extension is arbitrary.

Then OpenSCAD eats those files along with a bunch of configuration settings and spits out a solid model of the positive mold in STL format.

The MakePositive.scad OpenSCAD source code:

// Mold positive pattern from grayscale height map using Minkowski sum
// Ed Nisley KE4ZNU - February 2014 - adapted from cookie press, added alignment pins

//-----------------
// Mold files

fnMap = "SqWr_map.dat";					// override with -D 'fnMap="whatever.dat"'
fnPlate = "SqWr_plate.dat";				// override with -D 'fnPlate="whatever.dat"'

DotsPerMM = 3.0;						// overrride with -D DotsPerMM=number

MapHeight = 5.0;						// overrride with -D MapHeight=number

ImageX = 100;							// overrride with -D ImageX=whatever
ImageY = 100;

MapScaleXYZ = [1/DotsPerMM,1/DotsPerMM,MapHeight/255];
PlateScaleXYZ = [1/DotsPerMM,1/DotsPerMM,1.0];

echo("Press File: ",fnMap);
echo("Plate File: ",fnPlate);

echo(str("ImageX:",ImageX," ImageY: ", ImageY));
echo(str("Map Height: ",MapHeight));
echo(str("Dots/mm: ",DotsPerMM));
echo(str("Scale Map: ",MapScaleXYZ,"  Plate: ",PlateScaleXYZ));

//- Extrusion parameters - must match reality!

ThreadThick = 0.25;
ThreadWidth = 2.0 * ThreadThick;

//- Buid parameters

PlateThick = IntegerMultiple(1.0,ThreadThick);		// solid plate under press relief

PinOD = 1.75;				// locating pin diameter
PinDepth = PlateThick;		//  ... depth into bottom surface = total length/2
PinOC = 20.0;				// spacing within mold item

echo(str("Pin depth: ",PinDepth," spacing: ",PinOC));

//- Useful info

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);

HoleWindage = 0.2;

Protrusion = 0.1;						// make holes & unions work correctly

MaxConvexity = 5;						// used for F5 previews in OpenSCAD GUI

ZFuzz = 0.2;							// numeric chaff just above height map Z=0 plane

//-----------------
// Import plate height map, slice off a slab to define outline

module Slab(Thick=1.0) {
	intersection() {
		translate([0,0,Thick/2])
			cube([2*ImageX,2*ImageY,Thick],center=true);
		scale(PlateScaleXYZ)
			difference() {
				translate([0,0,-ZFuzz])
					surface(fnPlate,center=true,convexity=MaxConvexity);
				translate([0,0,-1])
					cube([2*ImageX,2*ImageY,2],center=true);
			}
	}
}

//- Put peg grid on build surface

module ShowPegGrid(Space = 10.0,Size = 1.0) {

	Range = floor(50 / Space);

	for (x=[-Range:Range])
	  for (y=[-Range:Range])
		translate([x*Space,y*Space,Size/2])
			%cube(Size,center=true);
}

//-- convert cylinder to low-count polygon

module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes

  Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);

  FixDia = Dia / cos(180/Sides);

  cylinder(r=(FixDia + HoleWindage)/2,
           h=Height,
	   $fn=Sides);
}

//-- Locating pin hole with glue recess
//  Default length is two pin diameters on each side of the split

module LocatingPin(Dia=PinOD,Len=0.0) {

	PinLen = (Len != 0.0) ? Len : (4*Dia);

	translate([0,0,-ThreadThick])
		PolyCyl((Dia + 2*ThreadWidth),2*ThreadThick,4);

	translate([0,0,-2*ThreadThick])
		PolyCyl((Dia + 1*ThreadWidth),4*ThreadThick,4);

	translate([0,0,-(Len/2 + ThreadThick)])
		PolyCyl(Dia,(Len + 2*ThreadThick),4);

}

//- Build it

//ShowPegGrid();

echo("Building mold");
union() {
	difference() {
		Slab(PlateThick + Protrusion);
		for (i=[-1,1])
			translate([0,i*PinOC/2,0])
				rotate(180/4) LocatingPin(Len=2*PinDepth);
	}
	translate([0,0,PlateThick])							// cookie press height map
		scale(MapScaleXYZ)
		difference() {
			translate([0,0,-ZFuzz])
				surface(fnMap,center=true,convexity=MaxConvexity);
			translate([0,0,-1])
				cube([2*ImageX,2*ImageY,2],center=true);
		}
}

The molds have alignment pin holes in the back:

Tux positive mold - solid model - backside

Tux positive mold – solid model – backside

That match up with the holes in a baseplate:

SqWr Positive Mold Framework - 2x3 pinsThe plate holds the molds in place, perhaps with tapeless sticky, while you’re slathering silicone goop to make the negative mold:

Tux Positive Mold Framework - 2x3 array

Tux Positive Mold Framework – 2×3 array

As you might expect, the OpenSCAD file that generates the plate-with-holes can also embed the positive molds atop the plate, so you could get a solid (well, infilled at 20%) chunk of plastic without attaching the molds. I’d rather do the plate separately from the molds, so you can recycle the plate for many different molds. Your mileage may vary.

The Positive Mold Framework.scad OpenSCAD source code:

// Positive mold framework for chocolate slabs
// Ed Nisley - KE4ZNU - January 2014

Layout = "FramePins";		// FramePins FrameMolds Pin

//- Extrusion parameters must match reality!
//  Print with 2 shells and 3 solid layers

ThreadThick = 0.20;
ThreadWidth = 0.40;

Protrusion = 0.1;			// make holes end cleanly

HoleWindage = 0.2;

//----------------------
// Dimensions

FileName = "Tux-positive.stl";	// overrride with -D

Molds = [2,3];					// count of molds within framework

MoldOC = [40.0,45.0];			// on-center spacing of molds
MoldSlab = 1.0;					// thickness of slab under molds

BaseThick = 5.0;

BaseSize = [(Molds[0]*MoldOC[0] + 0),(Molds[1]*MoldOC[1] + 0),BaseThick];
echo(str("Overall base: ",BaseSize));

PinOD = 1.75;					// locating pin diameter
PinLength = 2.0;				//  ... total length
PinOC = 20.0;				// spacing within mold item

//----------------------
// Useful routines

//- Put peg grid on build surface

module ShowPegGrid(Space = 10.0,Size = 1.0) {

	RangeX = floor(100 / Space);
	RangeY = floor(125 / Space);

	for (x=[-RangeX:RangeX])
		for (y=[-RangeY:RangeY])
			translate([x*Space,y*Space,Size/2])
			%cube(Size,center=true);

}

module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes

  Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);

  FixDia = Dia / cos(180/Sides);

  cylinder(r=(FixDia + HoleWindage)/2,
           h=Height,
	   $fn=Sides);
}

// Locating pin hole with glue recess
//  Default length is two pin diameters on each side of the split

module LocatingPin(Dia=PinOD,Len=0.0) {

	PinLen = (Len != 0.0) ? Len : (4*Dia);

	translate([0,0,-ThreadThick])
		PolyCyl((Dia + 2*ThreadWidth),2*ThreadThick,4);

	translate([0,0,-2*ThreadThick])
		PolyCyl((Dia + 1*ThreadWidth),4*ThreadThick,4);

	translate([0,0,-(Len/2 + ThreadThick)])
		PolyCyl(Dia,(Len + 2*ThreadThick),4);

}

module LocatingPins(Length) {
	for (i=[-1,1])
	translate([0,i*PinOC/2,0])
		rotate(180/4)
		LocatingPin(Len=Length);
}

//-- import a single mold item

module MoldItem() {
	import(FileName,convexity=10);
}

//-- Overall frame shape

module Frame() {

//	translate([0,0,BaseSize[2]/2])		// platform under molds
//		cube(BaseSize,center=true);

	difference() {
		hull()
			for (i=[-1,1], j=[-1,1])
				translate([i*BaseSize[0]/2,j*BaseSize[1]/2,0])
					sphere(r=BaseThick);
		translate([0,0,-BaseThick])
			cube(2*BaseSize,center=true);
	}

}

//- Build it

ShowPegGrid();

if (Layout == "Pin")
	LocatingPin(Len=PinLength);

if (Layout == "Frame")
	Frame();

if (Layout == "FramePins")
	difference() {
		Frame();

		translate([-MoldOC[0]*(Molds[0] - 1)/2,-MoldOC[1]*(Molds[1] - 1)/2,0])
			for (i=[0:Molds[0]-1],j=[0:Molds[1]-1])
				translate([i*MoldOC[0],j*MoldOC[1],BaseSize[2]])
					LocatingPins(BaseThick);
	}

if (Layout == "FrameMolds") {
	Frame();
	translate([-MoldOC[0]*(Molds[0] - 1)/2,-MoldOC[1]*(Molds[1] - 1)/2,0])
		for (i=[0:Molds[0]-1],j=[0:Molds[1]-1])
			translate([i*MoldOC[0],j*MoldOC[1],BaseThick - MoldSlab + Protrusion])
			MoldItem();
}

And then it’s time to pour some chocolate… which someone else knows how to do much better than I!

,

3 Comments

Chocolate Mold: Image Preparation Checklist

Start with a monochrome image:

Tux-Shirt

Tux-Shirt

A bit of tinkering produce a height map image:

Tux Mold - Height Map - large

Tux Mold – Height Map – large

I picked a 3.0 pixel/mm scale factor, so a 33 mm mold covers only 100 pixels. That image is 1100 mm tall and will be reduced by a factor of 10 to the final image size: this is not the place for fine detail and fancy lettering!

The conversion process assumes you’ll handle the Z axis scaling yourself, so the script no longer normalizes the gray levels. If you select gray levels using HSV, the V slider gives you a direct reading in percent-of-maximum thickness; Tux varies from V = 80 to 100, so he’s pretty much bas relief.

The border around the image must be 0 = black and will be stripped from the final mold. That’s why Tux doesn’t turn into a bird served on a rectangular platter.

Because this is a mold, its edges must have some draft, which means the outline must shade from black to whatever gray represents the interior of the mold. Do this:

  • Trace the outline using the Scissors Select tool = snap to high-contrast outer edge
  • Create / go to a new layer filled with whatever gray you want for the interior (V = 80 here)
  • Select → Grow the selection by 60 pixels (on a 1000×1100 image)
  • Select → Invert to select the exterior of the outline
  • Bucket fill the exterior with 0 = black
  • Select  → Invert to select the interior of the outline again
  • Select → Border: Add a 30 pixel border to the selection with the “Feather border” option
  • Bucket fill the border with 0 = black
  • Unselect and you have a layer with a nice graduation around the mold

Which looks like this with V=80 gray inside:

Tux Mold - Height Map - outline gradient

Tux Mold – Height Map – outline gradient

The 30 pixel feathered border, scaled by the 10× reduction, means the edge of the mold goes from 0 = black to the interior in about 3 pixel / (3 pixel/mm) = 1 mm. If the interior is 255 = white at 7 mm, the draft angle is arctan 1/7 = 8°, which is probably about right for the deepest part of the mold. The edge of the Tux mold is V = 80 (or about 200 gray), so it’s at 0.8 × 7 mm = 5.6 mm and the draft angle is arctan 1/5.6 = 10°.

Inside the mold, anything goes, but you should avoid 0 = black levels so that the alignment pins don’t poke through the mold. Any 255 = 100 V = white levels will be the maximum mold thickness, which is 7 mm for the molds you see here and that may be somewhat too thick for a chocolate treat. It is really hard to maintain draft on small features, but I think if you don’t get carried away it’ll be all good.

There’s also a 1 mm backing plate below the mold that ensures the deepest mold parts have some substance behind them and the alignment pin sockets have enough depth to be useful.

Scaling the image down by 10× to about 110 pixels tall (including the black border) will make the final Tux mold about 37 mm tall:

Tux

Tux

This image enlarges it by 10× with no smoothing to show the gritty nature of the image. This is why you can’t have delicate detail or fine lettering:

Tux - enlarged to show texture

Tux – enlarged to show texture

Notice the nearly complete lack of draft on the interior features. Each level differs by about V = 5 over the range V = 80(the border) to V = 100 (beak and flipper), so they amount to only 0.05 × 7 mm =  0.35 mm = one or two thread layers at 0.20 mm/layer. I think if you were doing this right, you’d pick an overall thickness so that V = 5 increments corresponded to one layer or use whatever V increments corresponded to a single layer.

Running that image through the Bash script & OpenSCAD programs (more on those later) produces a reasonable result:

Tux positive mold - solid model - oblique

Tux positive mold – solid model – oblique

When it’s converted into plastic, you can count the layers in each V = 5 level (clicky for more dots):

Tux positive mold - plastic - oblique

Tux positive mold – plastic – oblique

It may be a bit less rounded in the tummy than the real Tux, but seems good enough for the purpose.

,

Leave a comment