Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the wp-graphql domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/wp-includes/functions.php on line 6114
How To Script A Sketch In FreeCAD Using Python - SingerLinks

How To Script A Sketch In FreeCAD Using Python

Link Path 2D
Link Path 2D

In this post we will review how to script a sketch in FreeCAD using python. Our goal is to create a chain link model which we will do entirely via python scripting. This post covers how to script a sketch in FreeCAD and also how to create an additive pipe in FreeCAD using python. The code is part of a larger macro that generates chain link models based on parameters you enter into a dialog box. See the following related posts:

The FreeCAD chainlink generator macro – this post provides the full code and explains how the macro works. If you just want to run code and generate chain links ready to 3D print start here.

The FreeCAD chainlink generator UI – This post explains how the user interface code works.

Create a Part and Body

The first step in the process is to create a new Part and Body. The Body will eventually have the additive pipe that defines the first link in the chain link.

def createPart(self, name):
   # create the chainlink part
   print("create the chainlink part")
   Gui.activateWorkbench("PartDesignWorkbench")
   self.partChainLink = App.activeDocument().addObject('App::Part',name)
   self.partChainLink.Label = name
   self.partChainLink.Type = "ChainLink"
   Gui.activateView('Gui::View3DInventor', True)
   Gui.activeView().setActiveObject('part', self.partChainLink)

def createBody(self, name):
   # create the body for the first link
   print("create the body for the first link")
   self.bodyLink = App.activeDocument().addObject('PartDesign::Body','Body')
   self.bodyLink.Label = 'Link'
   # add the body to the chainlink part
App.activeDocument().getObject(self.bodyLink.Name).adjustRelativeLinks(self.partChainLink)
   App.activeDocument().getObject(self.partChainLink.Name).addObject(self.bodyLink)
   # make the body the active object
   Gui.ActiveDocument.ActiveView.setActiveObject('pdbody',self.bodyLink)

The code above is divided into two methods, one for the Part and one for the Body. Looking at the code in createPart() we see it activates the Part Design Workbench. Then it creates an “App::Part” object in the activeDocument. Then it sets the Label and Type properties of the part. Finally, we activate the “Gui::View3DInventor” view. This causes FreeCAD to display the 3D window to view the sketch.

The createBody() method creates the “PartDesign::Body” object and then sets the Label property. It then uses the “adjustRelativeLinks method of the newly created Body to link it to the Part. Finally the script uses the “addObject” method to add the Body to the Part.

Create the Path Sketch

Link Path 2D
Link Path 2D

This is a sketch of one link in the chain link. It is the “path” sketch needed for the additive pipe object.

def createPathSketch(self,name):
   # create a sketch for the link path
   print("create path sketch")
   self.pathSketch = self.bodyLink.newObject('Sketcher::SketchObject','Sketch')
   self.pathSketch.Label = name
   self.pathSketch.Support = self.bodyLink.Origin.OriginFeatures[3]	
   self.pathSketch.MapMode = 'FlatFace'
   self.pathSketch.Visibility = False

The code above creates a sketch object called pathSketch using the newObject method of the body object created earlier. This is how the sketch is made part of the body object. Then we set the label of the sketch to the name parameter. The next line sets the “Support” property of the sketch to one of the planes of the body. If you look at the tree view in FreeCAD you will see the “Origin” under the body. Inside the Origin are the three axis and three planes of the body. OriginFeatures[3] picks the first plane. Finally, we set the “MapMode” and “Visibility” properties.

def drawPathSketch(self):
   # draw the sketch for the link path
   print("draw path sketch")
   linkEdgeLen = self.scale(self.linkLen) - self.scale(self.linkWidth)
   # top arc
   self.pathSketch.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(25,0,0),App.Vector(0,0,1),50),0,3.14159265),False)
   self.pathSketch.addConstraint(Sketcher.Constraint('Diameter',0,App.Units.Quantity("{} {}".format(str(self.scale(self.linkWidth)), self.unit))))
   self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',0,2,-1,1)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('DistanceY',0,3,0)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('DistanceY',0,1,0)) 
   # left line
   self.pathSketch.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(0,linkEdgeLen * -1,0)),False)
   self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',1,1,0,2)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('Vertical',1)) 
   self.pathSketch.addConstraint(Sketcher.Constraint("DistanceY", 1,2,1,1, App.Units.Quantity("{} {}".format(str(linkEdgeLen), self.unit))))
# right line
   self.pathSketch.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(0,linkEdgeLen * -1,0)),False)
   self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',2,1,0,1)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('Vertical',2)) 
   self.pathSketch.addConstraint(Sketcher.Constraint("DistanceY", 2,2,2,1, App.Units.Quantity("{} {}".format(str(linkEdgeLen),self.unit))))
   #bottom arc
   self.pathSketch.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(self.scale(self.linkWidth)/2,linkEdgeLen*-1,0),App.Vector(0,0,1),self.scale(self.linkWidth)/2),-3.14159265, 0),False)
   self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',1,2,3,1)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,2)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('Horizontal',1,2,3,3)) 

Add An Arc To The Sketch

Now for the fun part – adding geometry to the sketch itself.

self.pathSketch.addGeometry(Part.ArcOfCircle(Part.Circle(App.Vector(25,0,0),App.Vector(0,0,1),50),0,3.14159265),False)

First, we create the top arc using the “Part.ArcOfCircle” method. The Part module contains methods for all the geometry shapes that can be added to a sketch. You can see more examples here. To create an ArcOfCircle you need to supply a circle, along with the start and end angles in radians. The Part.Circle is defined using three points which are hardcoded. The points don’t really matter as we will adjust the resulting arc to what we want using constraints.

Add Diameter Constraint To The Arc

Now let’s add the constraints.

self.pathSketch.addConstraint(Sketcher.Constraint('Diameter',0,App.Units.Quantity("{} {}".format(str(self.scale(self.linkWidth)), self.unit))))

The “addConstraint” method of the sketch is what you use to add constraints. First we add a “Diameter” type constraint which requires two additional parameters – the arc or circle that is being constrained and the amount of the constraint, i.e. the diameter. In the example above I referenced the arc using it’s index in the list of objects in the sketch so “0” refers to the top arc which is the first object we defined. We supply quantities using the “App.Units.Quantity()” method which takes in a string (for example “50 mm”).

App.Units.Quantity("{} {}".format(str(self.scale(self.linkWidth)), self.unit))

To create the proper string value for “App.Units.Quantity” we use the Python format method of generating strings from variables. “self.scale(self.linkWidth)” takes the link width entered into the UI and scales it according to the scale factor entered into the UI. The unit can be either “mm” or “in” which is also defined in the UI.

Coincident Constraint

self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',0,2,-1,1))

Now we will add a Coincident constraint. The Coincident constraint takes 4 parameters:

1. the line that doesn’t move. We specified “0” which is the index into the objects defined in the sketch. “0” is the first item in the list which is the arc we defined above.

2. the point on the “non-moving” line (or arc in our case). We specified “2” which is the 2nd point on the arc. This is the left most point.

3. the line that will move. We specified “-1” which actually refers to the Horizontal “X” axis and not an actual line.

4. the point on the line that will move. We specified “1” which refers to the origin.

The net effect of this constraint is to anchor the left-hand side of the arc to the origin.

For a more detailed explanation of scripting constraints see here.

Vertical Distance Constraint

self.pathSketch.addConstraint(Sketcher.Constraint('DistanceY',0,3,0)) 

Next we add a “DistanceY” constraint to the arc. DistanceY takes 3 parameters as follows:

  1. The index to the object being constrained. We specified “0” which refers to the arc.
  2. The point on the arc. We specified “3” which is the center point of the arc.
  3. The distance to the origin. We specified “0”.

The net effect of this constraint is to lock the center point of the arc on the horizontal axis.

The next constraint is just like the previous one. It adds a DistanceY constraint to the right hand point of the arc. Now the arc is locked in place.

Add A Line Segment To The Sketch

Next we will add a Line segment.

self.pathSketch.addGeometry(Part.LineSegment(App.Vector(0,0,0),App.Vector(0,linkEdgeLen * -1,0)),False)

The line segment is defined by it’s starting point and it’s ending point in 3 dimensional space. These are specified using the “App.Vector” method. The starting point is the origin (0,0,0). The ending point is down the Y axis an amount that was calculated earlier in the script and is the length of the link.

Now we add constraints to the line segment.

self.pathSketch.addConstraint(Sketcher.Constraint('Coincident',1,1,0,2)) 
   self.pathSketch.addConstraint(Sketcher.Constraint('Vertical',1)) 
   self.pathSketch.addConstraint(Sketcher.Constraint("DistanceY", 1,2,1,1, App.Units.Quantity("{} {}".format(str(linkEdgeLen), self.unit))))

The first constraint is a Coincident constraint that locks the beginning of the line to the end of the arc.

The second constraint is a Vertical constraint that sets the line to being vertical.

The third constraint is a DistanceY constraint that sets the distance between the two points on the line. Note the use of the App.Units.Quantity() function to provide the distance as described earlier.

The rest of the code adds a second line segment like the one described above but attached to the other side of the arc. Finally, the bottom arc is added and linked to the line segments. This completes the sketch of the link path.

Create The Profile Sketch

Link Profile 2D
Link Profile 2D
def createProfileSketch(self,name):
   # create an empty sketch for the link profile
   print("create a sketch for the link profile")

   self.profileSketch = self.bodyLink.newObject('Sketcher::SketchObject','Sketch')
   self.profileSketch.Label = name
   self.profileSketch.Support = self.bodyLink.Origin.OriginFeatures[4]	
   self.profileSketch.MapMode = 'FlatFace'
def drawProfileSketch(self):
   # draw the sketch for the link profile
   print("draw profile sketch")
   self.profileSketch.addGeometry(Part.Circle(App.Vector(0.000000,-0.000000,0),App.Vector(0,0,1),10),False)
   self.profileSketch.addConstraint(Sketcher.Constraint('Diameter',0,App.Units.Quantity("{} {}".format(str(self.scale(self.linkDiameter)), self.unit))))
   self.profileSketch.addConstraint(Sketcher.Constraint('Coincident',0,3,-1,1)) 

The profile sketch defines the profile of the additive pipe. In our case, the profile is a simple circle. The code is shown above and is much like the code explained in the path sketch.

Script Additive Pipe

CHAINLINK Link 3D
CHAINLINK Link 3D
def addLinkPipe(self):
   self.linkPipe =       self.bodyLink.newObject('PartDesign::AdditivePipe','AdditivePipe')
   self.linkPipe.Profile = self.profileSketch
   self.linkPipe.Spine = self.pathSketch

Well all the hard work has been completed and now it’s time to create the additive pipe. How to create an additive pipe in FreeCAD using python is shown in the code above. First we use the “newObject” method of the body object to add a “PartDesign::AdditivePipe”. The we just need to set the Profile and Spine properties to the appropriate sketches that we generated in the code shown earlier.

That’s it! The image above shows the first link in what will become the chain link model that is ready to 3D print.

And Finally…

This post described how to script a sketch in FreeCAD using python. We also described how to script an additive pipe. A detailed explanation of sketch and constraint scripting can be found here. I hope you will look at the other posts in this series on how to model chain links for 3D printing.

Total
0
Shares
Previous Post
FreeCAD TaskPanel Dialog

How To Create A Python Script To Display A Form In FreeCAD

Next Post
Chain Link Output

How To Model Chain Link In FreeCAD

Related Posts