Archive for September 5th, 2011

OpenSCAD vs. Skeinforge 40: Bogus G-Code

My first pass at the NEMA 17 motor mount bracket used additive modeling, glomming together several blocks made from cube primitives:

  • The motor mounting plate, less five holes
  • Two side struts to stiffen the motor plate
  • The baseplate, minus two mounting holes

Makes perfect sense to me; perhaps I’m an additive kind of guy. That produced an OpenSCAD model with positive surfaces for the various parts and negative surfaces inside the holes:

NEMA 17 Mount - additive model

NEMA 17 Mount - additive model

Compile that through CGAL, export as STL, inhale into RepG 25, and you (well, I) get what looks to be a fine object in the preview pane:

NEMA 17 RepG preview - additive

NEMA 17 RepG preview - additive

Then run it through Skeinforge 40, which emits a flurry of messages along these lines:

[19:11:08] Warning, the triangle mesh slice intersects itself in getLoopsFromCorrectMesh in triangle_mesh.
[19:11:08] Something will still be printed, but there is no guarantee that it will be the correct shape.
[19:11:08] Once the gcode is saved, you should check over the layer with a z of:
[19:11:09] 0.165

The usual searching suggested that sometimes Skeinforge has problems with coincident surfaces, such as between the motor mount plate and the struts and the base, or coincident edges where two blocks abut. Judging from the messages, the problem ran all the way to the top of the struts. Oddly, Skeinview didn’t show any problems, so the G-Code was (presumably) OK.

Error messages tend to make me twitchy, though. I modified the OpenSCAD code to extend the struts 0.1 mm inside the base and ran that model through the software stack, which produced not a single complaint about anything, anywhere.

Success!

However, painful experience has caused me to review the G-Code for every single object with the Skeinlayer plugin, which, right on cue, revealed this interesting anomaly:

NEMA 17 Mount - Skeinview - Bad gcode

NEMA 17 Mount - Skeinview - Bad gcode

That happened for every layer in the square motor mount plate: the lower right corner is fine, the upper left seems to be the negative of the actual solid model. The holes are filled, the plate is empty. The Skirt outline ignores the smaller holes, goes around the large one, and continues on its merry way.

I putzed around for a while and discovered that the failure seems acutely sensitive to the side strut thickness. Yeah, like that makes any sense.

Any variations along those lines that I tried generated either:

  • A flurry of mesh error messages, with seemingly good G-Code
  • No error messages whatsoever, with totally bogus G-Code

Running the STL files through netfabb Cloud Service produced the same diagnostic for both:

Number of holes: 3
Number of shells: 2
Mesh is not manifold and oriented.
 We unfortunately have not yet enough experience with the occuring server loads, that we can securely enable shell merging at the moment.

However, the repaired STL files produce correct G-Code: evidently OpenSCAD spits out bogus STL data. The fact that RepG/SF treats the two files differently suggests improved diagnostics would be in order, but that’s in the nature of fine tuning.

So I junked the additive model and went subtractive, chewing the recesses out of one huge block:

NEMA 17 Stepper Mount - solid model

NEMA 17 Stepper Mount - solid model

That worked:

Number of holes: 0
Number of shells: 1
Mesh is manifold and oriented.

I like processes that don’t emit error messages or result in mysterious failures, although it’s not obvious that subtractive modeling will always produce correct results. Heck, I’m not sure I can think in terms of negative volumes all that well.

The OpenSCAD code for the additive model, with a highlight on the conditional that will trigger the two errors:

// NEMA 17 stepper motor mount
// Ed Nisley KE4ZNU August 2011

include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>

//-- Layout Control

Layout = "Build";				// Build Show None

Examine = "None";				// Mount Stand None

//-- Extrusion parameters

ThreadThick = 0.33;
ThreadWT = 2.0;
ThreadWidth = ThreadThick * ThreadWT;

HoleWindage = 0.3;			// enlarge hole dia by this amount

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

//-- Useful sizes

Tap2_56 = 0.070 * inch;
Clear2_56 = 0.082 * inch;
Head2_56 = 0.156 * inch;
Head2_56Thick = 0.055 * inch;
Nut2_56Dia = 0.204 * inch;
Nut2_56Thick = 0.065 * inch;

Tap3_48 = 0.079 * inch;
Clear3_48 = 0.096 * inch;
Head3_48 = 0.184 * inch;
Head3_48Thick = 0.058 * inch;
Nut3_48Dia = 0.201 * inch;
Nut3_48Thick = 0.073 * inch;

Tap4_40 = 0.089 * inch;
Clear4_40 = 0.110 * inch;
Head4_40 = 0.211 * inch;
Head4_40Thick = 0.065 * inch;
Nut4_40Dia = 0.228 * inch;
Nut4_40Thick = 0.086 * inch;

Tap10_32 = 0.159 * inch;
Clear10_32 = 0.190 * inch;
Head10_32 = 0.373 * inch;
Head10_32Thick = 0.110 * inch;
Nut10_32Dia = 0.433 * inch;
Nut10_32Thick = 0.130 * inch;

NEMA17_ShaftDia = 5.0;
NEMA17_ShaftLength = 24.0;
NEMA17_PilotDia = 0.866 * inch;
NEMA17_PilotLength = 0.080 * inch;
NEMA17_BCD = 1.725 * inch;
NEMA17_BoltDia = 3.5;
NEMA17_BoltOC = 1.220 * inch;

//-- Mount Sizes

MountSide = IntegerMultiple(NEMA17_BCD,ThreadWidth);
MountThick = IntegerMultiple(8.0,ThreadThick);

MountBoltDia = 3.0;

StrutThick = IntegerMultiple(5.0,ThreadWidth);
StrutHeight = MountSide;

StandThick = IntegerMultiple(4.0,ThreadWidth);

UprightLength = IntegerMultiple(MountSide + 2*StrutThick,5);

StandBoltAllowance = IntegerMultiple(Head10_32,5);
StandBoltOC = UprightLength + 2*StandBoltAllowance;

StandLength = IntegerMultiple(StandBoltOC + 2*StandBoltAllowance,ThreadWidth);
StandWidth = IntegerMultiple(2*StandBoltAllowance,ThreadThick);

echo(str("Stand Base: ",StandLength," x ",StandWidth));
echo(str("Stand Bolt OC: ",StandBoltOC));

//-- Convenience values

Protrusion = 0.1;		// make holes look good and joints intersect properly

BuildOffset = 3 * ThreadWidth;

//----------------------
// 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);

}

//----------------------
// Motor Mount plate

module MotorMount() {

  difference() {
	translate([0,0,MountThick/2])
	  cube([MountSide,MountSide,MountThick],center=true);
if (true) {
	translate([0,0,-Protrusion])
	  PolyCyl(NEMA17_PilotDia,(MountThick + 2*Protrusion));
}
else {
	translate([0,0,MountThick - NEMA17_PilotLength])
	  PolyCyl(NEMA17_PilotDia,(NEMA17_PilotLength + Protrusion));
	translate([0,0,-Protrusion])
	  PolyCyl(1.5*NEMA17_ShaftDia,(MountThick + 2*Protrusion));
}
	for (x=[-1,1])
	  for (y=[-1,1])
		translate([x*NEMA17_BoltOC/2,y*NEMA17_BoltOC/2,-Protrusion])
		  PolyCyl(MountBoltDia,(MountThick + 2*Protrusion));
  }

}

//----------------------
// Stand to support the plate

module Stand() {

  difference() {

	union() {
	  translate([0,0,StandThick/2])
		cube([StandLength,StandWidth,StandThick],center=true);
	  for (x=[-1,1])						// side support struts
if (true)	// this causes bizarre negative rendering and a diagonal negative section
		translate([(x*((MountSide + StrutThick)/2)),0,
				  (StandThick + StrutHeight/2 - Protrusion/2)])
		  cube([StrutThick,StandWidth,(StrutHeight + Protrusion)],center=true);
else	// this generates "triangle slice mesh intersects iself"
		translate([(x*((MountSide + StrutThick)/2)),0,
				  (StandThick + StrutHeight/2)])
		  cube([StrutThick,StandWidth,StrutHeight],center=true);
	}

	for (x=[-1,1])
	  translate([x*StandBoltOC/2,0,-Protrusion])
		PolyCyl(Clear10_32,StandThick + 2*Protrusion);
  }
}

//----------------------
// Combined for single build

module Combined() {

//  union() {
	translate([-MountSide/2,0,0])
	  MotorMount();

	translate([StandThick,0,StandWidth/2])
	  rotate([90,0,270])
		Stand();

//  }
}

//----------------------
// Lash everything together

ShowPegGrid();

if (Examine == "Mount")
  MotorMount();

if (Examine == "Stand")
  Stand();

if (Layout == "Build" && Examine == "None") {
  translate([MountSide/2,0,0])
	Combined();
}

if ((Layout == "Show") && Examine == "None") {
  translate([-StandWidth/2,0,StandThick])
	rotate([0,90,0])
	  Combined();
}

OpenSCAD depends on CGAL for all the 3D heavy lifting, which puts any STL export problems further upstream.  I suppose I could open Yet Another RepG ticket to get better diagnostics, but the others haven’t gotten much attention so far and I suppose it’s not really their problem anyway.

,

18 Comments