Showing posts with label sheet metal. Show all posts
Showing posts with label sheet metal. Show all posts

Wednesday, 30 December 2015

NiCr



NiCr (read as 'nicrome') is the name of my CNC Hot Wire Foam Cutter, an OpenSource/OpenHardware project that I'm making public with this post.

The github repository: https://github.com/JMG1/NiCr

 

Description:

As the name says, the machine cuts shapes from foam blocks, being the original intention to cut wing cores for lamination and molding.

I've composed a little video about it:


I'm opening this project in exchange of all the good things that the OpenSource world has given to me (I like to think about it as 'from the OpenSource to the OpenSource').

Details:

The NiCr project can be divided in three parts: Machine, Arduino and FreeCAD


-Machine:

The machine is the physical thing, and is being designed to be built from easy to find, easy to work with materials, like extruded aluminium tubes and simple bolted joints, taking into account a low budget and the DIY factor (only cut/drill/bend operations).

It features two CoreXY frames, easy-to-find Nema17 motors, and a spring tensor for the wire. The design is also very scalable and can, possibly, be applied to other type of machines.

The design has taken place between FreeCAD and the real world:

FreeCAD pictures:

Detail of the Nema17 stepper, X axis slider and belt pulleys

Machine assembled inside FreeCAD
Real world pictures:

Frame size

Slider on the X axis

First version of the cut-wire tensor




-Arduino:

The machine movement is done with an Arduino Mega 2560 board and a Ramps shield, that, together with four stepper drivers (A4988), some limit switches and a power source form all the electronics.
The firmware is being developed specifically for this machine, you can see some of its parts in this links:


-FreeCAD:

The shapes are created using FreeCAD existing tools and converted later to .nicr (similar to GCode) in a custom workbench.

This workbench features a parametric machine, a shape-to-path algorithm, trajectory planning and simulation tools.

Some pictures of the workbench:

Workbench and parametric machine

Cut path simulation result for three wings with different precision settings

Workbench demo video:



The result of the shape-to-path algorithm is a .nicr file that contains instructions similar to GCode and with this comes a question:
Why I have not used the path workbench (in development) and the existing GCode standard?
 Because this machine produces 2.5D shapes (could do '2.75D' with the addition of a fifth axis, to be studied) and the movement is very different to the movement of a 3D printer or mill, and by using 4 axis, someone can be mistaken and use the code for the wrong machine.
Anyway, it is going to be a documented language so export-import tools can be created if needed.


Conclusion:

NiCr is in active development, at the moment I'm trying to achieve a basic stability and usability of the software before releasing (and some documentation too, maybe the hardest thing!).
Once I achieve that, the code and machine 3d parts will be uploaded to github (I have not decided the particular license yet) After a complete day of reading about licenses, I have chosen the GNU GPL.

I'll be updating this post with any news I have.
-> January 1, code uploaded to github: https://github.com/JMG1/NiCr








By the way, have a happy 2016!

Javier.


Monday, 13 July 2015

What's going on this summer?

Hello!

I write this brief post to explain, among other things, what is currently happening with the sheet metal workbench:

The workbench at the moment is at 30%: Document structure is almost done, simple unfold is working and there are tools, like this one, to create even more complex and powerful features.


Also, I talked about some crowdfounding campaign or paid development for this workbench: all it is stopped because I've found a powerful sponsor (to be revealed in a future).

 

Am I working at sheet metal?

No. I'm going to be studying from now until I finish my degree, somewhere around December. But this does not mean a complete shutoff, there are things and important works on the way.

For example, for  the "maker" community, I am developing a new machine that is being born by the end of this year (and is part of my degree project). An open source machine with stepper motors, completely designed with FreeCAD, that works using Arduino and Python and is not a 3D printer.




Also, I've been working in improvements at the "Exploded Assembly Animation workbench" and additions to the macro "WorkFeatures"


In conclusion, things are going to freeze a bit, but no project is going to disappear.


Have a nice summer!!


Javier.

Friday, 5 June 2015

FreeCAD: Sheet Metal Update 2

Some screenshots of the current state of the workbench:

My goal sheet metal part and what can be currently done with the workbench:


It took me less than 5 minutes to draw it (I cheated a bit and did not put any real measurements). It sums up the current capabilities.

The object in the tree view:


You can also see the two orange icons, the only ones by now: "base fold" and "fold on edge".

The properties of the main feature, the fold:


It seems not too different from Update 1, but there has been a lot of work under the hood. Topological naming is a headache (every time a shape is modified, freecad sorts its edges, faces and vertexes randomly), and for the unfold, currently I have two approaches, the one that is currently working, produces this:


The other method, when finished, will allow to unfold partially and set different bend radius and k factors for each fold.

Thought priority is to finish Fold part class and unfold methods, to take a rest from it I code other tools, for example, hem along edges:


Or tear drop fold (a simplified fold object):


Slowly, this is starting to feel mature enough to use it in for real world. Once I solve some key problems, I will launch the crowdfunding campaing.

Any comment is greatly appreciated!

Thursday, 14 May 2015

FreeCAD: Sheet Metal Update 1

After some time without touching FreeCAD, I have managed to go a bit further at the development of the sheet metal workbench. Although is not really a "workbench", but a set of scripts at the moment, most of the critical part is done: workflow, document structure, data tree...

A screenshot:



This video sums up the current possibilities:


Objects are totally parametric and unfold algorithm is easier then ever. Next things to do are folds created from sketches (almost completed), get the unfold algorithm to work correctly with the k factor and other bend methods, set up the workbench with icons, and a lot of small utilities like reliefs, slots, punching...


The idea for this workbench is to be open source, but in a special way, because once completed and tested, I will start some kind of "crowdfunding" campaign so I can get some economic feedback. Once a specified amount is reached, I will upload the complete code to GitHub and start to work at documentation and further integration with FreeCAD (if it is considered good enough by the main devs).

With this method I hope to prove that open source software and economic benefit are not antagonist concepts.

What do you think?

EDITED: The development of this workbench (including funding campaign) is stopped until the first release of the project NiCr.

Sunday, 14 December 2014

FreeCAD: Sheet metal idea part 2

UPDATE 2:  Document structure and basic tools
-
UPDATE: Ulrich has developed an unfolding algorithm that can be used as a macro.
-

Some things have changed since sheet metal part 1, where, for example, faces were classified by being flat or cylindrical, or folds only counted if their angle was 90º.

Even thought the algorithm is not finished (current problem is being discussed here), the new approach I'm going to explain here is more robust and is not even limited to face geometry or fold angles. You can even do folds with different radius, this face classifier can withstand it.

This is the part I'm going to use as example:


It has been created using this script, and you can download it here.

The workflow that I have in mind to unfold a part is:

- Select the face that will behave as base (all faces will be unfolded over the plane by it described )
- Run the script.

Let's start.

Import the libraries:

As always, we need to import some library. The only one we need is "Gui", from the  main FreeCAD library. In addition, we set the thickness of the sheet:

from FreeCAD import Gui
thk = 1.0

Retrieve user selection:

This is a basic command very useful at writing macros:

# Get Selected Shape and Face
SelObject = Gui.Selection.getSelection()[0].Shape
SelFace = Gui.Selection.getSelectionEx()[0].SubObjects[0]

SelObjects contains the selected shape and SelFace contains the selected face. Note that user only needs to click on the face and with that we can take the whole object.

Get ride off non useful faces:

The action starts here, but first: Which are the non-useful faces?
They are the ones marked in green at the picture:


This faces are not needed because they do not store useful geometry data. To clear things in a future, we are going to create a list excluding this faces:

faceList = [] # Create an empty list that will hold the filtered faces
for face in SelObject.Faces:
  apnd = True
  for edge in face.Edges: # Measure length of all edges from all faces
    if abs( edge.Length - thk ) < 0.001: #tolerance because float point things
      apnd = False # If edge length is equal to thickness, do not append it.
      break
  
  if apnd:  # If the face hasn't any edge with length == thickness, append it.
    faceList.append( face )


Did it work? Is something easy to test: select a face from the object and paste at FreeCAD's python console all the code.
Then, do:

len( SelObject.Faces )

len( faceList )


The first one gives "46" and the second "18". That's a great data reduction!

Unfold tree: step 1

We have a list containing only meaningful data, but this data is duplicated. Why? 






Is duplicated because the faces selected in the first picture (back of the part) are parallel to the faces on the second picture (top of the part). While is easy to create another filter to remove the repeated faces, is very hard to not lose the connection between faces with it.

So we are going to create a list containing wich face links to wich face or group of faces. This list has this form:

    index       A        B        C        D        E        F
     "list" =  [ [B],[A,C,D],[B],  [B,E,F], [D],    [D] ]

The index number belongs to the position in "faceList" of the face. The content under the index are the faces linked to that face.
To explain it better, "list" is the solution to faces links of this simplified part:



So how we create a list like that one?
Just run this little "monster":

# auxiliar function: find face number (index) in faceList:
def gfN( inFace ):
  SF_COM = inFace.CenterOfMass
  SF_NOR = inFace.normalAt( 0, 0 )
  for n in range( len( faceList ) ):
    F_COM = faceList[n].CenterOfMass
    F_NOR = faceList[n].normalAt(0,0)
    if SF_COM == F_COM and SF_NOR == F_NOR:
      break

  return n

# get selected face position (index)  in faceList:
SelFaceNumber = gfN( SelFace )

# auxiliar function: get faces linked to input face (returns position in faceList )
def gfR( inFaceN ):
  temporalList0 = []
  inFace = faceList[inFaceN]
  for inEdge in inFace.Edges:
    if str( inEdge.Curve )[1:5] == "Line":
      P_ia = inEdge.valueAt( 0.0 )
      P_ib = inEdge.valueAt( inEdge.Length )
      V_0 = ( P_ib - P_ia )
      for n in range( len( faceList ) ):
        if n != inFaceN:
          face = faceList[n]
          for edge in face.Edges:
            P_a = edge.valueAt( 0.0 )
            P_b = edge.valueAt( edge.Length )
            V1 = P_b - P_ib
            V2 = P_a - P_ia
            condition0 = abs( ( V_0.cross( V1 ) ).Length ) < 0.0001
            condition1 = abs( ( V_0.cross( V2 ) ).Length ) < 0.0001
            if condition0 and condition1:
              faceNumber = gfN( face )
              temporalList0.append( faceNumber )
              break

  # clean from repeated faces
  temporalList1 = []
  for i in temporalList0:
    if not( i in temporalList1 ):
      temporalList1.append( i )

  return temporalList1



compFaceRel = []
for fn in range( len( faceList ) ):
  data = gfR( fn )
  compFaceRel.append( data )


If you run the code above, and then type "comFaceRel" you should see something like:

[[3, 2], [4, 5], [0, 3, 6], [0, 7], [1, 8], [1, 4, 9], [2], [3, 10, 11], [4], [5, 12, 13], [7, 14], [7, 10, 15], [9, 13, 16], [9, 17], [10], [11], [12], [13]]

How a link is recognized:



faceA is the current face being analyzed to inspect its links. FaceB is one of all the faces contained in facesList suspicious of being linked with faceA. 
V_0 the director vector of the current edge of faceA being inspected. V1 and V2 are vectors going from the end-points of the edge from faceA to the end-points of an edge owned by faceB. 
A face is linked if the cross products V1xV0 and V2xV0 have a length of 0.

All this is calculated inside the previous function "gfR".


At the moment, this is the current state of the sheet metal workbench. Next step is to get the link between final nodes of the tree of faces to the selected face to unfold. 
Taking the previous example model with faces A, B, C..., the needed list previous to unfold is, with B as unfold base:
                                                  [ [A,B], [C,D], [F,D,B], [E,D,B]]

Knowing that list would be the major step forward. With it, the unfold algorithm would be almost finished.


Complete code:

# JMG december 2014
from FreeCAD import Gui

thk = 1.0

# Get Selected Shape and Face
SelObject = Gui.Selection.getSelection()[0].Shape
SelFace = Gui.Selection.getSelectionEx()[0].SubObjects[0]

# remove faces placed on the thickness
faceList = [] # Create an empty list that will hold the filtered faces
for face in SelObject.Faces:
  apnd = True
  for edge in face.Edges: # Measure length of all edges from all faces
    if abs( edge.Length - thk ) < 0.001: #tolerance because float point things
      apnd = False # If edge length is equal to thickness, do not append it.
      break
  
  if apnd:  # If the face hasn't any edge with length == thickness, append it.
    faceList.append( face )


# auxiliar function: find face number (index) in faceList:
def gfN( inFace ):
  SF_COM = inFace.CenterOfMass
  SF_NOR = inFace.normalAt( 0, 0 )
  for n in range( len( faceList ) ):
    F_COM = faceList[n].CenterOfMass
    F_NOR = faceList[n].normalAt(0,0)
    if SF_COM == F_COM and SF_NOR == F_NOR:
      break

  return n

# get selected face position (index)  in faceList:
SelFaceNumber = gfN( SelFace )

# auxiliar function: get faces linked to input face (returns position in faceList )
def gfR( inFaceN ):
  temporalList0 = []
  inFace = faceList[inFaceN]
  for inEdge in inFace.Edges:
    if str( inEdge.Curve )[1:5] == "Line":
      P_ia = inEdge.valueAt( 0.0 )
      P_ib = inEdge.valueAt( inEdge.Length )
      V_0 = ( P_ib - P_ia )
      for n in range( len( faceList ) ):
        if n != inFaceN:
          face = faceList[n]
          for edge in face.Edges:
            P_a = edge.valueAt( 0.0 )
            P_b = edge.valueAt( edge.Length )
            V1 = P_b - P_ib
            V2 = P_a - P_ia
            condition0 = abs( ( V_0.cross( V1 ) ).Length ) < 0.0001
            condition1 = abs( ( V_0.cross( V2 ) ).Length ) < 0.0001
            if condition0 and condition1:
              faceNumber = gfN( face )
              temporalList0.append( faceNumber )
              break

  # clean from repeated faces
  temporalList1 = []
  for i in temporalList0:
    if not( i in temporalList1 ):
      temporalList1.append( i )

  return temporalList1



compFaceRel = []
for fn in range( len( faceList ) ):
  data = gfR( fn )
  compFaceRel.append( data )




Bye!

Thursday, 11 December 2014

FreeCAD: Sheet metal tool "Add Wall"

 Thought not seen here, I work in the sheet metal workbench from time to time (last update), being my greater effort coding the unfold algorithm (related post). But I also work at the surrounding tools that this workbench needs, one of them is almost usable and is the reason of this post.

Add wall tool:


When I create the "dummy" shapes for testing the unfolding algorithm, the process is somewhat tedious. So I created this tool and developed it a bit to be easily integrated in the workbench in a future.

The utility of the tool is explained with this video:



Code can be found here

How to use it:

The script contains the function  "addWall", when calling it this are the possible input parameters:

thk = float:           Sheet thickness, 1.0 is the default value
bendR =  float:     Bend radius, default value is 3.0
alpha = float:       Angle of the new wall plane in reference to previous shape plane. 0 will give error (infinite bending radius needed ). By default 90º

L1 and L2 are better explained with a picture:


L1 and L2 values (float) modify the lengths noted by the white dimension marks. By default 0.0

Relief = boolean:  Create or not square relief slots (I think this is the correct name, but tell me if not) like this:


Relief slots have two parameters:

rlfWidth = float: The width of the relief, by default 0.5
rlfDepth = float: The depth of the relif, by default 1.0

Other parameters are:

inverted = boolean: Reverse bend direction, false by default
create = boolean:  This is to merge new wall with current object, true by default. Workbench utility
sketch = boolean: Automatically places a new sketch on new wall, false by default.


To use it, copy-paste the code at FreeCAD's  python console, select one suitable face to place a new wall and then type "addWall( arguments )".

It should work.


Bye!

Thursday, 24 July 2014

FreeCAD: Sheet metal idea part 1

A sheet metal workbench will be a great addition to FreeCAD. In fact, there are several posts in the forum wishing for it.
Here I show my modest attempt to create such functionality for FreeCAD, in a very early stage of development.

I've divided the algorithm in three stages:

Part 1: Explore the shape
Part 2: Get the particular geometry of every face
Part 3: Unfold

Once it works correctly, I will try to code a modelling tool to add walls, perforations, and standard elements. (UPDATED: See sheet metal idea part 2 for improved algorithms and further info )

Part 1

As said above, here I`ll try to filter an input shape that meets some conditions, let's go.

First: create the 3d object

The sheet object is created manually at the moment. In a future, a specific tool can be developed to make it easier. If you want to jump this step, get the final model here

In a new document, create a sketch on XZ:


What we are going to build there is the profile of the folded sheet, so thickness and the bending radius are set here. 
We can start by drawing the folds:


They must be concentric and the inner one should measure the bending radius.

Now we draw the rest of it:


Applying the correspondent constrains you should obtain something like this:


Where we set the thickness of the sheet to 2 mm and the bending radius to 8 mm.

Now, extrude the sketch:



For example, 100 mm.


Now create a hole on top and cut the sheet to change its square form:


Pocket the sketch and this is the result:



The algorithm:

The algorithm works by exploring the shape with some variables in mind, like bending radius, thickness, 90º degree bending angles and that the part is based on XY plane.
Use it by selecting the shape on the tree-view and copy-paste the code.

Import the libraries:

import Part
import math as mt

Basic definitions needed:

Thickness = 2.0
BendingRadius = 8
k = 0.33
Alpha = 90.0

Get user selection:

SObj = Gui.Selection.getSelection()[0]
SObj_Shape = SObj.Shape

Create empty lists:

Faces = []
FlatFaces = []
CylFaces = []

Get all faces of the selected object and gather them in the list "Faces"

for i in SObj_Shape.Faces:
  Faces.append(i)


Classify the gathered faces by being flat or cylindrical:

for i in Faces:
  Surface = i.Surface
  if str(Surface) == "<Plane object>":
    FlatFaces.append(i)
  if str(Surface) == "<Cylinder object>":
    CylFaces.append(i)


At the moment we have all the faces of the shape classified by being cylindrical (bends) and flat. 

The next step is to remove the faces marked on the picture, because we do not need them


To do it:

RemoveFaces = []

for i in FlatFaces:
  for n in i.Edges:
    Len = n.Length
    if Len > Thickness*0.99 and Len < Thickness*1.01:
      RemoveFaces.append(i)
      break

It searches for faces which have one of their edges equal to the sheet thickness (with a tolerance, to ride off floats inaccuracy) and appends them to the new list RemoveFaces.

for i in RemoveFaces:
  FlatFaces.remove(i)

With that sentence the non desired faces are removed from the main list "FlatFaces"

The next faces to remove are the parallel ones, we need only one of them:




This works this way:

-Get the center of mass of a face
-Get the center of mass of another face
-Are them separated by the sheet thickness?
-If they are, append one of them to RemoveFaces

RemoveFaces = []
for i in FlatFaces:
  C1 = i.CenterOfMass
  for n in FlatFaces:
    C2 = n.CenterOfMass
    V12 = C2 - C1
    M12 = abs(V12.Length)
    if M12 > Thickness*0.99 and M12 < Thickness*1.01:
      FlatFaces.remove(n)
      break

for i in RemoveFaces:
  FlatFaces.remove(i)



To finish this post (I've more coded, future posts about this will come ;) ), a test to see what is in the list "FlatFaces":

def TESTF(FlatFaces):
  for i in FlatFaces:
    center = i.CenterOfMass
    Origin = center
    Origin_Vertex = Part.Vertex(Origin)
    Origin = App.ActiveDocument.addObject("Part::Feature","Test_Point")
    Origin.Shape = Origin_Vertex
    Origin_User_Name = Origin.Label
    FreeCADGui.ActiveDocument.getObject(Origin_User_Name).PointColor = (0.33, 0.00, 1.00)
    FreeCADGui.ActiveDocument.getObject(Origin_User_Name).PointSize = 5.00


The function input is a list containing faces. It draws a point at the center of mass of every face of the list, and if we apply it to our "FlatFaces" list we obtain:


That means we had a success at filtering the input shape!

Next steps are gather what is inside face (hole, squares...) and unfold.

Part 1 complete code:

"""
Javier Martinez Garcia, 2014
"""
import Part
import math as mt

Thickness = 2.0
BendingRadius = 8
k = 0.33
Alpha = 90.0

SObj = Gui.Selection.getSelection()[0]
SObj_Shape = SObj.Shape

Faces = []
FlatFaces = []
CylFaces = []

for i in SObj_Shape.Faces:
  Faces.append(i)

for i in Faces:
  Surface = i.Surface
  if str(Surface) == "<Plane object>":
    FlatFaces.append(i)
  if str(Surface) == "<Cylinder object>":
    CylFaces.append(i)

RemoveFaces = []

for i in FlatFaces:
  for n in i.Edges:
    Len = n.Length
    if Len > Thickness*0.99 and Len < Thickness*1.01:
      RemoveFaces.append(i)
      break

for i in RemoveFaces:
  FlatFaces.remove(i)

RemoveFaces = []
for i in FlatFaces:
  C1 = i.CenterOfMass
  for n in FlatFaces:
    C2 = n.CenterOfMass
    V12 = C2 - C1
    M12 = abs(V12.Length)
    if M12 > Thickness*0.99 and M12 < Thickness*1.01:
      FlatFaces.remove(n)
      break

for i in RemoveFaces:
  FlatFaces.remove(i)

def TESTF(FlatFaces):
  for i in FlatFaces:
    center = i.CenterOfMass
    Origin = center
    Origin_Vertex = Part.Vertex(Origin)
    Origin = App.ActiveDocument.addObject("Part::Feature","Test_Point")
    Origin.Shape = Origin_Vertex
    Origin_User_Name = Origin.Label
    FreeCADGui.ActiveDocument.getObject(Origin_User_Name).PointColor = (0.33, 0.00, 1.00)
    FreeCADGui.ActiveDocument.getObject(Origin_User_Name).PointSize = 5.00


TESTF(FlatFaces)

Feel free to criticize or point out anything you consider ;)

Bye!