Browse Source

Workbench for LaserCladding at KVT (initial commit)

master
Jörg Kurlbaum 3 years ago
commit
64302c1b92
  1. 5
      Init.py
  2. 47
      InitGui.py
  3. 6
      Resources/LaserCladding.qrc
  4. 351
      Resources/icons/Laser_Workbench.svg
  5. 6
      lasercladding/__init__.py
  6. 77
      lasercladding/commands.py
  7. 110
      lasercladding/job.py
  8. 168
      lasercladding/kuka.py
  9. 149
      lasercladding/pad.py
  10. 16
      lasercladding/path.py

5
Init.py

@ -0,0 +1,5 @@
# Workbench LaserCladding
import FreeCAD as App
# add Import/Export types
#App.addExportType("Kuka KRL (*.src)", "exportKukaSrc")

47
InitGui.py

@ -0,0 +1,47 @@
import FreeCAD as App
class LaserCladding (Workbench):
MenuText = "Laser Cladding"
ToolTip = "Create Simple Path on workpieces for Laser Cladding"
Icon = """"""
def __init__(self):
__dirname__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "LaserCladding")
_tooltip = "The LaserCladding Workbench can create Paths for Robots"
self.__class__.Icon = os.path.join(__dirname__,
"Resources", "icons",
"Laser_Workbench.svg")
def Initialize(self):
"""This function is executed when the workbench is first activated.
It is executed once in a FreeCAD session followed by the Activated function.
"""
import lasercladding
self.list = ["CreateCladdingJob", "SelectBaseReference", "CreatePad"] # A list of command names created in the line above
self.appendToolbar("My Commands",self.list) # creates a new toolbar with your commands
self.appendMenu("LaserCladding",self.list) # creates a new menu
self.appendMenu(["LaserCladding","My submenu"],self.list) # appends a submenu to an existing menu
def Activated(self):
"""This function is executed whenever the workbench is activated"""
#from importlib import reload
#reload(lccmd)
return
def Deactivated(self):
"""This function is executed whenever the workbench is deactivated"""
return
def ContextMenu(self, recipient):
"""This function is executed whenever the user right-clicks on screen"""
# "recipient" will be either "view" or "tree"
self.appendContextMenu("My commands",self.list) # add commands to the context menu
def GetClassName(self):
# This function is mandatory if this is a full Python workbench
# This is not a template, the returned string should be exactly "Gui::PythonWorkbench"
return "Gui::PythonWorkbench"
Gui.addWorkbench(LaserCladding())

6
Resources/LaserCladding.qrc

@ -0,0 +1,6 @@
<RCC>
<qresource>
<file>icons/Laser_Workbench.svg</file>
<!-- <file>ui/preferences-draft.ui</file> -->
</qresource>
</RCC>

351
Resources/icons/Laser_Workbench.svg

@ -0,0 +1,351 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2766"
height="64px"
width="64px"
sodipodi:docname="Laser_Workbench.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1278"
inkscape:window-height="1417"
id="namedview975"
showgrid="false"
inkscape:zoom="8.5625"
inkscape:cx="32"
inkscape:cy="32"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2766" />
<defs
id="defs2768">
<linearGradient
id="linearGradient3787">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3789" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3791" />
</linearGradient>
<linearGradient
id="linearGradient3864">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868" />
</linearGradient>
<linearGradient
gradientTransform="matrix(1.0614931,0,0,1,-540.96941,-73.37642)"
y2="100.10708"
x2="609.54919"
y1="126.79625"
x1="581.26331"
gradientUnits="userSpaceOnUse"
id="linearGradient3181"
xlink:href="#linearGradient3864" />
<linearGradient
y2="92.711899"
x2="626.31323"
y1="92.711899"
x1="605.94659"
gradientTransform="matrix(-0.72140579,0.77867919,-0.73356972,-0.67961421,545.35072,-389.46656)"
gradientUnits="userSpaceOnUse"
id="linearGradient3184"
xlink:href="#linearGradient3864" />
<linearGradient
id="linearGradient3864-0">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3864-0"
id="linearGradient3217"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0614931,0,0,1,-643.22843,-82.445766)"
x1="605.94659"
y1="92.711899"
x2="626.31323"
y2="92.711899" />
<linearGradient
y2="92.711899"
x2="626.31323"
y1="92.711899"
x1="605.94659"
gradientTransform="matrix(1.1915091,0,0,1,-772.13265,-84.532596)"
gradientUnits="userSpaceOnUse"
id="linearGradient3184-4-0"
xlink:href="#linearGradient3864-0-2" />
<linearGradient
id="linearGradient3864-0-2">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-8" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-3" />
</linearGradient>
<linearGradient
y2="92.711899"
x2="626.31323"
y1="92.711899"
x1="605.94659"
gradientTransform="matrix(1.1915091,0,0,1,-772.13265,-84.532596)"
gradientUnits="userSpaceOnUse"
id="linearGradient3184-4-4"
xlink:href="#linearGradient3864-0-9" />
<linearGradient
id="linearGradient3864-0-9">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-9" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-4" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3864-0-9-5"
id="linearGradient4054-6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0614931,0.54122665,0,1,-692.52246,-451.18305)"
x1="634.20868"
y1="91.597527"
x2="679.06909"
y2="102.88628" />
<linearGradient
id="linearGradient3864-0-9-5">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-9-0" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-4-0" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3864-0-9-5"
id="linearGradient4090"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0614931,0.54122665,0,1,-687.73118,-415.49631)"
x1="634.20868"
y1="91.597527"
x2="679.06909"
y2="102.88628" />
<linearGradient
xlink:href="#linearGradient3864-0-9-5-0"
id="linearGradient4090-6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0614931,0.54122665,0,1,-687.73118,-415.49631)"
x1="634.20868"
y1="91.597527"
x2="679.06909"
y2="102.88628" />
<linearGradient
id="linearGradient3864-0-9-5-0">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-9-0-2" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-4-0-5" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3864-0-9-5-0"
id="linearGradient4124"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.80817481,0.4120665,0,0.76135663,-517.34974,-304.44262)"
x1="634.20868"
y1="91.597527"
x2="679.06909"
y2="102.88628" />
<linearGradient
xlink:href="#linearGradient3864-0-2-2"
id="linearGradient4018-2"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.5385683,-0.86055196,0,0.96535888,-879.38043,517.26249)"
x1="635.40765"
y1="100.79263"
x2="672.73157"
y2="100.2725" />
<linearGradient
id="linearGradient3864-0-2-2">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-8-9" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-3-2" />
</linearGradient>
<linearGradient
y2="48.117603"
x2="709.04407"
y1="114.56509"
x1="663.32715"
gradientTransform="matrix(1.5385683,0.52247795,0,0.96535888,-912.7401,-396.04414)"
gradientUnits="userSpaceOnUse"
id="linearGradient4073-8"
xlink:href="#linearGradient3864-0-1" />
<linearGradient
id="linearGradient3864-0-1">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-2" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-47" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3864-0-9-6"
id="linearGradient4054-8"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.5385683,0.52247795,0,0.96535888,-912.7628,-417.24568)"
x1="634.20868"
y1="91.597527"
x2="679.06909"
y2="102.88628" />
<linearGradient
id="linearGradient3864-0-9-6">
<stop
style="stop-color:#0619c0;stop-opacity:1;"
offset="0"
id="stop3866-6-9-3" />
<stop
style="stop-color:#379cfb;stop-opacity:1;"
offset="1"
id="stop3868-2-4-7" />
</linearGradient>
</defs>
<g
id="layer1">
<path
id="rect3200-5-3"
d="M 33,35 5,49 33,59 59,45 z m 0,5 16,6 -16,8 -17,-6 z"
style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
<metadata
id="metadata57">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="" />
<dc:date>Mon Oct 10 13:44:52 2011 +0000</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>[wmayer]</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg</dc:identifier>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
<dc:subject>
<rdf:Bag>
<rdf:li>box</rdf:li>
<rdf:li>plane</rdf:li>
<rdf:li>rectangle</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>A box floating above a projection of its lower face</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#ff0000;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 33.119982,5.9005474 V 43.968066"
id="path1011" />
<ellipse
style="fill:#ff1a00;fill-opacity:1;stroke:none;stroke-width:9.49383"
id="path1020"
cx="33.058163"
cy="44.76939"
rx="1.6904655"
ry="1.6950274" />
<path
style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 33.119982,43.968066 38.117245,40"
id="path1022" />
<path
style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 33.119982,43.968066 29.151916,38.970803"
id="path1022-3" />
<path
style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 33.119982,43.968066 -6.204197,-1.49202"
id="path1022-6"
inkscape:transform-center-x="2.4142336"
inkscape:transform-center-y="-0.3120438" />
<path
style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 33.119982,43.968066 -5.853996,2.539469"
id="path1022-5"
inkscape:transform-center-x="-2.177793"
inkscape:transform-center-y="2.5275147" />
<path
style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 33.316894,43.931615 5.935078,2.343723"
id="path1022-35"
inkscape:transform-center-x="-3.2044307"
inkscape:transform-center-y="1.9014819" />
<path
style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 33.119982,43.968066 6.311789,-0.937814"
id="path1022-62" />
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

6
lasercladding/__init__.py

@ -0,0 +1,6 @@
# module lasercladding
from .commands import *
from .path import *
from .job import *
from .kuka import *

77
lasercladding/commands.py

@ -0,0 +1,77 @@
import FreeCAD as App
import FreeCADGui as Gui
import Part
from .job import LaserJob, ViewProviderLaserJob
from .pad import LaserPad, create_laserpad
class CreateCladdingJob():
def Activated(self):
# Here your write what your ScriptCmd does...
App.Console.PrintMessage('Create Lasser Cladding Job')
a=App.ActiveDocument.addObject("App::FeaturePython","LaserJob")
LaserJob(a)
ViewProviderLaserJob(a.ViewObject)
def GetResources(self):
return {'Pixmap' : 'path_to_an_icon/myicon.png', 'MenuText': 'Create Cladding Job', 'ToolTip': 'Add a Job to your Document'}
class SelectBaseReference():
def Activated(self):
# Here your write what your ScriptCmd does...
App.Console.PrintMessage('Select Base reference!')
if not Gui.Selection.hasSelection():
App.Console.PrintMessage('Select a Vertex')
return
# check length
selection = Gui.Selection.getSelectionEx()
# find first vertex
for s in selection:
if s.HasSubObjects:
for obj in s.SubObjects:
if isinstance(obj, Part.Vertex):
vertex = obj.copy()
laserjob_entry = App.ActiveDocument.getObject('LaserJob')
if laserjob_entry is None:
App.Console.PrintMessage('Create a LaserJob first')
return
laserjob_entry.base_reference = App.Vector((vertex.X, vertex.Y, vertex.Z))
App.ActiveDocument.recompute()
def GetResources(self):
return {'Pixmap' : 'path_to_an_icon/myicon.png', 'MenuText': 'Select Base reference', 'ToolTip': 'Add a Job to your Document'}
class CreatePad():
def Activated(self):
# Here your write what your ScriptCmd does...
App.Console.PrintMessage('Select some Face as reference')
if not Gui.Selection.hasSelection():
App.Console.PrintMessage('Select a Face')
return
# check length
ref_face = (Gui.Selection.getSelection()[0],
Gui.Selection.getSelectionEx()[0].SubElementNames[0])
#selection = Gui.Selection.getSelectionEx()
# find first vertex
#for s in selection:
# if s.HasSubObjects:
# for obj in s.SubObjects:
# if isinstance(obj, Part.Face):
# face = obj.copy()
create_laserpad(ref_face)
App.ActiveDocument.recompute()
def GetResources(self):
return {'Pixmap' : 'path_to_an_icon/myicon.png', 'MenuText': 'Select Base reference', 'ToolTip': 'Add a Job to your Document'}
Gui.addCommand('CreateCladdingJob', CreateCladdingJob())
Gui.addCommand('SelectBaseReference', SelectBaseReference())
Gui.addCommand('CreatePad', CreatePad())

110
lasercladding/job.py

@ -0,0 +1,110 @@
import FreeCAD as App
class LaserJob:
def __init__(self, obj):
'''Add some custom properties to our box feature'''
obj.addProperty("App::PropertyVector", "base_reference", "Reference", "Reference Point (teached)")
obj.base_reference = App.Vector(0,0,0)
## to make addObject() available
obj.addExtension("App::GroupExtensionPython", None)
obj.Proxy = self
def addObject(self, obj):
self.pads = [obj]
def onChanged(self, fp, prop):
'''Do something when a property has changed'''
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
def execute(self, fp):
'''Do something when doing a recomputation, this method is mandatory'''
App.Console.PrintMessage("Recompute Python Box feature\n")
class ViewProviderLaserJob:
def __init__(self, obj):
'''Set this object to the proxy object of the actual view provider'''
obj.addProperty("App::PropertyColor","Color","Box","Color of the box").Color=(1.0,0.0,0.0)
obj.addExtension("Gui::ViewProviderGroupExtensionPython", None)
obj.Proxy = self
def attach(self, obj):
'''Setup the scene sub-graph of the view provider, this method is mandatory'''
self.onChanged(obj,"Color")
def updateData(self, fp, prop):
'''If a property of the handled feature has changed we have the chance to handle this here'''
# fp is the handled feature, prop is the name of the property that has changed
pass
def getDisplayModes(self,obj):
'''Return a list of display modes.'''
modes=[]
modes.append("Shaded")
modes.append("Wireframe")
return modes
def getDefaultDisplayMode(self):
'''Return the name of the default display mode. It must be defined in getDisplayModes.'''
return "Shaded"
def setDisplayMode(self,mode):
'''Map the display mode defined in attach with those defined in getDisplayModes.\
Since they have the same names nothing needs to be done. This method is optional'''
return mode
def onChanged(self, vp, prop):
'''Here we can do something when a single property got changed'''
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
#if prop == "Color":
# c = vp.getPropertyByName("Color")
# self.color.rgb.setValue(c[0],c[1],c[2])
def getIcon(self):
'''Return the icon in XPM format which will appear in the tree view. This method is\
optional and if not defined a default icon is shown.'''
return """
/* XPM */
static const char * ViewProviderBox_xpm[] = {
"16 16 6 1",
" c None",
". c #141010",
"+ c #615BD2",
"@ c #C39D55",
"# c #000000",
"$ c #57C355",
" ........",
" ......++..+..",
" .@@@@.++..++.",
" .@@@@.++..++.",
" .@@ .++++++.",
" ..@@ .++..++.",
"###@@@@ .++..++.",
"##$.@@$#.++++++.",
"#$#$.$$$........",
"#$$####### ",
"#$$#$$$$$# ",
"#$$#$$$$$# ",
"#$$#$$$$$# ",
" #$#$$$$$# ",
" ##$$$$$# ",
" ####### "};
"""
def __getstate__(self):
'''When saving the document this object gets stored using Python's json module.\
Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\
to return a tuple of all serializable objects or None.'''
return None
def __setstate__(self,state):
'''When restoring the serialized object from document we have the chance to set some internals here.\
Since no data were serialized nothing needs to be done here.'''
return None

168
lasercladding/kuka.py

@ -0,0 +1,168 @@
import FreeCAD
import numpy as np
import math
import time
import Part
TeachPointFold = """
;FOLD LIN P4 Vel= 0.2 m/s CPDAT1 Tool[1] Base[0];%{PE}%R 5.4.27,%MKUKATPBASIS,%CMOVE,%VLIN,%P 1:LIN, 2:P4, 3:, 5:0.2, 7:CPDAT1
$BWDSTART = FALSE
LDAT_ACT=LCPDAT1
FDAT_ACT=FP4
BAS(#CP_PARAMS,0.2)
LIN XP4
;ENDFOLD
"""
TeachPointDat = """
DECL E6POS XP4={X -25.1844196,Y 1122.42603,Z 1158.07996,A -14.3267002,B 0.537901878,C 179.028305,S 6,T 59,E1 0.0,E2 0.0,E3 0.0,E4 0.0,E5 0.0,E6 0.0}
DECL FDAT FP4={TOOL_NO 1,BASE_NO 0,IPO_FRAME #BASE,POINT2[] " "}
DECL LDAT LCPDAT1={VEL 2.0,ACC 100.0,APO_DIST 100.0,APO_FAC 50.0,ORI_TYP #VAR}
"""
header_src = """&ACCESS RVP
&REL 1
&PARAM TEMPLATE = C:\KRC\Roboter\Template\ExpertVorgabe
&PARAM EDITMASK = *
"""
class Kuka_Prog:
def __init__(self):
self.pose_list = []
self.base = (0,0,0)
def append_pose(self, pose):
self.pose_list.append(pose)
def append_poses(self, poses):
if not len(self.pose_list):
self.pose_list.extend(poses)
# poses are sorted
# but maybe we need to reverse
#get the point distance from first and last pose
last = self.pose_list[-1]
nfirst = poses[0]
nlast = poses[-1]
last = FreeCAD.Base.Vector(last.X, last.Y, last.Z)
nlast = FreeCAD.Base.Vector(nlast.X, nlast.Y, nlast.Z)
nfirst = FreeCAD.Base.Vector(nfirst.X, nfirst.Y, nfirst.Z)
dnl = last.distanceToPoint(nlast)
dnf = last.distanceToPoint(nfirst)
if dnl < dnf:
poses.reverse()
self.pose_list.extend(poses)
def draw_wire(self):
path = Part.makePolygon([FreeCAD.Base.Vector(p.X, p.Y, p.Z) for p in self.pose_list ])
s = Part.show(path)
s.ViewObject.LineColor=(1.0,0.5,0.0)
s.ViewObject.LineWidth=(2.5)
def save_prog(self, filename):
srcfile = open(filename+'.src','w')
srcfile.write(header_src)
# subroutine definition
srcfile.write("DEF "+filename+"( )\n\n")
srcfile.write(";- Kuka src file, generated by KVT\n")
srcfile.write(";- "+ time.asctime()+"\n\n")
# defining world and base
srcfile.write(";------------- definitions ------------\n")
srcfile.write("EXT BAS (BAS_COMMAND :IN,REAL :IN ) ;set base to World\n")
srcfile.write("BAS (#INITMOV,0 ) ;Initialicing the defaults for Vel and so on \n\n")
srcfile.write("$BASE = BASE_DATA[6] ; SET BASE to WORKPLACE\n\n")
srcfile.write("\n;------------- main part ------------\n")
#V = w.Velocity / 1000.0 # from mm/s to m/s
V = 0.05
srcfile.write("$VEL.CP = %f ; m/s ; m/s \n"%V)
for pose in self.pose_list:
srcfile.write("LIN {} ; GENERATED\n".format(pose.to_string()))
# end of subroutine
srcfile.write("\n;------------- end ------------\n")
srcfile.write("END \n\n")
srcfile.close()
class Kuka_Pose:
def __init__(self):
self.X = 0.0
self.Y = 0.0
self.Z = 0.0
self.A = 0.0
self.B = 0.0
self.C = 0.0
self.S = 0
self.T = 0
self.E1 = 0.0
self.E2 = 0.0
def set_from_point_and_normal(self, point, normal):
self.X = point.x
self.Y = point.y
self.Z = point.z
r = FreeCAD.Base.Rotation(FreeCAD.Base.Vector(0,0,1), normal)
ABC_in_deg = r.toEulerAngles('ZYX')
self.A = math.radians(ABC_in_deg[0])
self.B = math.radians(ABC_in_deg[1])
self.C = math.radians(ABC_in_deg[2])
#print("Rotation:", self.A, self.B, self.C)
def from_point_and_normal(point, normal):
pose = Kuka_Pose()
pose.X = point.x
pose.Y = point.y
pose.Z = point.z
r = FreeCAD.Base.Rotation(FreeCAD.Base.Vector(0,0,1), normal)
ABC_in_deg = r.toEulerAngles('ZYX')
pose.A = math.radians(ABC_in_deg[0])
pose.B = math.radians(ABC_in_deg[1])
pose.C = math.radians(ABC_in_deg[2])
#print("Rotation:", self.A, self.B, self.C)
return pose
def to_string(self):
pose_string="X {:.3f}, Y {:.3f}, Z {:.3f}, A {:.4f}, B {:.4f}, C {:.4f}, E1 {:.4f}, E2 {:.4f}"
return "{" + pose_string.format(self.X, self.Y, self.Z, self.A, self.B, self.C, self.E1, self.E2) + "}"
def draw_pose(self):
#line=Part.makeLine(point, point+3*normal)
#lines.append(line)
# from euler to some line
# create upfacing vector then rotate around each axis?
up = FreeCAD.Base.Vector(0,0,1)
rotx = FreeCAD.Base.Rotation(FreeCAD.Base.Vector(1,0,0), math.degrees(self.C))
roty = FreeCAD.Base.Rotation(FreeCAD.Base.Vector(0,1,0), math.degrees(self.B))
rotz = FreeCAD.Base.Rotation(FreeCAD.Base.Vector(0,0,1), math.degrees(self.A))
rot = rotz.multiply(roty.multiply(rotx))
up_rotated = rot.multVec(up)
basepoint = FreeCAD.Base.Vector(self.X, self.Y, self.Z)
line = Part.makeLine(basepoint, basepoint+5*up_rotated)
#line.Placement.Rotation = rot
s = Part.show(line)
s.ViewObject.LineColor=(1.0,0.0,0.0)
def get_list_of_poses(face, edge, n):
poses = []
points = edge.discretize(n)
lines = []
for point in points:
uv = face.Surface.parameter(point)
normal = face.normalAt(uv[0], uv[1])
line=Part.makeLine(point, point+3*normal)
lines.append(line)
# create pose
pose = Kuka_Pose.from_point_and_normal(point, normal)
poses.append(pose)
return poses

149
lasercladding/pad.py

@ -0,0 +1,149 @@
import FreeCAD as App
import numpy as np
from . import kuka
class LaserPad:
def __init__(self, obj, face):
'''Add some custom properties to our box feature'''
obj.addProperty("App::PropertyEnumeration", "pathtype", "SlicingParameters", "Type of Path generation (slice, etc)")
obj.pathtype = ["sliced", "auto", "wire"]
obj.addProperty("App::PropertyEnumeration", "slicedirection", "SlicingParameters", "When Slicing, which direction to use")
obj.slicedirection = ["xaxis", "yaxis", "zaxis", "custom"]
obj.addProperty("App::PropertyLinkSub","ref_surface","SlicingParameters","reference_1")
obj.ref_surface = face
obj.Proxy = self
def onChanged(self, fp, prop):
'''Do something when a property has changed'''
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
def execute(self, fp):
'''Do something when doing a recomputation, this method is mandatory'''
App.Console.PrintMessage("Recompute LaserPad\n")
face = fp.ref_surface[0].getSubObject(fp.ref_surface[1])[0]
print(face)
if fp.pathtype == "sliced":
xmin = face.BoundBox.XMin
xmax = face.BoundBox.XMax
ymin = face.BoundBox.YMin
ymax = face.BoundBox.YMax
slice_direction = App.Vector(0,0,0)
if fp.slicedirection == "xaxis":
slice_direction = App.Vector(1,0,0)
pmin, pmax = xmin, xmax
if fp.slicedirection == "yaxis":
slice_direction = App.Vector(0,1,0)
pmin, pmax = ymin, ymax
if fp.slicedirection == "zaxis":
slice_direction = App.Vector(0,0,1)
pmin, pmax = zmin, zmax
slices = face.slices(slice_direction, [x for x in np.arange(pmin, pmax, 2)])
prog = kuka.Kuka_Prog()
for edge in slices.Edges:
poses = kuka.get_list_of_poses(face, edge, 10)
prog.append_poses(poses)
prog.draw_wire()
prog.save_prog("/home/jk/test_export_workbench")
### helper to create some object
def create_laserpad(face):
laserjob_entry = App.ActiveDocument.getObject('LaserJob')
if laserjob_entry is None:
App.Console.PrintMessage('Create a LaserJob first')
return
pad_obj = App.ActiveDocument.addObject("App::FeaturePython","LaserPad")
pad = LaserPad(pad_obj, face)
#a=laserjob_entry.addObbject(pad)
ViewProviderLaserPad(pad.ViewProvider)
return pad
class ViewProviderLaserPad:
def __init__(self, obj):
'''Set this object to the proxy object of the actual view provider'''
obj.addProperty("App::PropertyColor","Color","Box","Color of the box").Color=(1.0,0.0,0.0)
obj.Proxy = self
def attach(self, obj):
'''Setup the scene sub-graph of the view provider, this method is mandatory'''
self.onChanged(obj,"Color")
def updateData(self, fp, prop):
'''If a property of the handled feature has changed we have the chance to handle this here'''
# fp is the handled feature, prop is the name of the property that has changed
pass
def getDisplayModes(self,obj):
'''Return a list of display modes.'''
modes=[]
modes.append("Shaded")
modes.append("Wireframe")
return modes
def getDefaultDisplayMode(self):
'''Return the name of the default display mode. It must be defined in getDisplayModes.'''
return "Shaded"
def setDisplayMode(self,mode):
'''Map the display mode defined in attach with those defined in getDisplayModes.\
Since they have the same names nothing needs to be done. This method is optional'''
return mode
def onChanged(self, vp, prop):
'''Here we can do something when a single property got changed'''
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
#if prop == "Color":
# c = vp.getPropertyByName("Color")
# self.color.rgb.setValue(c[0],c[1],c[2])
def getIcon(self):
'''Return the icon in XPM format which will appear in the tree view. This method is\
optional and if not defined a default icon is shown.'''
return """
/* XPM */
static const char * ViewProviderBox_xpm[] = {
"16 16 6 1",
" c None",
". c #141010",
"+ c #615BD2",
"@ c #C39D55",
"# c #000000",
"$ c #57C355",
" ........",
" ......++..+..",
" .@@@@.++..++.",
" .@@@@.++..++.",
" .@@ .++++++.",
" ..@@ .++..++.",
"###@@@@ .++..++.",
"##$.@@$#.++++++.",
"#$#$.$$$........",
"#$$####### ",
"#$$#$$$$$# ",
"#$$#$$$$$# ",
"#$$#$$$$$# ",
" #$#$$$$$# ",
" ##$$$$$# ",
" ####### "};
"""
def __getstate__(self):
'''When saving the document this object gets stored using Python's json module.\
Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\
to return a tuple of all serializable objects or None.'''
return None
def __setstate__(self,state):
'''When restoring the serialized object from document we have the chance to set some internals here.\
Since no data were serialized nothing needs to be done here.'''
return None

16
lasercladding/path.py

@ -0,0 +1,16 @@
import FreeCAD as App
class LaserPath:
def __init__(self, obj):
'''Add some custom properties to our box feature'''
obj.addProperty("App::Property", "base_reference", "Reference", "Reference Point (teached)")
obj.base_reference = App.Vector(0,0,0)
obj.Proxy = self
def onChanged(self, fp, prop):
'''Do something when a property has changed'''
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
def execute(self, fp):
'''Do something when doing a recomputation, this method is mandatory'''
App.Console.PrintMessage("Recompute Python Box feature\n")
Loading…
Cancel
Save