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.

About these ads

,

  1. #1 by nophead on 5-September-2011 - 08:06

    The problem isn’t with SF or OpenScad, it is with the concept of CSG. You cannot do boolean operations on things with coincident faces so it is a pain to make things additively as you have to have small fudge factors all over the place. See http://reprap.org/wiki/Separate_page_on_the_question

    • #2 by Ed on 5-September-2011 - 09:14

      Ramming the side supports into the base by 0.1 mm should eliminate the coincident-face issue, but that’s where the bogus G-Code came from. Maybe (Hah! probably) I’m screwing up something, but that seemed straightforward: coincident faces produced error messages, non-coincident faces produced bad G-Code. Not what I expected and both can’t arise from numerical roundoff in the bowels of the CSG routines.

      The Protrusion variable extends holes from the surface enough to eliminate rendering weirdness, making even trivial subtractive models challenging… and, yeah, keeping track of that nonsense is tedious.

  2. #3 by Keith Neufeld on 5-September-2011 - 09:39

    Nophead, “you can’t do boolean operations on coincident faces” is a meme started by people who use no-cost and low-cost modeling software that I’m very tired of hearing. They work perfectly in CATIA and export manifold meshes every single time, and I’m sure they do in other high-end modeling packages as well.

    “You can’t do boolean operations on coincident faces without paying for the software” seems to be true.

    This distinction is a sore point for me because it’s one of two major issues driving me away from using open-source modeling software. It takes me an order of magnitude more time to make the fudge factors than to make the model, and that’s not a good use of my time.

    (The other issue is that open-source modeling software is so clumsy compared to, again, CATIA as the software that I know, that it takes me an order of magnitude more time to build a model. I would actually accept that issue for the sake of publishing model source files in a freely-usable format if not for the boolean problems.)

    • #4 by Ed on 5-September-2011 - 09:56

      OK, I’ll bite: how much does it cost for one seat of a 3D CAD/CAM program that can do relational design (so I can link the dimensions to measured values), handle CSG Boolean operations with aplomb (so I can just build the models and have them work), and produce G-Code suitable for printers in the RepRap family tree (and maybe for EMC2, so I can run the Sherline, too)?

      Every time I’ve looked at that, I’ve always concluded that I couldn’t possibly justify the expense… but perhaps I’ve overlooked an option.

    • #5 by nophead on 5-September-2011 - 10:11

      Hi Keith,
      Does CATIA use CGS? I can do boolean ops in CoCreate and they always work unless I deliberately create a knife edge by doing something like subtract a cylinder from a block it is tangential to. I think CoCreate uses BREP instead of CSG. However the full version is far too expensive and I have converted to openscad now.

      • #6 by Keith Neufeld on 5-September-2011 - 10:34

        Ed, one CATIA seat costs about a million billion dollars ($15,000). I’m not suggesting that we’re all going to be using CATIA with our RepRaps and MakerBots any time soon — only that it *is possible* to do boolean operations with perfect results. For open-source software advocates to claim it’s impossible will only perpetuate the lack of availability of open-source software that can do it.

        Nophead, you raise a fair point — I don’t know what CATIA is using internally. Its workflow is primarily boolean — you sketch a 2D outline in a plane, “pad” (extrude) it into a 3D shape, sketch more 2D outlines and “pocket” (subtract) them from the 3D shape, etc. But that certainly doesn’t mean it’s using CSG for its engine.

        That said, my point remains that I can do all manner of boolean operations on coincident and tangential surfaces in CATIA — and you can in CoCreate, and doubtless others can in AutoCAD and Solidworks — and generate perfect manifold meshes (although Rhino appears to make grotesquely non-manifold models), and we can’t yet in open-source modeling software. I wish we would phrase it that open-source modeling software *can’t yet* do booleans with coincident/tangent surfaces rather than perpetuate the belief that it’s *impossible* to do so.

        • #7 by Ed on 5-September-2011 - 13:33

          one CATIA seat costs about a million billion dollars ($15,000).

          Ooof! But then it’s only a few billion for the annual maintenance contract, I suppose. [grin]

          I misunderstood your point. It obviously can be done (and, most of the time, OpenSCAD / CGAL gets it right), so the catch right now seems to be getting somebody interested in finishing the job. There’s plenty of opportunity for fame, if not fortune, in the 3D modeling / printing universe…

  3. #8 by Dustin Andrews on 17-November-2011 - 14:12

    Have you tried using the union() operator on objects with coincident faces? I do this as a matter of habit in openscad and my models seem to work fine.

    • #9 by Ed on 17-November-2011 - 15:28

      As nearly as I can tell, union has no effect on the problem, because all it does is logically combine all the child objects for further processing; it doesn’t actually weld the objects into a geometric lump. In any event, I’ve tried it both ways to no avail.

      The heart of the problem seems to be that coincident surfaces may produce slight numerical mismatches that knock Skeinforge off the rails. Adding a Finagle constant to one surface to move it a nontrivial amount inside the other makes the problem Go Away, but it’s a distinct hassle to do that (and get it right) at every possible junction.

      But I may be totally wrong on any part of that…

  4. #10 by Derek on 19-March-2014 - 13:08

    Hi Ed,
    I realize this post is virtually ancient by internet standards, but I’m curious if you’ve been able to resolve these issues in the time since. Has OpenSCAD, or skeinforge, or any of the rest of the tool chain got to the point where you can build things in OpenSCAD additively, and print them without issue?
    I’m just starting to get into modeling, to learn the ropes a bit before getting a 3d printer, so I’m curious how much things have improved in the open-source software space in the last 2 years.
    Cheers

    • #11 by Ed on 19-March-2014 - 14:15

      virtually ancient by internet standards

      That’s why I leave comments enabled for old posts: you never know when they’ll come in handy!

      build things in OpenSCAD additively

      The problem wasn’t in either OpenSCAD or Skeinforge (which is now completely unmaintained), but in the nature of Constructive Solid Geometry. A valid solid model will be manifold (which I discuss elsewhere), but you can easily produce a non-manifold model by aligning shapes so that they have coincident surfaces or vertexes; the fact that the result looks good isn’t sufficient.

      Slicers have become better at handling small modeling errors, but that leads to overconfidence and utter bafflement when a model produces weird errors. Similarly, automated mesh repair tools produce good results almost all the time and emit spectacular failures when they can’t figure out what you intended to do.

      I now hold something of an absolutist view: if a model isn’t manifold, make it so before proceeding. In my case, that means doing nearly all OpenSCAD modeling with subtractive manipulations, rather than merging separate pieces together. It works well enough for what I do, but probably isn’t a good general rule for all models / programs / people… [grin]

      Welcome to the club; there’s nothing like seeing something you’ve designed take shape on a build platform to get you hooked!

      • #12 by Derek on 19-March-2014 - 16:56

        Oh, I see. That’s not at all what I expected. I had assumed that whatever conversion to STL OpenSCAD was doing was identifying coincident faces and joining them into a single solid, or that at least union would do that. I recognize that that’s far from a simple task though.

        Does this mean that any modelling with OpenSCAD (for purposed of 3D printing) needs to effectively create a mold, and subtract that from a single object in order to ensure the result is manifold? Or is there some additivity that can be ‘tolerated’, provided the surfaces are only intersecting, and not coincident? (eg, the cylindrical clips on the sides of this model: https://twitter.com/SurprisingEdge/status/446141243366457344/photo/1)

        I have yet to try running the STL from that through a slicer, though based on what you’ve told me, and the fact that most of it is constructed additively, the result probably won’t be pretty.

        • #13 by Ed on 19-March-2014 - 18:33

          That’s been kicked around on the OpenSCAD mailing list; it turns out to be a very difficult problem, for reasons like the non-uniform spacing of floating point numbers.

          The rule of thumb is that you can have interpenetrating shapes, but no more than two surfaces may meet at any edge of the joined solid. You can ram a small cube into a larger one, but they can’t share a side or an edge; centering the smaller one on a side face will work, because the result has two sides meeting at each edge of the joint.

          Those cylinders (which aren’t really cylinders, of course, but faceted polyhedrons) will work fine, because the long faces join the cube in the middle. If you built the side walls by adding cubes together, that might be a problem at the edges; better to punch out a few big blocks from the overall shape. Similarly, you can build the gridwork by punching depressions, rather than overlaying thin ribs.

          Remember that’s not an OpenSCAD limitation, it’s how CSG works in general!

  5. #14 by solaandjin on 20-March-2014 - 09:40

    I’m a big fan of OpenSCAD, but for more complex models where I don’t already have a firm idea of what I want, I’ve lately been learning/using DesignSpark Mechanical. It was released late last summer, and I haven’t seen widespread notice of it, but it turns out, it’s pretty good! It is “based on” SpaceClaim, one of those high power commercial packages with a “call us for a quote” price. From what I’ve read, it is pretty much the same as SpaceClaim Engineer without some of the professional modules, like some export/import formats. It is also free, but not open source. Windows only, as a lot of these things are. Direct modeling with some parametric features. Work is done mostly through manipulating 3D models directly, using 2D sketches as a starting point. Similar to SketchUp except that you will end up with a manifold mesh in the end.

    • #15 by Ed on 20-March-2014 - 20:38

      you will end up with a manifold mesh

      Now that is a definite selling point, right there!

      At some point, I should try that on the Token Windows Laptop, but … so many projects!

  1. Stepper Dynamometer: Sync Wheel « The Smell of Molten Projects in the Morning
  2. Zombie Apocalypse Preparations « The Smell of Molten Projects in the Morning
  3. KG-UV3D GPS+Voice: Quasi-Extruded Case « The Smell of Molten Projects in the Morning