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:
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:
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:
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:
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.





#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.
#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
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…
#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…