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, Arduinoand 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:
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
While the video itself is not very exciting, it shows a machine moving using custom code (of course, I'm excluding the bootloader and stepper firmware, not to mention the complete Linux core running on the pc...).
You can find the code used in the video (Arduino and Python) in this github repository The code is as is (being it a test I have not taken the time to clean it).
The next step is to achieve a continuous movement (currently, it stops completely between instructions) using some kind of instruction buffer.
Since few days ago, this blog is two years old, and, also, last month it crossed the 4000 views/month barrier.
I was thinking about a way of celebrating this events, and last night I saw a .gif image about a flat mechanism at which I could be staring the whole day:
I don't know its exact name, but "double slider mechanism" seems appropriate. It belongs to the family of flat, four bar linkage mechanisms, and, possibly, there is no real use for this one. But it moves very smoothly, with the outer end of the rotating arm drawing some kind of ellipse.
The kinematics of this one are not too difficult (none of the family of four bar linkage mechanism are), and for the animation I have solved it in an analytical way.
The code:
# Javier Martinez Garcia August 2015 GPL V2.0from PySide import QtCore
from math import sin, cos, radians
# retrieve the objects from the document
slider_x = FreeCAD.ActiveDocument.getObject("Pad003002")
slider_y = FreeCAD.ActiveDocument.getObject("Pad003001")
arm = FreeCAD.ActiveDocument.getObject("Pad002001")
# store initial placement (needed to restore initial position)
slider_x_placement = slider_x.Placement
slider_y_placement = slider_y.Placement
arm_placement = arm.Placement
# store object placements in a new variable
r_slider_x_pl = slider_x.Placement
r_slider_y_pl = slider_y.Placement
r_arm_pl = arm.Placement
defreset():
# function to restore initial position of the objects
slider_x.Placement = r_slider_x_pl
slider_y.Placement = r_slider_y_pl
arm.Placement = r_arm_pl
# In this mechanism, "i" represents the angle of the rod in degrees
i =0# update function calculates object position as f(i) and increases idefupdate():
global i
alpha = radians( i )
x =150.0*cos( alpha )
y =150.0*sin( alpha )
slider_x.Placement = FreeCAD.Placement( slider_x_placement.Base + FreeCAD.Vector( 150-x, 0, 0 ),
slider_x_placement.Rotation )
slider_y.Placement = FreeCAD.Placement( slider_y_placement.Base + FreeCAD.Vector( 0, y, 0 ),
slider_y_placement.Rotation )
arm.Placement = FreeCAD.Placement( arm_placement.Base + FreeCAD.Vector( 0, y, 0 ),
FreeCAD.Rotation( FreeCAD.Vector( 0,0,1), i))
# update the scene
FreeCAD.Gui.updateGui()
# increase mechanism input position
i +=1# create a timer object
timer = QtCore.QTimer()
# connect timer event to function "update"
timer.timeout.connect( update )
# start the timer to trigger "update" every 10 ms
timer.start( 10 )
Download the .fcstd model and animation script here, on github.
I am working on a project that has a lot of drill operations. Being an "assembly", it can get quite tedious to create an sketch or part cylinder, place it and then do a boolean cut to make the drill. To speed it up, I have written a short macro that drills existing holes into the selected shapes:
Selection:
Script:
"""
Drill Macro
JMG 2015 GPL
Select a set of circles and then the shapes that you want to drill
"""#get user selection of edges
SelEdges = Gui.Selection.getSelectionEx()[0].SubObjects
# get user selection for shapes
SelectedShapes = Gui.Selection.getSelectionEx()
# create the faces that will be extruded to create the drill
cutFaces = []
for edge in SelEdges:
cutFace = Part.Face(Part.Wire(edge)) # create face from edge
cutFaces.append( cutFace )
# drill all selected shapes minus first one (selected edges)for i inrange(len(SelectedShapes)-1):
#retrieve object from selection list
SelShape = SelectedShapes[i+1].Object
#create an empty part object in the document
drilledShape=FreeCAD.ActiveDocument.addObject("Part::Feature",'Drilled'+SelShape.Name)
drilledShape.Shape = SelShape.Shape
#cut selected shape and assign shape to "drilledShape"for f in cutFaces:
drilledShape.Shape=drilledShape.Shape.cut(f.extrude(f.normalAt(0,0)*1000000))
drilledShape.Shape=drilledShape.Shape.cut(f.extrude(f.normalAt(0,0)*-1000000))
#turn off visibility of the selected shape and set same color for the new object
SelShape.ViewObject.Visibility =False
drilledShape.ViewObject.ShapeColor = SelShape.ViewObject.ShapeColor
Output:
Also, if you want to drill something more complex, like a slot, a variation of the script can do it:
Selection:
Script:
"""
Drill Edges Macro
JMG 2015 GPL
Select a set of edges that form a closed wire and then the shapes
that you want to drill
"""#get user selection of edges
SelEdges = Gui.Selection.getSelectionEx()[0].SubObjects
# get user selection for shapes
SelectedShapes = Gui.Selection.getSelectionEx()
# create the face that will be extruded to create the drill
cutFace = Part.Face(Part.Wire( SelEdges ))
# drill all selected shapes minus first one (selected edges)for i inrange(len(SelectedShapes)-1):
#retrieve object from selection list
SelShape = SelectedShapes[i+1].Object
#create an empty part object in the document
drilledShape=FreeCAD.ActiveDocument.addObject("Part::Feature",'Drilled'+SelShape.Name)
#cut selected shape and assign shape to "drilledShape"
drilledShape.Shape=SelShape.Shape.cut(cutFace.extrude(cutFace.normalAt(0,0)*1000000))
drilledShape.Shape=drilledShape.Shape.cut(cutFace.extrude(cutFace.normalAt(0,0)*-1000000))
#turn off visibility of the selected shape and set same color for the new object
SelShape.ViewObject.Visibility =False
drilledShape.ViewObject.ShapeColor = SelShape.ViewObject.ShapeColor
Result:
I have them in a custom toolbar and they have helped me to do things faster.
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.
Scanning 3d objects mostly relies on several cameras and complex software, so it is not a low budget project. But I want to show you this quick idea I got to virtualize real models using scrap parts, Arduino and FreeCAD:
A 3 degrees of freedom arm that knows its position by the variable resistors that form its joints.
The arduino reads this resistors and prints the code by serial, where a python script running inside FreeCAD waits for the data.
I'm impressed with its accuracy because it recreates the real world objects with some kind of precision, in spite of being poorly built.
This photo is an example:
The propeller seen in the first picture:
With a better built arm, I'm sure this can improve enough to be usable.
Some time ago, when I was investigating about octrees and voxels to create a true CNC simulator, I played a bit with Coin3d, the scene graph render of FreeCAD.
Coin3d is coded in C++, but can be accesed with Python using Pivy.
While I didn't find anything specially useful, I realized that one could easily plot math equations like this:
...which have a nice look and permit you to take curious snapshots, for example:
The code is:
from pivy import coin
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
print sg
for s inrange(5):
for n inrange(-25,25):
for i inrange(-25,25):
col = coin.SoBaseColor()
col.rgb=(i*2,n*2,s*2)
trans = coin.SoTranslation()
# equation: z = f( i, n ) s is to plot at several heights
trans.translation.setValue([i*2.5,n*2.5,(i/2.0)**2+(n/2.0)**2+s*5])
cub = coin.SoSphere()
myCustomNode = coin.SoSeparator()
myCustomNode.addChild(col)
myCustomNode.addChild(trans)
myCustomNode.addChild(cub)
sg.addChild(myCustomNode)
I'm having september exams, and that's the reason for not publishing anything remarkable this month. But that doesn't mean I've stopped doing FreeCAD stuff.
This is what is currently going on:
A GCode generator for 2D CNC cutter. Is very experimental, but starts to show results. (green fast movement, red = given feed speed)
I've also worked with the SheetMetal project, where I re-coded a big part of it. I hope to finish it somewhere around next year.
Related with 2D CNC, I remodelled my crappy machine and did this:
It does more less what is intended. The weird result can be half attributed to the machine and the other half to the code. I coded a bit more after that video, now it draws better.
The machine is working with an arduino DUE and three 4899et. It talks with FreeCAD by serial (USB), with a custom code running in the arduino.
I also tried to create a game using FreeCAD, basically a model of a car controlled by joystick. Is almost working, but because of its non-sense nature, is the lower on my preferences list.
void setup()
{
Serial.begin(9600); /// Start serial at 9600 baud
pinMode( A0, INPUT ); /// Set pin A0 as input
}
void loop()
{
Serial.println( analogRead( A0 ) ); /// Print to serial A0 value
}
Initialize serial and then constantly print the sensor value.
The Python code:
This are the needed libraries:
import serial
from PySide import QtCore
Serial for reading the serial (obvious one) and PySide for the timer object.
To initialize the serial at 9600 baud pointed to the Arduino:
ser = serial.Serial('/dev/ttyACM0', 9600)
The Arduino serial should be in '/dev/ttyACM0' but sometimes it switches to '/dev/ttyACM1',
if you fire up the Arduino Ide and open the serial window, the path is at the window title.
The name "ser" now contains the serial, which we can read this way:
ser.readline()
That prints whatever the serial is saying.
The FreeCAD part:
We need a function that changes the position of the servo arm to the input value. From the arduino, we receive a number from 0 to 1024, a servo rotates ~180 degrees, so a conversion is needed.
defSERVO(valor):
angle = valor*-180.0/1024.0
Position = FreeCAD.Placement(App.Vector(12.5,12,53),App.Vector(0,0,1),angle)
FreeCAD.ActiveDocument.Fillet004.Placement = Position
The first line of the function SERVO is the 1024 to 180Âş conversion, the second and third ones do the position change of the FreeCAD object. Note that the servo arm is called "Fillet004".
You can test this function by giving values to it, like SERVO(200) or SERVO(1000), it should move.
The link:
The function SERIAL links everything together:
servalue0 =0# avoid problems with serial initializationdefSERIAL():
global servalue0
hysteresis =6.0# to smooth the movementtry:
servalue =int(ser.readline()) # to int the serial valueexcept:
servalue = servalue0
if servalue > servalue0 + hysteresis or servalue < servalue0 - hysteresis:
SERVO(servalue) # update the servo position
servalue0 = servalue # keep last value to check hysteresis
The timer:
To give it life we need to call SERIAL function repetitively with a timer:
First line creates timer object, second one connects its signals to the SERIAL function and the third one makes it emit a signal every 1 ms.
Complete Python script:
If you just want to test it, create the Arduino circuit, download the servo model and copy paste this at FreeCAD console:
"""
Javier Martinez Garcia, 2014
"""import serial
from PySide import QtCore
try:
ser = serial.Serial('/dev/ttyACM0', 9600)
except:
ser = serial.Serial('/dev/ttyACM1', 9600)
defSERVO(valor):
angle = valor*-180.0/1024.0
Position = FreeCAD.Placement(App.Vector(12.5,12,53),App.Vector(0,0,1),angle)
FreeCAD.ActiveDocument.Fillet004.Placement = Position
servalue0 =0# avoid problems with serial initializationdefSERIAL():
global servalue0
hysteresis =6.0# to smooth the movementtry:
servalue =int(ser.readline()) # to int the serial valueexcept:
servalue = servalue0
if servalue > servalue0 + hysteresis or servalue < servalue0 - hysteresis:
SERVO(servalue) # update the servo position
servalue0 = servalue # keep last value to check hysteresis
timer = QtCore.QTimer()
timer.timeout.connect(SERIAL)
timer.start(1)
And this is all you need to repeat my video.
There are things that can be improved, for example the timer. Maybe using the threading library can give the same result.
About the possibilities, the communication can be bi-directional too, here I show realworld->FreeCAD, but the opposite is perfectly possible. And the FreeCAD objects attributes that can be changed do not need to be exclusively placement, but color or even parametric models.
Before starting, I must warn that this is not intended to produce any realistic simulation.
I created it as a proof of concept and for fun.
How it works:
The milling script removes material by cutting the workpiece with the tool object (a rectangular box) in a simple Part.cut(tool) operation.
The tool object position is determined by taking the last point and the next point from the path points list. Then, the script creates vector and walks through it by steps. At every step, the tool cuts the workpiece, and every 4 steps, the document is refreshed. Refreshed means the command Part.show(workpiece). Because this command creates a shape every time is called, before Part.show(workpiece), the previous shape is removed.
Wild and dirty.
Below I try to explain how to use this script. You can get the full code here
Creating toolpath:
The first thing we need is the list of points that the "tool" will follow.
This list has the form:
Raw = Part.makeBox(raw_size[0],raw_size[1],raw_size[2])
Raw_shape = App.ActiveDocument.addObject("Part::Feature","Workpiece")
Raw_shape.Shape = Raw
Gui.ActiveDocument.getObject("Workpiece").Visibility=False
This is the function that performs the milling-like action:
L1 = Raw.cut(Tool_shape.Shape)
Part.show(L1)
i=0
n=0.0
swd =3# cut refreshing interval
s=0def Machining():global n, i,feed_rate, L1, Tool_radius, swd, s
if i <=len(Program):
Current_position = Tool_shape.Placement.Base
Vector_trajectory = App.Vector(Program[i+1])-App.Vector(Program[i])
Vector_direction =(App.Vector(Program[i+1])-App.Vector(Program[i])).normalize()
VT_modulus = Vector_trajectory.Length
VD_modulus =1.0if VT_modulus > VD_modulus*n*feed_rate:
Next_position = App.Vector(Program[i])+Vector_direction.multiply(n*feed_rate)+App.Vector(-Tool_radius,-Tool_radius)
n+=0.1else:
Next_Position = App.Vector(Program[i+1])
i+=1
n=0.0
s +=1if s > swd:
App.ActiveDocument.removeObject("Shape")
L1 = L1.removeSplitter()
Part.show(L1)
s =0
Tool_shape.Placement = App.Placement(Next_position, ToolR)
AnimatedTool.Placement = App.Placement(Next_position+App.Vector(Tool_radius,Tool_radius,0), App.Rotation(App.Vector(0,0,1),n*43))
L1 = L1.cut(Tool_shape.Shape)
Then, by calling repeatedly Machining() with a timer starts the animation:
timer=QtCore.QTimer()
timer.timeout.connect(Machining)
timer.start(1)
With the tool-path created above this is the result:
What's next?
Well, I've been playing with creating the points list using a script that follows the contour of a FreeCAD part. That way you will only need to execute that script and then run the simulation.