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.
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 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
ifstr(Surface) =="<Plane object>":
FlatFaces.append(i)
ifstr(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.99and 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.99and M12 < Thickness*1.01:
FlatFaces.remove(n)
breakfor 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":
defTESTF(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
ifstr(Surface) =="<Plane object>":
FlatFaces.append(i)
ifstr(Surface) =="<Cylinder object>":
CylFaces.append(i)
RemoveFaces = []
for i in FlatFaces:
for n in i.Edges:
Len = n.Length
if Len > Thickness*0.99and Len < Thickness*1.01:
RemoveFaces.append(i)
breakfor 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.99and M12 < Thickness*1.01:
FlatFaces.remove(n)
breakfor i in RemoveFaces:
FlatFaces.remove(i)
defTESTF(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 ;)
-Create another function that, with the value of the serial, updates the object in FreeCAD
-Call them repetitively by a timer
The Arduino part:
At the video I'm using an Arduino DUE because it was handy, but an UNO board is valid too.
The electrical thing consists of connecting a variable resistor to the A0 pin, like the scheme:
Any variable resistor above 1kΩ will do the job, in the video I use a 4.7kΩ one.
The code is quite simple:
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.