Part Slicing for Foam-Padded Case

Post here for help on using FreeCAD's graphical user interface (GUI).
Forum rules
and Helpful information
IMPORTANT: Please click here and read this first, before asking for help

Also, be nice to others! Read the FreeCAD code of conduct!
ickby
Veteran
Posts: 3116
Joined: Wed Oct 05, 2011 7:36 am

Re: Part Slicing for Foam-Padded Case

Post by ickby »

I had a stupid error in my script. I did not notive it yesterday as I don't run macros in a local environment... here a fixed version. As noted by deepsoic it does not work with occ7 as there are multiple differences. I think this is worth a bugreport and some investigation, at least the multifuse issue must be handled in FreeCAD, such differences should not be accepted.

Code: Select all

import Part

#get the shape to slice
shape = Gui.Selection.getSelection()[0].Shape

#parameters
thickness = 2; #thickness of foam
slices = 11;   #number of slices 

#needed boiler plate: Bounding box and side faces
bbox = shape.BoundBox;
extbox = Part.makeBox(bbox.XLength*1.2, bbox.YLength*1.2, bbox.ZLength*1.2)
extbox.translate(bbox.Center - extbox.CenterOfMass);
rightFace = extbox.Faces[2]
leftFace = extbox.Faces[3]

for x in range(0, slices):
      
   #build the foam slice we want to use
   foam = Part.makeBox(thickness, bbox.YLength*1.2, bbox.ZLength*1.2);
   foam.translate(App.Vector(bbox.XMin + bbox.XLength/slices*x, bbox.Center.y - foam.CenterOfMass.y, bbox.Center.z - foam.CenterOfMass.z));
   bface = foam.Faces[0];
   
   #get the part sections that needs to be handled by that slice
   section = foam.common(shape);
   
   
   #we need to handle each solid seperate
   for solid in section.Solids:
   
      #get the needed largest needed outline by projecting all faces
      projections = [];
      for face in solid.Faces:
         edges = bface.project([face]);
         face = Part.Face(Part.Wire(edges.Edges));
         projections.append(face);
   
      projection = projections[0].multiFuse(projections);
      Part.show(solid)
      Part.show(projection)
      projection = projection.removeSplitter();
      outline = projection.Faces[0].OuterWire;
   
      #find the left- and rightmost points in the outline and ensure that we have verices there
      
      def splitEdges ( outline, dist ):
         if dist[2][0][3] == 'Edge':
            e = outline.Edges[dist[2][0][4]];
            edges = outline.Edges;
            del edges[dist[2][0][4]]
            split = e.split(dist[2][0][5]);
            edges += split.Edges;
            vertex = split.Vertexes[1];
            return (Part.Wire(edges), vertex);
         
         return (outline, outline.Vertexes[dist[2][0][4]])
   
      res = splitEdges(outline, rightFace.distToShape(outline));
      v1 = res[1];
      res = splitEdges(res[0], leftFace.distToShape(res[0]));
      v2 = res[1];
      outline = res[0];
   
      #build extension lines to ensure we cut out the whole foam
      e = [];
      e.append( Part.Edge(Part.Line(v1.Point, v1.Point + App.Vector(0,0,bbox.ZLength))) );
      e.append( Part.Edge(Part.Line(v1.Point + App.Vector(0,0,bbox.ZLength), v2.Point + App.Vector(0,0,bbox.ZLength))) );
      e.append( Part.Edge(Part.Line(v2.Point + App.Vector(0,0,bbox.ZLength), v2.Point)) );
   
      #find a set of outline edges between the vertices: it doesnt matter which path of the two possibles
      def getNextEdge(edges, vertex):
         for edge in edges:   
            if (edge.Vertexes[0].Point - vertex.Point).Length < 1e-4:
               edges.remove(edge);
               return (edge, edge.Vertexes[1]);
            if (edge.Vertexes[1].Point - vertex.Point).Length < 1e-4:
               edges.remove(edge);
               return (edge, edge.Vertexes[0]);
   
      edges = outline.Edges;
      res = getNextEdge(edges, v2);
      e.append(res[0]);
      while res[0] and ((res[1].Point - v1.Point).Length > 1e-4):
         res = getNextEdge(edges, res[1]);
         e.append(res[0]);
   
      #we are not sure we have build the real outer wire, we could have followed the wrong edge... brute forece again!
      face = Part.Face(outline).fuse(Part.Face(Part.Wire(e)));
      
      #build the cut shape and finaly cut the foam
      hole = face.extrude(App.Vector(thickness, 0 ,0));
      foam = foam.cut(hole).removeSplitter();
   
   Part.show(foam)
   
I added a bug report to ensure this is not forgotten: issue #0002676
Spida
Posts: 5
Joined: Wed Aug 17, 2016 8:22 pm
Location: Germany
Contact:

Re: Part Slicing for Foam-Padded Case

Post by Spida »

ickby wrote:this script should work and hold all your constraints.
Thank you very much for your work, the script looks like it will do exactly what I need.

Sadly, the script does not seem to work on my file.

Issues I found:
  • Using a comparison on

    Code: Select all

    (Point1 - Point2).Length
    to get a distance seems to fail in case both points are coincident.

    Code: Select all

    Point1.distanceToPoint(Point2)
    seems to cover that corner case better, though.
  • Fixing these, I get the following error when I run the script:

    Code: Select all

    Traceback (most recent call last):
      File "script.py", line 36, in <module>
        face = Part.Face(Part.Wire(edges.Edges));
    <class 'Part.OCCError'>: BRep_API: command not done
    As far as I understand, the root cause here is similar: points that should have been connected by an edge are coincident, so no edge is created.
  • If I wrap the contents the second loop (for solid in section.Solids) in a try/except block, I get a segfault.
Since both of these problems are very likely dependent on my layout, I made it available at https://www.spida.net/tmp/freecad/box_part.fcstd

For reference, here is my "fixed" script:

Code: Select all

import Part

#get the case
case = FreeCAD.ActiveDocument.getObjectsByLabel("Case")[0].Shape

#get the shape to slice
shape =  FreeCAD.ActiveDocument.getObjectsByLabel("Scope")[0].Shape

#parameters
thickness = 10;  #thickness of foam

#needed boiler plate: Bounding box and side faces
bbox = case.BoundBox
rightFace = case.Faces[2]
leftFace = case.Faces[3]

for x in range(0, int(bbox.XLength/thickness)):

   #build the foam slice we want to use
   foam = Part.makeBox(thickness, bbox.YLength, bbox.ZLength);
   foam.translate(App.Vector(bbox.XMin + thickness*x, bbox.Center.y - foam.CenterOfMass.y, bbox.Center.z - foam.CenterOfMass.z));
   bface = foam.Faces[0];

   #get the part sections that needs to be handled by that slice
   section = foam.common(shape);


   #we need to handle each solid separately
   for solid in section.Solids:

      #get the needed largest needed outline by projecting all faces
      projections = [];
      for face in solid.Faces:
         edges = bface.project([face]);
         face = Part.Face(Part.Wire(edges.Edges));
         projections.append(face);

      projection = projections[0].multiFuse(projections);
      projection = projection.removeSplitter();
      outline = projection.Faces[0].OuterWire;

      #find the left- and rightmost points in the outline and ensure that we have vertices there

      def splitEdges ( outline, dist ):
         if dist[2][0][3] == 'Edge':
            e = outline.Edges[dist[2][0][4]];
            edges = outline.Edges;
            del edges[dist[2][0][4]]
            split = e.split(dist[2][0][5]);
            edges += split.Edges;
            vertex = split.Vertexes[1];
            return (Part.Wire(edges), vertex);

         return (outline, outline.Vertexes[dist[2][0][4]])

      res = splitEdges(outline, rightFace.distToShape(outline));
      v1 = res[1];
      res = splitEdges(res[0], leftFace.distToShape(res[0]));
      v2 = res[1];
      outline = res[0];

      #build extension lines to ensure we cut out the whole foam
      e = [];
      if v2.Point.distanceToPoint(v2.Point) > 1e-4:
         e.append(Part.Edge(Part.Line(v1.Point, v1.Point + App.Vector(0, 0, bbox.ZLength))) );
         e.append(Part.Edge(Part.Line(v1.Point + App.Vector(0, 0, bbox.ZLength), v2.Point + App.Vector(0, 0, bbox.ZLength))) );
         e.append(Part.Edge(Part.Line(v2.Point + App.Vector(0, 0, bbox.ZLength), v2.Point)) );

      #find a set of outline edges between the vertices: it doesnt matter which path of the two possibles
      def getNextEdge(edges, vertex):
         for edge in edges:
            if edge.Vertexes[0].Point.distanceToPoint(vertex.Point) < 1e-4:
               edges.remove(edge);
               return (edge, edge.Vertexes[1]);
            if edge.Vertexes[1].Point.distanceToPoint(vertex.Point) < 1e-4:
               edges.remove(edge);
               return (edge, edge.Vertexes[0]);

      edges = outline.Edges;
      res = getNextEdge(edges, v2);
      if res:
         e.append(res[0]);
      while res and res[0] and res[1].Point.distanceToPoint(v1.Point) > 1e-4:
         res = getNextEdge(edges, res[1]);
         if res:
            e.append(res[0]);

      #we are not sure we have build the real outer wire, we could have followed the wrong edge... brute force again!
      face = Part.Face(outline).fuse(Part.Face(Part.Wire(e)));

      #build the cut shape and finaly cut the foam
      hole = face.extrude(App.Vector(thickness, 0 ,0));
      foam = foam.cut(hole).removeSplitter();

   Part.show(foam)
User avatar
Kunda1
Veteran
Posts: 13434
Joined: Thu Jan 05, 2017 9:03 pm

Re: Part Slicing for Foam-Padded Case

Post by Kunda1 »

ickby wrote:I had a stupid error in my script. I did not notive it yesterday as I don't run macros in a local environment... here a fixed version. As noted by deepsoic it does not work with occ7 as there are multiple differences. I think this is worth a bugreport and some investigation, at least the multifuse issue must be handled in FreeCAD, such differences should not be accepted.

Code: Select all

import Part

#get the shape to slice
shape = Gui.Selection.getSelection()[0].Shape

#parameters
thickness = 2; #thickness of foam
slices = 11;   #number of slices 

#needed boiler plate: Bounding box and side faces
bbox = shape.BoundBox;
extbox = Part.makeBox(bbox.XLength*1.2, bbox.YLength*1.2, bbox.ZLength*1.2)
extbox.translate(bbox.Center - extbox.CenterOfMass);
rightFace = extbox.Faces[2]
leftFace = extbox.Faces[3]

for x in range(0, slices):
      
   #build the foam slice we want to use
   foam = Part.makeBox(thickness, bbox.YLength*1.2, bbox.ZLength*1.2);
   foam.translate(App.Vector(bbox.XMin + bbox.XLength/slices*x, bbox.Center.y - foam.CenterOfMass.y, bbox.Center.z - foam.CenterOfMass.z));
   bface = foam.Faces[0];
   
   #get the part sections that needs to be handled by that slice
   section = foam.common(shape);
   
   
   #we need to handle each solid seperate
   for solid in section.Solids:
   
      #get the needed largest needed outline by projecting all faces
      projections = [];
      for face in solid.Faces:
         edges = bface.project([face]);
         face = Part.Face(Part.Wire(edges.Edges));
         projections.append(face);
   
      projection = projections[0].multiFuse(projections);
      Part.show(solid)
      Part.show(projection)
      projection = projection.removeSplitter();
      outline = projection.Faces[0].OuterWire;
   
      #find the left- and rightmost points in the outline and ensure that we have verices there
      
      def splitEdges ( outline, dist ):
         if dist[2][0][3] == 'Edge':
            e = outline.Edges[dist[2][0][4]];
            edges = outline.Edges;
            del edges[dist[2][0][4]]
            split = e.split(dist[2][0][5]);
            edges += split.Edges;
            vertex = split.Vertexes[1];
            return (Part.Wire(edges), vertex);
         
         return (outline, outline.Vertexes[dist[2][0][4]])
   
      res = splitEdges(outline, rightFace.distToShape(outline));
      v1 = res[1];
      res = splitEdges(res[0], leftFace.distToShape(res[0]));
      v2 = res[1];
      outline = res[0];
   
      #build extension lines to ensure we cut out the whole foam
      e = [];
      e.append( Part.Edge(Part.Line(v1.Point, v1.Point + App.Vector(0,0,bbox.ZLength))) );
      e.append( Part.Edge(Part.Line(v1.Point + App.Vector(0,0,bbox.ZLength), v2.Point + App.Vector(0,0,bbox.ZLength))) );
      e.append( Part.Edge(Part.Line(v2.Point + App.Vector(0,0,bbox.ZLength), v2.Point)) );
   
      #find a set of outline edges between the vertices: it doesnt matter which path of the two possibles
      def getNextEdge(edges, vertex):
         for edge in edges:   
            if (edge.Vertexes[0].Point - vertex.Point).Length < 1e-4:
               edges.remove(edge);
               return (edge, edge.Vertexes[1]);
            if (edge.Vertexes[1].Point - vertex.Point).Length < 1e-4:
               edges.remove(edge);
               return (edge, edge.Vertexes[0]);
   
      edges = outline.Edges;
      res = getNextEdge(edges, v2);
      e.append(res[0]);
      while res[0] and ((res[1].Point - v1.Point).Length > 1e-4):
         res = getNextEdge(edges, res[1]);
         e.append(res[0]);
   
      #we are not sure we have build the real outer wire, we could have followed the wrong edge... brute forece again!
      face = Part.Face(outline).fuse(Part.Face(Part.Wire(e)));
      
      #build the cut shape and finaly cut the foam
      hole = face.extrude(App.Vector(thickness, 0 ,0));
      foam = foam.cut(hole).removeSplitter();
   
   Part.show(foam)
   
I added a bug report to ensure this is not forgotten: issue #0002676
Any headway on this issue #2676: Differences in python API depending on OCC version ?
Alone you go faster. Together we go farther
Please mark thread [Solved]
Want to contribute back to FC? Checkout:
'good first issues' | Open TODOs and FIXMEs | How to Help FreeCAD | How to report Bugs
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Part Slicing for Foam-Padded Case

Post by DeepSOIC »

The documentation of OCC 7.1.0 still says the same, the output of face+face fusion is a compound of faces (and no shells).
https://www.opencascade.com/doc/occt-7. ... hms_9_4_12
AFAIK nothing was done on FreeCAD side to override this and bring back the old behavior yet.
Post Reply