slicing adds feature to my stl
Clear all

slicing adds feature to my stl  

Eminent Member
slicing adds feature to my stl

I have a weird problem.  When I look this model in edit mode it looks fine. When I slice the model it adds a channel to the top layers.  The channel it adds is similar to many other channels in the model.  The extra channel is between the upper-left tops of the "W".

I've used several different STL viewers and they show no  problem  Simplify doesn't have a problem with this file which is what I'm using for this model until I get past this problem.  Can someone please look at this and tell me what I'm doing wrong?  Slicer is version 2.4.0, win64.



Posted : 26/01/2022 6:05 am
Famed Member
RE: slicing adds feature to my stl

When I open your project in the same version Prusa Slicer reports that there are 3891 open edges and that it repaired 163 errors.  There is the warning check mark against it indicating the model has issues.

I exported the model as stl from PS and imported it into Blender.  The 3dprint tool reports 3874 Non manifold edges, 488 intersecting faces and a few other errors.  Which closely matches the open edges reported by PS.  Non Manifold issues are what really mess slicing up though.  If you look at the model its badly constructed.  If you modelled it then I'd check your design process and work out what you are doing to model in so many open edges.  
Either that or learn how to fix models after the fact.

I'm surprised any slicer is doing as well as it is. 

Anyway here is a mostly fixed version.  Managed to get it down to 36 open edges but it does appear to slice without major issues.  Dont know if its suitable though as fixing it altered some of the intersections where the grooved join so that they slice a bit different.


Posted : 26/01/2022 7:28 am
Noble Member
RE: slicing adds feature to my stl



The .stl shows 3276 open edges in 3dSMax, 3874 in Blender,  and 3891 open edges in slicer. I didn't see any point in checking in other software's beyond that.

Here are some other issues circled in red.


I also sliced the model at 0.15 layer height, the channel goes away however it shows new errors. if Simplify3D is correcting all that I say use it, it will be much faster then trying to fix all the errors. it's not PrusaSlicer the model needs help.





The Filament Whisperer

Posted : 26/01/2022 7:41 am
Eminent Member
Topic starter answered:
RE: slicing adds feature to my stl

Ok.  I guess I'll have to use S3d.  Bummer.

I'll post an issue on the jscad repo.  The model was generated with jscad.

Posted : 26/01/2022 5:57 pm
Noble Member
RE: slicing adds feature to my stl



As a side note, it looked as though it was made with multiple Boolean's and apparently they aren't closing the mesh. I'm not familiar with JSCadd, however some software packages require that you apply the Boolean's before you export, generally though, using to many bool's at a time can cause trouble at intersections.

Some programs bool better then others, Blender has very strong Boolean math, and is free, It's bool's are actually better then a couple of the pay for software's I use.





The Filament Whisperer

Posted : 26/01/2022 6:09 pm
Eminent Member
Topic starter answered:
RE: slicing adds feature to my stl

A cuboid is created.  Then a sphere is created for each point on the font.  Each sequential pair of spheres are joined with a hull and the hull is subtracted from the cuboid.  Each pair of pairs has the previous end sphere in the same place as the start of the next pair.  So it is basically a ton of hulls and subtractions.  

I'll try removing the redundancy of the matching spheres.  But I don't see how this can cause any problems.  It should work.

How would the output of jscad be put into blender?  Would I create an stl file with all separate features and input that in blender?  I would still have to do the hulls in jscad since blender wouldn't know which spheres to use the hull operations on.  Blender would only be used for the subtraction from the cuboid.


Posted : 26/01/2022 6:48 pm
Eminent Member
Topic starter answered:
RE: slicing adds feature to my stl

BTW, the reason I didn't respond to the posts about the problem and the suggested fixes is that I need to create these plates automatically a lot of times with different text.  A web site will take the text and the stl will be downloaded.  I don't know how the operations suggested could be automated.  And I realize now I can't tell the website user to buy S3D.  So I'm pretty much screwed unless I can get jscad to work.

I do really appreciate the fast responses to my original post and thanks for that.

Posted : 26/01/2022 6:56 pm
Eminent Member
Topic starter answered:
i fixed it

I discovered a jscad operation called hullChain.  You give it a bunch of objects and it creates exactly what I need.  It takes object pairs in the list and does a hull operation on each pair. Then does a union on all the hulls.  Not only did it fix my problem but the run time went from 30 secs to 3 secs.


Thanks to everyone for pushing me in the right direction.

Posted : 26/01/2022 9:41 pm
Noble Member

jscad/Openjscad appears to be an attempt to recast OpenSCAD as javascript.  It claims to import OpenSCAD files but failed in several places in one I tried.

However, what you appear to be attempting should be fairly straightforward in OpenSCAD so as long as you're happy with functional programming it may well be worth porting.


Posted : 26/01/2022 11:07 pm
Noble Member
RE: slicing adds feature to my stl



Very good, that sounds perfect for your application.



The Filament Whisperer

Posted : 26/01/2022 11:10 pm
Eminent Member
Topic starter answered:
RE: slicing adds feature to my stl

>attempt to recast OpenSCAD

I used openScad for years and IMHO jscad is much better.  The javascript  language is way more powerful.  Look at my code below.  I would never attempt this in openscad.  Also, javascript can be used in a functional manner if you wish.  First-class functions make functional programming possible in JavaScript:

> It claims to import OpenSCAD files

Really?  I've never run into that claim and I thought I'd read all jscad docs.  Openscad is totally invalid javascript.  Maybe jscad has changed.

In any case I'm a happy camper now.


const jscad             = require('@jscad/modeling');
const {union, subtract} = jscad.booleans;
const {sphere, cuboid}  = jscad.primitives;
const {vectorChar}      = jscad.text;
const {hull, hullChain} = jscad.hulls;
const {translate}       = jscad.transforms;

// ------ default params --------- 
const defStrParam = "k";

let   strParam = "k";
const genHulls = true;
const genHoles = true;
const genPlate = true;

const radius     = 0.75 + 0.2; // 0.2 is for expansion
const stepDist   = 0.1;        // step size when backing up
const segments   = 16;         // sphere segments
const bkupDist   = 3.0;        // dist to back up, frac times radius
const holeTop    = 1.5*radius; // only affects top sphere
const holeBot    = 3.0*radius; // only affects bottom sphere

const plateW     = 180;
const plateH     = 78.51;
const plateDepth = 4.1;
const textZofs   = 0.75;  // fraction of diameter below the surface
const padSides   = 20;
const padTopBot  = 15;
// const baseline   = 0.3;   // fraction of plateH

const pntEq = (A,B) => A[0] == B[0] && A[1] == B[1];

// return intersection point if exists
// but only if point isn't one of CD end points
const intersectionPoint = (segAB, segCD) => {
  const [[Ax,Ay], [Bx,By]] = segAB;
  const [[Cx,Cy], [Dx,Dy]] = segCD;
  const numeratorR  = ((Ay-Cy)*(Dx-Cx) - (Ax-Cx)*(Dy-Cy));
  const numeratorS  = ((Ay-Cy)*(Bx-Ax) - (Ax-Cx)*(By-Ay));
  const denominator = ((Bx-Ax)*(Dy-Cy) - (By-Ay)*(Dx-Cx));
  if(denominator == 0) return null; // parallel
  const r = numeratorR / denominator;
  const s = numeratorS / denominator;
  if(!(r >= 0 && r <= 1 && s >= 0 && s <= 1)) 
    return null;                           // doesn't intersect
  const iPnt = [Ax + r*(Bx-Ax), Ay + r*(By-Ay)]; // intersection point
  if((pntEq(iPnt,segAB[0]) || pntEq(iPnt,segAB[1])) ||
     (pntEq(iPnt,segCD[0]) || pntEq(iPnt,segCD[1]))) 
    return null;
  return iPnt;

// vector math
const dot = (v,w) => {
  const [x,y,z] = v;
  const [X,Y,Z] = w;
  return x*X + y*Y + z*Z;
const length = (v) => {
  const[x,y,z] = v;
  return Math.sqrt(x*x + y*y + z*z);
const vector = (b,e) => {
  const[x,y,z] = b;
  const[X,Y,Z] = e;
  return [X-x, Y-y, Z-z];
const unit = (v) => {
  const [x,y,z] = v;
  const mag = length(v);
  return [x/mag, y/mag, z/mag];
const distance = (p0,p1) => {
  return length(vector(p0,p1));
const scale = (v,sc) => {
  const [x,y,z] = v;
  return [x * sc, y * sc, z * sc];
const add = (v,w) => {
  const [x,y,z] = v;
  const [X,Y,Z] = w;
  return [x+X, y+Y, z+Z];

const distPntToVec = (pnt, vec) => {
  pnt = [pnt[0], 0, pnt[1]];
  let [start, end] = vec;
  start = [start[0], 0, start[1]];
  end   = [end[0],   0, end[1]];
  const line_vec       = vector(start, end);
  const pnt_vec        = vector(start, pnt);
  const line_len       = length(line_vec);
  const line_unitvec   = unit(line_vec);
  const pnt_vec_scaled = scale(pnt_vec, 1.0/line_len);
  const dt      = dot(line_unitvec, pnt_vec_scaled);
  const t       = Math.max(Math.min(dt,1),0);
  let   nearest = scale(line_vec, t);
  const dist    = distance(nearest, pnt_vec);
  return dist;

const showVec = (pfx, vec) => {
  // console.log(typeof vec, typeof vec?.[0], typeof vec?.[1]);
  console.log( pfx + ' ' +
    ((typeof vec?.[0] === 'undefined' || 
      typeof vec?.[1] === 'undefined') ? 'null' : (
      vec[0][0].toFixed(1).padStart(4) + ','    + 
      vec[0][1].toFixed(1).padStart(4) + ' -> ' +
      vec[1][0].toFixed(1).padStart(4) + ','    + 

// return point on vec far enough away from prevVec
const backUpPoint = (prevVec, vec, chkHead) => {
  // vec is A -> B and scan is A -> B
  const [[Ax, Ay],[Bx, By]] = vec;
  showVec('enter backUpPoint, prevVec', prevVec);
  showVec('                   vec    ', vec);
  const vecW = Bx-Ax;
  const vecH = By-Ay;
  const vecLen = Math.sqrt((vecW*vecW)+(vecH*vecH));
  // walk vec, each stepDist
  for(let distOnVec = 0; distOnVec < vecLen; 
            distOnVec += stepDist) {      
    const frac       = distOnVec/vecLen;
    const trialPoint = 
      (chkHead ? [Bx-frac*vecW, By-frac*vecH]
               : [Ax+frac*vecW, Ay+frac*vecH]);
    const dist2prev  = distPntToVec(trialPoint, prevVec);
    if(dist2prev > bkupDist * radius) return trialPoint;
  // vec was shorter then stepDist
  return null;

const prevVecs  = [];

const chkTooClose = (vec, first) => {
  let prevVecsTmp = (first ? prevVecs : prevVecs.slice(0,-1));
  // checking against all previous vecs (slow way)
  for(const prevVec of prevVecsTmp) {

    // ------ check ends touching  ------
    // tail point of vec only checked on first vector
    if( // first && 
       (pntEq(prevVec[0], vec[0]) ||
        pntEq(prevVec[1], vec[0]))) {
      // vec tail is touching prevVec head or tail
      showVec('tail touches prev', vec);
      const backUpPnt = backUpPoint(prevVec, vec, false);
      if(!backUpPnt) return {
        headClose:false, tailClose:true, vec1:null, vec2:null};
      return {
        headClose:false, tailClose:true, 
        vec1:[backUpPnt, vec[1]], vec2:null};
    if( pntEq(prevVec[0], vec[1]) ||
        pntEq(prevVec[1], vec[1])) {
      // vec head is touching prevVec head or tail
      showVec('head touches prev', vec);
      const backUpPnt = backUpPoint(prevVec, vec, true);
      if(!backUpPnt) return {
        headClose:true, tailClose:false, vec1:null, vec2:null};
      return {
        headClose:true, tailClose:false, 
        vec1:[vec[0], backUpPnt], vec2:null};

    // ------ check vecs intersection  ------
    const intPt = (intersectionPoint(vec, prevVec));
    if(intPt) {
      // vec intersects an old vec
      // and neither end point of vec is int point
      // split vec in two with both vecs far enough away
      console.log('intersected, at', intPt[0], intPt[1]);
      showVec('      prevVec', prevVec);
      showVec('      vec', vec);

      let vec1 = null;
      const backUpPt1 = backUpPoint(prevVec, [intPt, vec[0]]);
      if(backUpPt1) vec1 = [vec[0], backUpPt1];
      console.log('intersected, backUpPt1', backUpPt1);

      let vec2 = null;
      const backUpPt2 = backUpPoint(prevVec, [intPt, vec[1]]);
      if(backUpPt2) vec2 = [backUpPt2, vec[1]];
      console.log('             backUpPt2', backUpPt2);

      return {headClose:true, tailClose:true, vec1, vec2};

    // ------ check for either vec end point too close ------
    // tail point of vec only checked on first vector
    if(first) {
      // console.log('starting tail dist chk');
      const dist2prev = distPntToVec(vec[0], prevVec);
      if(dist2prev < (2 * radius)) {
        // tail end point too close to an old vec
        // back up to point on vec far enough away
        let vec1 = null;
        const backUpPt = backUpPoint(prevVec, vec, false);
        // console.log('tail dist chk, backUpPoint result', 
                        // backUpPt);
        if(backUpPt) {
          // console.log('vec tail too close to prev vec');
          return {
            headClose:false, tailClose:true, 
            vec1:[backUpPt, vec[1]], vec2: null};
        else {
          console.log('both ends of vec too close');
          return {  // skip vec
            headClose:true, tailClose:true, 
            vec1:null, vec2: null};
    // console.log('starting head dist chk');
    const dist2prev = distPntToVec(vec[1], prevVec);
    // console.log('did head dist chk, dist2prev:', dist2prev);
    if(dist2prev < (2 * radius)) {
      // head end point too close to an old vec
      // back up to point on vec far enough away
      let vec1 = null;
      const backUpPt = backUpPoint(prevVec, vec, true);
      // console.log('head dist chk, backUpPoint result', backUpPt);
      if(backUpPt) {
        vec1 = [vec[0], backUpPt];
        showVec('head dist chk, vec1', vec1);
        return {headClose:true, tailClose:false, vec1, vec2: null};
      else {
        // console.log('head dist chk, both ends too close');
        return {headClose:true, tailClose:true,  vec1: null, vec2: null};
  // vec not too close to any prev vec
  return {headClose:false, tailClose:false, 
          vec1: null, vec2: null};

const ptEq = (A,B) => (A[0] == B[0] && A[1] == B[1]);

const hullChains = [];
let   spherePts  = [];

const addToHullChains = () => {
  if(spherePts.length) {
    const spheres = =>
      sphere({radius, segments, center: pt.concat(0)}));

const addHull = (vec) => {
  showVec(' - hull', vec);
  if(genHulls) {
    if(spherePts.length && ptEq(, vec[0]))
    else {
      spherePts = [vec[0], vec[1]];

holes = [];

const addHole = (tailPoint, headPoint) => {
  showVec(' - hole', [tailPoint, headPoint]);
  if(genHoles) {
    const w       = headPoint[0] - tailPoint[0];
    const h       = headPoint[1] - tailPoint[1];
    const len     = Math.sqrt(w*w + h*h);
    const scale   = plateDepth/len;
    const holeLen = plateDepth*1.414;
    const x       = headPoint[0] + scale*w;
    const y       = headPoint[1] + scale*h;
    holes.push( hull(
      sphere({radius:holeTop, segments, 
      sphere({radius:holeBot, segments, 
              center: [x,y,-holeLen]})));

let lastPoint = null;

// returns next seg idx
const handlePoint = (point, segIdx, segLast) => {
  if(segIdx == 0) {
    // first point
    // console.log('first point of segment', point[0], point[1]);
    // console.log('only setting lastPoint');
    lastPoint = point;
    return 1; // next segidx is 1
  // not first point
  let vec = [lastPoint, point];
  console.log('\nhandlePoint segIdx, segLast:', segIdx, segLast);
  showVec(    '                       vec:', vec);

  const {headClose, tailClose, vec1, vec2} = 
         chkTooClose(vec, (segIdx == 1));
  if(headClose || tailClose) { 
    // ---- something was too close
    showVec('too close result, vec1', vec1);
    showVec('                  vec2', vec2);

    if(vec1 == null) {
      // ---- both ends too close, skipping vec -----
      lastPoint = null;
      return 0; // next segidx is 0

    // ---- an end was too close
    if(segIdx == 1 || tailClose) 
      addHole(point, vec1[0]); // add first hole
    if(vec2) {
      // had intersetion
      // vec was split into vec1 and vec2
      // handle vec2 as first in new segment
      lastPoint = vec2[0];
      handlePoint(vec2[1], 1, segLast);
      if(!segLast) return 2;  // next segidx is 2
    if(segLast || headClose) 
      addHole(vec1[0], vec1[1]); // add last hole
    lastPoint = vec[1];
    return segIdx + 1; // next segidx

  // ---- point was not too close
  showVec('  -- not too close', vec);
  if(segIdx == 1) addHole(point, lastPoint); // add first hole
  if(segLast) addHole(lastPoint, point); // add last hole
  lastPoint = point;
  return segIdx + 1; // next segidx

const getParameterDefinitions = () => {
  return [
    { name: 'text', type: 'text', initial: defStrParam, 
      size: 10, maxLength: 6, caption: 'Display Text:', 
      placeholder: 'Enter 3 to 6 characters' },

const main = (params) => {
  strParam = params.text;

  console.log("---- main ----");

  let strWidth  = 0;
  let strHeight = 0;
  for(const char of strParam) {
    const {width, height} = vectorChar(char);
    strWidth  += width;
    strHeight = Math.max(strHeight, height);

  console.log({strWidth, strHeight});
  const scaleW    = (plateW - padSides*2)  / strWidth;
  const scaleH    = (plateH - padTopBot*2) / strHeight;
  const textScale = Math.min(scaleW, scaleH);
  strWidth       *= textScale;
  strHeight      *= textScale;
  const xOfs      = (plateW - strWidth)/2  - plateW/2;
  const yOfs      = (plateH - strHeight)/2 - plateH/2;

  strWidth  = 0;
  for(const char of strParam) {
    console.log("\n======== CHAR:  " + char + '  ========');
    const {width, segments:segs} = 
           vectorChar({xOffset:strWidth}, char);
    strWidth  += width;
    segs.forEach( seg => {
      console.log("\n--- seg ---");
      let segIdx = 0;
      seg.forEach( point => {
        point[0] *= textScale;
        point[1] *= textScale;
        segIdx = handlePoint(point, segIdx, segIdx == seg.length-1);
  console.log("\n---- end ----");

  addToHullChains(); // add remaining spheres to hullchains
  if(!genPlate) return hullChains;

  const allHulls = hullChains.concat(holes);
  const zOfs     =  plateDepth/2 - textZofs*radius;
  console.log({hullChains, holes, allHulls});
  const hullsOfs = translate([xOfs, yOfs, zOfs], allHulls);
  const plate    = cuboid({size: [plateW, plateH, plateDepth]});
  const plateOut = subtract(plate, hullsOfs);
  return plateOut;

module.exports = {main, getParameterDefinitions};

Posted : 26/01/2022 11:55 pm