diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..afe6212
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/freecad/LaserCladdingWorkbench/__pycache__/
diff --git a/InitGui.py b/InitGui.py
deleted file mode 100644
index 2128bbc..0000000
--- a/InitGui.py
+++ /dev/null
@@ -1,47 +0,0 @@
-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())
diff --git a/Resources/LaserCladding.qrc b/Resources/LaserCladding.qrc
deleted file mode 100644
index 5229187..0000000
--- a/Resources/LaserCladding.qrc
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- icons/Laser_Workbench.svg
-
-
-
diff --git a/freecad/LaserCladdingWorkbench/Resources/LaserCladding.qrc b/freecad/LaserCladdingWorkbench/Resources/LaserCladding.qrc
new file mode 100644
index 0000000..c33b049
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/Resources/LaserCladding.qrc
@@ -0,0 +1,11 @@
+
+
+ icons/Laser_Workbench.svg
+ icons/LaserCreatePad.svg
+ icons/LaserCreateProg.svg
+ icons/LaserRecomputePad.svg
+ icons/LaserSaveProg.svg
+ icons/LaserSelectBaseRef.svg
+
+
+
diff --git a/freecad/LaserCladdingWorkbench/Resources/icons/LaserCreatePad.svg b/freecad/LaserCladdingWorkbench/Resources/icons/LaserCreatePad.svg
new file mode 100644
index 0000000..332b176
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/Resources/icons/LaserCreatePad.svg
@@ -0,0 +1,325 @@
+
+
diff --git a/freecad/LaserCladdingWorkbench/Resources/icons/LaserCreateProg.svg b/freecad/LaserCladdingWorkbench/Resources/icons/LaserCreateProg.svg
new file mode 100644
index 0000000..a8b909c
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/Resources/icons/LaserCreateProg.svg
@@ -0,0 +1,362 @@
+
+
diff --git a/freecad/LaserCladdingWorkbench/Resources/icons/LaserRecomputePad.svg b/freecad/LaserCladdingWorkbench/Resources/icons/LaserRecomputePad.svg
new file mode 100644
index 0000000..3758678
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/Resources/icons/LaserRecomputePad.svg
@@ -0,0 +1,362 @@
+
+
diff --git a/freecad/LaserCladdingWorkbench/Resources/icons/LaserSaveProg.svg b/freecad/LaserCladdingWorkbench/Resources/icons/LaserSaveProg.svg
new file mode 100644
index 0000000..f5c7cd1
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/Resources/icons/LaserSaveProg.svg
@@ -0,0 +1,357 @@
+
+
diff --git a/freecad/LaserCladdingWorkbench/Resources/icons/LaserSelectBaseRef.svg b/freecad/LaserCladdingWorkbench/Resources/icons/LaserSelectBaseRef.svg
new file mode 100644
index 0000000..fb514f2
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/Resources/icons/LaserSelectBaseRef.svg
@@ -0,0 +1,373 @@
+
+
diff --git a/Resources/icons/Laser_Workbench.svg b/freecad/LaserCladdingWorkbench/Resources/icons/Laser_Workbench.svg
similarity index 100%
rename from Resources/icons/Laser_Workbench.svg
rename to freecad/LaserCladdingWorkbench/Resources/icons/Laser_Workbench.svg
diff --git a/Init.py b/freecad/LaserCladdingWorkbench/__init__.py
similarity index 100%
rename from Init.py
rename to freecad/LaserCladdingWorkbench/__init__.py
diff --git a/freecad/LaserCladdingWorkbench/commands.py b/freecad/LaserCladdingWorkbench/commands.py
new file mode 100644
index 0000000..98fca47
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/commands.py
@@ -0,0 +1,170 @@
+import FreeCAD as App
+import FreeCADGui as Gui
+import Part
+import os
+import re
+import copy
+
+from .program import LaserProgram, ViewProviderLaserProgram
+from .pad import LaserPad, ViewProviderLaserPad
+from .kuka import Kuka_Prog, Kuka_Pose, get_list_of_poses
+
+
+class LCCreateProgram():
+ def Activated(self):
+ # Here your write what your ScriptCmd does...
+ App.Console.PrintMessage('Create Lasser Cladding Program')
+ a=App.ActiveDocument.addObject("App::FeaturePython","LaserProgram")
+ LaserProgram(a)
+ ViewProviderLaserProgram(a.ViewObject)
+
+ def GetResources(self):
+ return {'Pixmap' : ":icons/LaserCreateProg.svg", 'MenuText': 'Create Laser Program', 'ToolTip': 'Add a Laser Program to your Document'}
+
+
+class LCSelectBaseReference():
+ 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('LaserProgram')
+ 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' : ":icons/LaserSelectBaseRef.svg",
+ 'MenuText': 'Select Base reference',
+ 'ToolTip': 'Add a Job to your Document'}
+
+
+class LCCreatePad():
+
+ def _create_laserpad(self, ref_to_face):
+ laserprogram = App.ActiveDocument.getObject('LaserProgram')
+ if laserprogram is None:
+ App.Console.PrintMessage('Create a LaserProgram first')
+ return
+ pad_obj = laserprogram.newObject("App::FeaturePython","LaserPad")
+ LaserPad(pad_obj, ref_to_face)
+ ViewProviderLaserPad(pad_obj.ViewObject)
+ return pad_obj
+
+
+
+ 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()
+ self._create_laserpad(ref_face)
+ App.ActiveDocument.recompute()
+
+
+ def GetResources(self):
+ return {'Pixmap' : ":icons/LaserCreatePad.svg", 'MenuText': 'Create Pad', 'ToolTip': 'Create a Pad on selected face'}
+
+
+
+class LCRecompute():
+
+ def Activated(self):
+ # Here your write what your ScriptCmd does...
+ App.Console.PrintMessage('Recomputer Pads')
+ if not Gui.Selection.hasSelection():
+ App.Console.PrintMessage('Select a Pad')
+ return
+
+ if Gui.Selection.hasSelection():
+ laser_path_obj = Gui.Selection.getSelection()[0]
+ if len(laser_path_obj.Group):
+ laser_path_obj.removeObjectsFromDocument()
+ laser_path_obj.Proxy._create_path(laser_path_obj)
+ App.ActiveDocument.recompute()
+
+
+ def GetResources(self):
+ return {'Pixmap' : ":icons/LaserRecomputePad.svg",
+ 'MenuText': 'Recompute',
+ 'ToolTip': 'Recompute selected Pads'}
+
+
+class LCSaveProg():
+
+ def Activated(self):
+ # Here your write what your ScriptCmd does...
+ App.Console.PrintMessage('Saving to KRL')
+ c = App.ActiveDocument.getObject("LaserProgram")
+ pads = c.Group
+ prog = Kuka_Prog()
+
+ for pad in pads:
+ # one pad with contours and hatchlines
+ for progpart in pad.Group:
+ ## jedes Contour oder Hatchline Feature
+ if re.match('Contour*', progpart.Name):
+ # do Conoutes
+ face = pad.ref_body.getSubObject(pad.ref_surface)
+ edges = Part.__sortEdges__(progpart.Shape.Edges)
+ vlist = []
+ for edge in edges:
+ vlist.extend([v.Point for v in edge.Vertexes])
+ poly = Part.makePolygon(vlist)
+ #Part.show(poly, "ContourPath")
+ poses = get_list_of_poses(face, poly.Edges)
+ prog.append_contour(poses, progpart.pathtype)
+ elif re.match('Hatch*', progpart.Name):
+ face = pad.ref_body.getSubObject(pad.ref_surface)
+ edges = progpart.Shape.Edges
+ for edge in edges:
+ p0 = edge.Vertexes[0].Point
+ p1 = edge.Vertexes[1].Point
+ line = []
+ for p in [p0, p1]:
+ uv = face.Surface.parameter(p)
+ normal = face.normalAt(uv[0], uv[1])
+ pose = Kuka_Pose.from_point_and_normal(p, normal)
+ line.append(pose)
+ prog.append_hatchline(line, progpart.pathtype)
+
+ prog.set_base(c.base_reference)
+ prog.save_prog(c.progpath)
+ App.ActiveDocument.recompute()
+
+
+ def GetResources(self):
+ return {'Pixmap' : ":icons/LaserSaveProg.svg",
+ 'MenuText': 'Save Program',
+ 'ToolTip': 'Save the Program as KRL'}
+
+
+
+Gui.addCommand('LCCreateProgram', LCCreateProgram())
+Gui.addCommand('LCSelectBaseReference', LCSelectBaseReference())
+Gui.addCommand('LCCreatePad', LCCreatePad())
+Gui.addCommand('LCRecompute', LCRecompute())
+Gui.addCommand('LCSaveProg', LCSaveProg())
diff --git a/freecad/LaserCladdingWorkbench/init_gui.py b/freecad/LaserCladdingWorkbench/init_gui.py
new file mode 100644
index 0000000..f16e9ea
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/init_gui.py
@@ -0,0 +1,63 @@
+import FreeCAD as App
+import FreeCADGui as Gui
+from freecad.LaserCladdingWorkbench import lc_resource
+
+from freecad.LaserCladdingWorkbench import commands
+
+import os
+
+class LaserCladding (Gui.Workbench):
+ MenuText = "Laser Cladding"
+ ToolTip = "Create Simple Paths on workpieces for Laser Cladding"
+ Icon = ":/icons/Laser_Workbench.svg"
+
+
+ def __init__(self):
+ pass
+
+ 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 freecad.LaserCladdingWorkbench
+ self.appendMenu("LaserCladding",
+ ["LCCreateProgram",
+ "LCSelectBaseReference",
+ "LCCreatePad",
+ "LCRecompute",
+ "LCSaveProg"
+ ]
+ ) # creates a new menu
+
+ self.appendToolbar("LaserCladding",
+ ["LCCreateProgram",
+ "LCSelectBaseReference",
+ "LCCreatePad",
+ "LCRecompute",
+ "LCSaveProg"
+ ]
+ ) # creates a new toolbar
+
+ def Activated(self):
+ """This function is executed whenever the workbench is activated"""
+ import freecad.LaserCladdingWorkbench
+ from importlib import reload
+ reload(freecad.LaserCladdingWorkbench)
+ 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("Laser Cladding",[]) # 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())
diff --git a/freecad/LaserCladdingWorkbench/kuka.py b/freecad/LaserCladdingWorkbench/kuka.py
new file mode 100644
index 0000000..d34ded1
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/kuka.py
@@ -0,0 +1,265 @@
+import FreeCAD
+import numpy as np
+import math
+import time
+import Part
+import re
+import copy
+
+
+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 = *
+"""
+
+ptp_fold = """;FOLD PTP xp1 Vel=100 % PDAT1 Tool[6]:laser6 Base[2]:Laser;%{PE}%R 8.2.24,%MKUKATPBASIS,%CMOVE,%VPTP,%P 1:PTP, 2:xp1, 3:, 5:100, 7:PDAT1
+$BWDSTART=FALSE
+PDAT_ACT=PPDAT1
+FDAT_ACT=Fxp1
+BAS(#PTP_PARAMS,100)
+PTP Xxp1
+;ENDFOLD"""
+
+
+
+class Kuka_Prog:
+ def __init__(self):
+ self.contour_path_list = []
+ self.hatchlines_list = []
+ self.base = (0,0,0)
+
+ def set_base(self, vec):
+ self.base = (vec.x, vec.y, vec.z)
+
+ def append_contour(self, poses, segmenttype = 'LIN'):
+ self.contour_path_list.append((poses, segmenttype))
+
+ def append_hatchline(self, line, segmenttype = 'LIN'):
+ if not len(self.hatchlines_list):
+ self.hatchlines_list.append((line, segmenttype))
+ # poses are sorted
+ # but maybe we need to reverse
+ #get the point distance from first and last pose
+ last, _ = self.hatchlines_list[-1]
+ nfirst = line[0]
+ nlast = line[-1]
+ last = FreeCAD.Base.Vector(last[1].X, last[1].Y, last[1].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:
+ line.reverse()
+ self.hatchlines_list.append((line, segmenttype))
+
+ 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, obj):
+ path = Part.makePolygon([FreeCAD.Base.Vector(p.X, p.Y, p.Z) for p in self.pose_list ])
+ #s = Part.show(path)
+ obj.addObject(s)
+ s.ViewObject.LineColor=(1.0,0.5,0.0)
+ s.ViewObject.LineWidth=(2.5)
+
+ def get_vectors(self):
+ return [FreeCAD.Base.Vector(p.X, p.Y, p.Z) for p in poses for poses in self.contour_path_list ]
+
+ def save_prog(self, filename):
+ if not filename.endswith('.src'):
+ filename = filename +'.src'
+ srcfile = open(filename, '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("E6POS startp\n")
+ # srcfile.write("DECL E6AXIS xp1={A1 -1.9, A2 -105.76, A3 79.97, A4 178.83, A5 -20.3, A6 -4.37, E1 -90, E2 0}\n")
+ 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("BAS (#TOOL,6) ;Initialicing the defaults for Vel and so on \n\n")
+ srcfile.write("BAS (#BASE,2) ;Initialicing the defaults for Vel and so on \n\n")
+ #srcfile.write(ptp_fold)
+ srcfile.write("PTP {A1 -33.31, A2 -104.71, A3 114.60, A4 282.66, A5 -39.21, A6 -104.87, E1 -90, E2 1.0}\n")
+ srcfile.write("\n;------------- main part ------------\n")
+ srcfile.write("startp=$POS_ACT\n")
+ #V = w.Velocity / 1000.0 # from mm/s to m/s
+ V_prozess = 0.0225
+ V_max = 0.15
+ CDIS = 2.3
+ CVEL = 95.0
+ LASERPOWER = 0.4
+ srcfile.write("$VEL.CP = %f ; m/s ; m/s \n"%V_max)
+ srcfile.write("$APO.CDIS = %f ; mm \n"%CDIS)
+ srcfile.write("$APO.CVEL = %f ; percent \n"%CVEL)
+ srcfile.write("$ANOUT[1] = %f ; \n"%LASERPOWER)
+ srcfile.write("$OUT[7] = TRUE ; \n")
+ srcfile.write("$OUT[9] = TRUE ; \n")
+ srcfile.write("LIN startp:{X -100.0, Y 0.0, Z 0.0, A 0.0000, B 0.0000, C 0.0000, E1 0.0000, E2 0.0000} C_VEL; GENERATED\n")
+ srcfile.write("WAIT SEC 10.0\n")
+ srcfile.write(";- Contourpaths\n")
+ for (poses, seg_type) in self.contour_path_list:
+ # start laser code
+ srcfile.write("$VEL.CP = %f ; m/s ; m/s \n"%V_prozess)
+ srcfile.write(";- Turn on Laser\n")
+ if seg_type == 'LIN':
+ srcfile.write("LIN startp:{} C_VEL; GENERATED\n".format(poses[0].translate_with(self.base).to_string()))
+ srcfile.write("TRIGGER WHEN DISTANCE=0 DELAY=0 DO $OUT[3]=True\n" ) ## Einschalten
+ for pose in poses[1:]:
+ srcfile.write("LIN startp:{} C_VEL; GENERATED\n".format(pose.translate_with(self.base).to_string()))
+
+ if seg_type == 'SPLINE':
+ srcfile.write("SPLINE\n")
+ for pose in poses:
+ srcfile.write(" SPL startp:{} ; GENERATED\n".format(pose.translate_with(self.base).to_string()))
+ srcfile.write("ENDSPLINE\n")
+
+ srcfile.write(";- Turn off Laser\n")
+ srcfile.write("$OUT[3] = FALSE\n")
+ # end of subroutine
+
+ srcfile.write(";- Hatchlines\n")
+ for (line, seg_type) in self.hatchlines_list:
+ # start laser code
+ srcfile.write(";- Hatchline\n")
+ if seg_type == 'LIN':
+ srcfile.write("$VEL.CP = %f ; m/s ; m/s \n"%V_max)
+ srcfile.write("LIN startp:{} C_VEL; GENERATED\n".format(line[0].translate_with(self.base).to_string()))
+ srcfile.write("TRIGGER WHEN DISTANCE=0 DELAY=0 DO $OUT[3]=True\n" ) ## Einschalten
+ srcfile.write("$VEL.CP = %f ; m/s ; m/s \n"%V_prozess)
+ srcfile.write("LIN startp:{} C_VEL; GENERATED\n".format(line[1].translate_with(self.base).to_string()))
+ srcfile.write("TRIGGER WHEN DISTANCE=0 DELAY=0 DO $OUT[3]=FALSE\n") ## Ausschalten
+ # end of subroutine
+ srcfile.write("$OUT[3] = FALSE\n")
+ srcfile.write("$OUT[7] = FALSE ; \n")
+ srcfile.write("$OUT[9] = FALSE ; \n")
+ 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, rot=False):
+ if rot:
+ 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) + "}"
+ else:
+ 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, 0,0,0,0,0) + "}"
+
+
+ def translate_with(self, vector):
+ pose = copy.copy(self)
+ pose.X = pose.X - vector[0]
+ pose.Y = pose.Y - vector[1]
+ pose.Z = pose.Z - vector[2]
+ return pose
+
+
+ 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, edges):
+ poses = []
+ for edge in edges:
+ p0 = edge.Vertexes[0].Point
+ p1 = edge.Vertexes[1].Point
+
+ for p in [p0, p1]:
+ uv = face.Surface.parameter(p)
+ normal = face.normalAt(uv[0], uv[1])
+ pose = Kuka_Pose.from_point_and_normal(p, normal)
+ poses.append(pose)
+ return poses
diff --git a/freecad/LaserCladdingWorkbench/lc_resource.py b/freecad/LaserCladdingWorkbench/lc_resource.py
new file mode 100644
index 0000000..6ebe040
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/lc_resource.py
@@ -0,0 +1,1029 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 5.15.2
+# WARNING! All changes made in this file will be lost!
+
+from PySide2 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x0a\x13\
+\x00\
+\x00/+x\x9c\xddZ[\x8f\x9bH\x16~\xcf\xaf@\
+\xce\xcbDc\xa0\xaePE\xdb\x8e\xb2\xdbJ\xb4R\xa2\
+\x19\xe52\xfb\xb0\x1a\xadh(\xdbLc\xb0\x00\xb7\xdd\
+\xf9\xf5{\xaa\x0c\x18l\xd2\xb7I\x9c\xd6v\x94n|\
+\xeaT\x9d:_\x9d\xab\xa9\xc9\xeb\xdd*\xb5nTQ\
+&y6\x1da\x07\x8d,\x95Ey\x9cd\x8b\xe9\xe8\
+\xcb\xe7\xb7\xb6\x18Ye\x15fq\x98\xe6\x99\x9a\x8e\xb2\
+|\xf4z\xf6bR\xde,^X\x96\x05\x93\xb32\x88\
+\xa3\xe9hYU\xeb\xc0u\xd7\x9b\x22u\xf2b\xe1\xc6\
+\x91\xabR\xb5RYU\xba\xd8\xc1\xee\xe8\xc0\x1e\x1d\xd8\
+\xa3B\x85Ur\xa3\xa2|\xb5\xca\xb3\xd2\xcc\xcc\xca\x97\
+\x1d\xe6\x22\x9e\xb7\xdc\xdb\xed\xd6\xd9R\xc3\x84\xa5\x94.\
+\x22.!6p\xd8\xe5mV\x85;\xbb?\x15\xf68\
+4\x95 \x84\x5c\x18;p>\x8c+\xd8\xa5Iv\xfd\
+\xcd\xcd\x98\xd1\xaet\xc0p\x0d\xff\xdb\x09\x0d\xc1)\xf3\
+M\x11\xa99\xccTN\xa6*\xf7\xf2\xf3e;h#\
+'\xae\xe2\xce2\xb0h\x19\x85k\xd5\x93\xdb\x10\xf7x\
+\x85+U\xae\xc3H\x95nC7\xf3;\x87\x8a\x0d!\
+\x89\xa7#\xd0\x88\xf8\x9eg>/U\xb2XV\xd3\x91\
+\xc7\xd6;C\xd8&q\xb5\xec|n6\x15\xc4y\xa4\
+\xa5LG\xef\xc3R\x15\x1f\xf5q\xad7\x95\xfa=\x8c\
+\x9d\x06\xa2Ft\xd0\xb5%\x87X\xbf(\xe1E\xc2G\
+\xc2\x97c\x8b \x82m\x84m\xcc_\x8df0k\xd2\
+J\xd0\xcb\xc77\x89\xda\xea\xb5,k\x1d.@H\x9a\
+\x17\xd3\xd1\xcb\xb9\xf9\x19\xed\x07\xae\xf2\x22VE3\xe4\
+\x99\x9f\xdeP\x0eH$\xd5-\x88\xaf\xc9\xf9\xd5_*\
+\xaa\xaa\xc6B\xca\
+\xben\xa0\x05\xc5\x94\xd0\x9en\x03\xbcX\xe3\xc0\x1d\xc9\
+<.Ot;\xc5\x1er\xa9O0\x83\x10+\xc7\xf0\
+\xe8\x0b\x0f\xdc@\x8e5\x99R\xeeI\x9f\xe8g \xc2\
+\x09\x11<\xe6\x8c;\x94#M\xa5B:\xcc\xf3\xb8\xf7\
+\xea{@\xc8\xbe\x17\x84\xc3\xceh\xa3s\xba\xa3}f\
+\x87\xb4\xc9\xd3\x5c\xf2\x1e\xa8mt\xd7\x99\x916\xe5>\
+\xf4\xe0\x1f\x11\x01\xcd\x0b\
+\xff\xdf\xcd\xeb\xc1\xa1\x1d\x8c\xe4^_\xfc\xa6y\xc93\
+\x9b\xd79+\xd3\xbdy\xb1\x1f\x14\xe4mi\xf3;N\
+\x11j\x03f{?\x22\xd4C\xb1\x86\x09\x81\x12\xa2\x8e\
+\xf9`\xd8\x9c\x10\xe6\x8dm\xc6\xc1D\x05E\xfc(\xe6\
+S\xe6\x10\xa4+\xd0\x9e?`\x87C\xa3G\xfc\xbe\xef\
+\xf8\x12$I${1\x1f#\xe2\x08\xe1\x11\xf1t;\
+\x03\xb0\xceli6:\xbf\xadud\x9e\xd9\xda$:\
+\x87\xad\x09\x1f\x0a\x5c\x88\xa2`k\x98;Lz\x14\xff\
+4[{\x10fw\x16c\x1a\xb5\xf3\xf8\xe8s\xc2\xed\
+.\x1f=w\xc1\xaf\xbd\xb4S\x1c\x9d\xd1O\xc1\x9d~\
+\x9c\xa7\xdemu\x98\xb0\xeffs\xc8\x11H`\x9f\x09\
+mt\xb02\xda\xdb\x1c\xf4\xa1\x1e\x86F\xd4\x83N\x80\
+c\xdf\xa1L\xfa\x0c\x9aO\xc4\xa0+ \x1ey\xc6N\
+Klr\xa7\xcbb\xd1a\xf8\xfb.\xcb\xa9\xe0\x9e\xa0\
+\xba]\x17P\x0cr,=\x83\x9f\xf48\xe5B\x80\xc7\
+\x0a\xd0\x98\x0a\x04M\x95F\x12\xc0c\xf2\x18>pi\
+\xe4{\xbc\x07\x9f\xfe\xb2\xc5\x97\xc4\xa3G\xf0\x11\x1d\x09\
+\xb8\x7f\x04\x1fr\x88O\xf8\xd3\xbb\x84\xf3\xf7\x09?\xa1\
+\x94\xa3O\xed\xd85\xc6L8\x18\xfb\x1e\xea\x9f\x87\x8f\
+\xc0\x9c\x19\x9c^\xff\xec0\x94\xf3\x1eGG\xbd\x82G\
+\x1dJ|\xcc\x1feX\xc8Tg\xbe/\xf9\x91]I\
+\x0c\xa6\x00\xf6\x0cn)=\xbd\x09\xcc\xfe~\xf3\x00\x9a\
+P\xbb\xf3\xfd\xde\xbd\xee\x86\x9fls\xf8\xcc\x16\xf7\x13\
+\xd2\x84\xff\xe3r\x84w\xe7!B\xef ~@\x90\xbb\
+\xdb\x16!|\xeb\xfa\x04b\x1c\x03\xee\xe7X\x9fxg\
+\xafN\xe8O\xa8M\xee\xb6\xba\x89\xab_B\x99\xa7\xc5\
+\x8b\x03`\xfa\x05W\xe3\x91\x93uX-\xbbp\x16*\
+\xaa(AP\xf4\xd8\x87\xf0\x07\xf4\x0f\x16\xa5c\xca-\
+>fR?riq9f\xdc\xfaj\xad,4\xe6\
+\x16\xf6\xc6\x9ee\xc3o\x01\xbf}(d\xad\xaf\xed\xfc\
+Z\xff\x06n\xf3s1O\xd24\xa8\xdf\xab\x9a\x0f\x1d\
+\x1c\xcc\xc7b\x93\xaa@\xdd\xa8,\x8fc\x00\xaa\xc8\xaf\
+\x15L\xbe\xc2\x9c\xe0\xfa\xe3\xfe\x05f@\x9a\x8f\x1a\x82\
+(\x5c\x07E\xbe\xc9\xe2.\xf1\xaf<\xc9\xfa\xd4UR\
+\xa9\x22M\xe0O\xc0\x1a\xdaA~M\x88\xc3r\x19\x16\
+Ex\x1bdy\xa6\xba\xd4\xfd\xe9\x04\xe8b\x15\x16\xd7\
+\xaa\xd8\x8f\xdf$er\x95\xa4z\x09\xf3\x98\xaa\x8b8\
+)\xd7\x00x\x90dz\x1b\x17\xf9\x8d*\xe6i\xbem\
+\xc7U\x16\xc2\x1f\xfb*\x8c\xae\x17f\x7fA\x18E\x9b\
+\xd5&\x0d+\xd5\xbcWt\x17\xe6\xcfJUa\x1cV\
+\xe1\xe1$\x1b\x0ao^\x18N\x8ax\x1e|\xbc|\xdb\
+\x9aa\x14\x05\xff\xce\x8b\xeb\x83\x05i\x86\xf0*\xdf\x80\
+a\xb5\x0e\xa2_]F\x81\x8e\x03a5KV\xe1B\
+\xe9+\x07\xbf\xeeV)\x18P;\xd0c\xaen\xd7\xea\
+\xb0\xe8~\xd9B\xed\xaf\x14\x0c\xde\xc2\x88#@\x1a&\
+\xb9\x9f*8\xda\x7fi!\x1dw\xa9\x17M\xaaT\xcd\
+\x8c\xcc\xfd\xe3a\x14\xf4H\x93He\xe5\x1dRO\xd6\
+\x03h\xd4\xecC\x9eY\xbfE\x95\x85\x91\x85i\xc0X\
+\xc0\x89E\x10\xc6\xd6\xaf\xda\x08\x8d0\xc3\xd7\x9bi.\
+\x84\xe4\xc5\xac#Lo\xe1\xcd\xa2\x0d\xe7\xa7\xfb\xfe\xcf\
+v\xa5\x1d\xeb\xcf\xa1\xfd\xebC<\x9do8OD\xe9\
+\x15\x0b\xfd\xd2\xbd|\x94\xf8\xb7\x85R\xff|si\xbd\
+\x7f\xf7\xfb{\xf2\xeb#7q,O/\xbb\xde\x5c\xa5\
+I\xb9T\x8fC\xa1\xde\xc6#\xe5\x0f\xc8\xd2K&1\
+\xb0&\xf3\x04\xe8\xf5\xbanYD\xee\x87\xd0[\xa0-\
+j\x9aE\xa8\x83\xc7\x0c\x9dBA\x9e\xf5\xde\x89t0\
+\xc7\x12\x8a6\xd0B\xfaH\xa0\x81\xd3<\xbcv\xfb\xd9\
+J\xac\x06\x95\xb0=h7\x18\x96Psb\xb0H\x82\
+\xc8\x90\x0e'7\xb9\xaa\xa6\xf9\xb1\xa1\xae\x00\xb9\xfaJ\
+\x19q\xa0\xb3&\x94\xde\xcf\x0c\xe6i#\x87b\x90L\
+\xc5\xf3\x86\x87;\x82S)\xbd1\x81\xe6N2O\x0e\
+\xc1sr'o\x08\x1e\x9b8\xd8\x87\x96\x90>\x04\x1e\
+\xfd\xaa\xc9\xe7\xb8m\x8c\x9f\x05:\x14{B2\x83\x0e\
+'xN\x8cG\xbf\x1d\xc7\xbe0\xd7\x95$\
+\xf5\x05f\x83\xbe\xd5F\xb9J\xed\xea\xe6\x1c\xba\x87\xc0\
+\xdc\x07\x06>\xa8\xd1Uq\xd3|\xff\xd0\xa8\x07\xe5\x89\
+m\x9eAI\xe80\xd2\x0bC\xd9\x9a\x0b\xa7=R\x99\
+|U\x01C\xa0\xa8\xd6\xa6\xbe\x92\x1a`\x87\xf0\xfd\xf8\
+<\x5c%\xe9mP\x02\xc86\x08J\xe6uk\xd9\xe9\
+3\x87\xd3Y\xbd!\x9d\xa5\xb8\xe3s\xdeF\x0e}\xf3\
+\x14;\xd0\xd8\x1f\xae\xafi\x85\xb5v\x98\x83\x03\xcd&\
+\x15\xe8\x96\xb5\xedms\x7f\xb8\xc8\xb5fz\x97\xbd\xaf\
+l\x0c3L\xec|\xf7q\x22\xf2H\xe8\xec\xe3\xc45\
+\xd3\xa0\x1f\xd2bg/&\xba)\x9b\xbd\xf8\x1fI\xaa\
+\xf3\xde\
+\x00\x00\x0a\x8d\
+\x00\
+\x000\x84x\x9c\xedZ\xebo\xdb8\x12\xff\xde\xbfB\
+p\xbf4XK\xe2[\xa4\x12g\xd1\xdd\xa2\xc5\x02-\
+\xf6\xd0\xc7\xde\x0b\x8b\x83\x22\xd1\x8e6\xb2dHr\xec\
+\xf4\xaf\xbf\xa1\xde\xb2\x9d\xb4I\x13\xb78\x5c\x8a\xc6\xf2\
+p\xc8\xe1\xcc\xfcf8\x13\xf1\xec\xe7\xed2\xb1\xaeu\
+^\xc4Y:\x9b`\x07M,\x9d\x86Y\x14\xa7\x8b\xd9\
+\xe4\xd3\xc7\xd7\xb6\x9cXE\x19\xa4Q\x90d\xa9\x9eM\
+\xd2l\xf2\xf3\xf9\xb3\xb3\xe2z\xf1\xcc\xb2,\x98\x9c\x16\
+~\x14\xce&\x97e\xb9\xf2]w\xb5\xce\x13'\xcb\x17\
+n\x14\xba:\xd1K\x9d\x96\x85\x8b\x1d\xecNz\xf6\xb0\
+g\x0fs\x1d\x94\xf1\xb5\x0e\xb3\xe52K\x8bjfZ\
+<\x1f0\xe7\xd1\xbc\xe3\xdel6\xce\x86VLX)\
+\xe5\x22\xe2\x12b\x03\x87]\xdc\xa4e\xb0\xb5\xc7Sa\
+\x8f\x87\xa6\x12\x84\x90\x0bc=\xe7\xd7q\xf9\xdb$N\
+\xafn\xddL5:\x94\x0e6\x5c\xc1\xffnBKp\
+\x8al\x9d\x87z\x0e3\xb5\x93\xea\xd2}\xf5\xf1U7\
+h#'*\xa3\xc12\xb0h\x11\x06+=\x92\xdb\x12\
+k{\x05K]\xac\x82P\x17nK\xaf\xe6\x0f\x9c\x8a\
++B\x1c\xcd&\xa0\x11\xf1\x84\xa8\xbe_\xeaxqY\
+\xce&\x82\xad\xb6\x15a\x13G\xe5\xe5\xe0{\xbb)?\
+\xcaB#e6y\x1b\x14:\xff\x00\x8e\x0d\xcb_\xe0\
+\xf1\xbd\x9e;\xad\x8dZ\xd9\xfe\x10L\x0e\xb1^h)\
+B\xe9!\xe9\xa9\xa9E\x10\xc16\xc26\xe6'\x93s\
+\x98u\xd6\x890\xebG\xd7\xb1\xde\x98\xb5,k\x15,\
+\x00\x14I\x96\xcf&\xcf\xe7\xd5\xcf\xa4\x1e\xb8\xc8\xf2H\
+\xe7\xed\x90\xa8~FC\x19\x98\x22.o@|C\xce\
+.\xfe\x82\xed\x96Y\xa2\xf3 \x0dA\x09\x8c\x9a\x91E\
+\x0e\x0a\x1f\xa2\xaf\xe3H\x1f\x1a\xe8t4\xdb\xeb\x04\x1d\
+\x1c-.\x83(\xdb\xcc&dwp\x13\xa70`7\
+\xb6&\x88{\xb7p\xb4\xee\xc1\x0cw,\xe0\xc1\xceP\
+\xca\xe3\x0d\xb9\xb8\xcc6F\x97\xd9d\x1e$\x85\xde]\
+\xefs\x96-\xc1\xab\x0e\xe2L ,w\x87\xc3\xedl\
+\xc2\x00#\x5c2N\xf7\x06\x8d!=\x07\x09\xecu\xe2\
+v7\xba=`\x82f\xe8\x90u\x9a\xa1e\xb0\x8d\x97\
+\xf1g\x1d\xf5\x9e\xea\xa5\xae\xf3\x1c2\x87\x9d\x047:\
+\x1f\x83v\xc0\x05\xa8\x5c\x9b\x04c\xe7Y\x09Y\xc4@\
+\x0e\xb2\x97[\xe1*\xd2\xf3\xa2\xb7\x99\xf9\x06+\xc8\x0a\
+s0\xba\x0c\xf2+\x9d\xd7\xe3`\xbe\xf2&\x01/g\
+\x00\xdcy\x92m\xfc\xeb\xb8\x88/\x12}:i\x19\xcc\
+\x12/\xf3<\xdb\x90\x0f:\x8d:r\xae\xe7\xff\x00\x91\
+\x0e\x1aR\xfe9\xa6dy\x0c;\x9cM\x82u\x99\xf5\
+\xeb\xb5\x1a\x14e\x16^\xdd\xb2z\xc7\x14\x17\x15\xdbl\
+R\xe6k\xddh\x00:\xac\x82\xf2\xb2\xe5\xb5\xac\x12\xa0\
+Z@J\x01G\xc3\xacD\xbf@\x0e=\xb1*\xc3\xe8\
+\x17X\xa2\x93\x9a#1_m\xe2\xd0):\x99\xf4\xb3\
+a\x07\xef,\xe9xXr\xe9\xc9)s\x10\xa5\x1e\xe5\
+\xc4zk\x013A\x1e\x91\x8aOA/\x0c\x00\xa2\x84\
+\x0b\x18h\xd8%\x9b\xda\xc0\x8f\xb0\x87\xd4A\x15z\xc1F\x06\xa0\x00L\x0eO\
+6\xaf\x1emL\x1c> \xd7\xd4\x96u\x84\xb6\xde\xcb\
+\x90h\x1e\xd9\xcb\xe4\xed\x93\xa6\x8d\xb7\x8f\xe8e8\x07\
+n\xf32\xfe\x7f\xcex@\xce\xf0\xc8mh2\x82\x83\
+\xfcM\x1eD\xc6\xd7C\xd0\x8cG\xa8'\xbd\xde\x7f\xe0\
+\xd4\xd5\x9e\xbe\x86hW5\x11lM`\x15\x9a\x8d\x03\
+\xa9\xdf\xf6`o\xd9|^\xe8\xb2?\x98[\xa9f\x06\
+\xc8R\xdd~\xbfF\x1a\xf5T8\xbf\xf8\x0ai\xbb\xd6\
+\xa9\xa5)<\xb0\xceX\xed\xfbZI\x0av4+\xc9\
+A\x8exz+I(\x5c\x1ed\xa5E\xf3\xfdc\x1f\
+\xe5\xcb\xa0\xcc\xe3-\x849\x18\x80)\x0a1\x0d\xff0\
+\xe4K\x86\x1c%\x14\x83G\x8f:\xd4\x13\x8c\xf4\xc1~\
+CL\x0d\x8c\x1c\x8c\xa0\x8c\xef\xa8[\xa0\x0a\xa4\x1c\xce\
+\x14V=/\x06^\x22\x1cOA\x8c\xf5\xbc@\xe5\x12\
+;DP\xda\xeb\xd8\xee\xefS\x1a\x97\xd0\x84\xadMo\
+a\x1a\x99\xdf\xd3O]\x15{\xd0\xd7X\xf6\x8bT}\
+\x97\x7f\x099\x14\xfa\x81\x03\x98\xe8,w\xd8FF7\
+E \xaf`\xa9\xd4X7\xd0\x82B\xe6\xa2#\xdd\x0e\
+\xf0bc\x07\xee(&\xb8\xda\xd3m\xdf\xf6\xd0\xe2y\
+\x043(\xfc\x15\xa4G\xcf\x93\x02\xc2@M\x0d\x99R\
+.\x94G\xcc3\x10\xc1C\x04O9\xe3\x0e\xe5\xc8P\
+\xa9T\x0e\x13\x82\x8b\x93\xc70!{,\x13\x1e\x0eF\
+\x1b\x1d3\x1c\xed#\x07\xa4M\x1e\x16\x92_0\xb5\x8d\
+\xee\xf2\x19\xe9\x1a\xc1\xafu\xfc=2\x80`\xd4!D\
+2:\xb5%q\x18\xe3\xd0q\x9d|\x01\xe4\xb7\x04\xc4\
+\xe1\xe0\x19\x05\xda\x0f\x17\x95P\xf8(\xcc\x91\xea\xec\xe1\
+y\xc4\x019\x82\x83=\x98\xc3\xa1\x82Q\x8f\x14w\xf6\
+\xd0\xcd_\x04\xc4\x10i\xf7\x8b?\x98y\xd4\x08\xb4\xe5\
+\xb1c\xd0\xa6\x0f\x8b\xc2\xffux}uj\x07\x90|\
+1\x16o\x85\x97:2\xbc\x8eY\x99\xd6\xf0bO\x94\
+\xe4me\xf3;\xbc\x08\xb5\x01\xb3\xc5S\xa4z(\xd6\
+0!PB49\x1f\x80\xcd\x09a\x02\xfa1\x0e\x10\
+\x95\x14\xf1\x9d\x9cO\x19\xf4r\xa6\x02\x1d\xc5\x03v\xb8\
+\xf28\xf1\xc6\xb1\xe3)\x90\xa4\x90\x1a\xe5|\x8c\x88#\
+\xa5 \xf2\xe18\x03c\x1d\x19i6:>\xd6\x062\
+\x8f\x8c6\x85\x8e\x815\xe9A\x81\x0bY\x14\xb0\x86\xb9\
+\xc3\x94\xa0\xf8\xbba\xed\xablvg1f\xacv\x9c\
+\x18\xfd\x91\xecvW\x8c\x1e\xbb\xe07Q:(\x8e\x8e\
+\x18\xa7\x10NO\x17\xa9w\xa3\x0e\x13\xf6h\x98C\x8e\
+D\x12{L\x1a\xd0\xc1\xca\xa8\xc6\x1c\xf4\xa1\x02C#\
+*\xa0\x13\xe0\xd8s(S\x1e\x83\xe6\x131\xe8\x0a\x88\
+ ?p\xd0\x12\x9b\xdc\x19\xb2X\x0e\x18\xbe=d9\
+\x95\x5cHj\xdau\x09\xc5 \xc7JT\xf6S\x82S\
+.%D\xac\x04\x8d\xa9D\xd0T\x19K\x82\xf1\x98\xda\
+5\x1f\x844\xf2\x04\x1f\x99\xcf\xfc\xb1\xc5SD\xd0\x1d\
+\xf3\x11\x93\x09\xb8\xb7c>\xe4\x10\x8f\xf0\x87w\x09\xc7\
+\xef\x13\xbeC)G\x1f\xda\xb1\x1b\x1b3\xe9`\xec\x09\
+4\xf6\x87\x87\x00\xce\x0c\xbc7\xf6\x1d\x86r^p\xb4\
+\xd3+\x08\xeaP\xe2a~/`\xa1\xaa:\xf3<\xc5\
+wp\xa50@\x01\xf0\x0ca\xa9\x84\xd9\x04f\xdf\xde\
+<\x80&\xd4\x1e\xfc}\xef\x8b\xe1\x86\x1f\x8c9|d\
+\xc4}\x87c\xc2{\xba3B\xdc\xe9D\xe8\x1d\xe4\x13\
+$\xb9\xbb\xb1\x08\xe9\xdb\xd4'\x90\xe3\x18p\xff\x88\xf5\
+\x898zuB\xbfCmr7\xea\xce\x5cs\x13\xa2\
+zZ<\xeb\x0dfnY\xb4\xab\x0e\xde\x08\xf6\xaf\xfe\
+\xd0\xd4&\xe8\xa4\xbd=1|\x85h\xe6\xe7:,)\
+AP\x16\xd9}\x82\xac^\x0fR:\xa5\xdc\xe2S\xa6\
+\xcc#W\x16WS\xc6\xad\x7fYK\x0bM\xb9\x85\xc5\
+TX6\xfc\x96\xf0\xdb\x83R\xd7\xfa\xdc\xcdo,\xd4\
+:\xa4\xfa9\x8d\xe2b\x05\xbb\xf5\xe3\xd4\xe8v\xba\xf7\
+\xea\xb5\xfa\x8c\x13c\xba\x96T\xbf\xaf\xab\xaf\x10\xed\xbc\
+\xaf;\xbd\xe3\x0d\xf5\x05\xe6\x04\x8f\xdf\x1a\x92\xe1\x1b\xc3\
+0X\x8d^\x18\x1e~\x8dh/\xe3R\xe7I\x0c\x1f\
+>kiQP\x5c\x06y\x0ej\xa4\x19(1\xa0\xd6\
+\x1e\xf6\x0f\xbcu\xac_\x1c\xd6\x13t\x1a\x80b\xf6E\
+\x10^-*a~\x10\x86\xeb\xe5\xdax\xaa\xbd\xfe\xe2\
+.\xaa\x8f\xa5.\x83((\x83\xde\xd7-\x85\xb7\xaf\x14\
+\xcf\xf2h\xee\xbf\x7f\xf5\xba\x03j\x18\xfa\x7f\xcf\xf2\xab\
+\x1ec\x86!\xb8\xc8\xd6\x00\xbd.\x84\xcc\x0d\x9b\xd07\
+0\x09\xca\xf3x\x19,\xb4\xb9+\xf7\xd3v\x99\x00\xc4\
+\xba\x81\x11sy\xb3\xd2\xfd\xa2\xf5\xb2\xb9\xae\xef\xc2\x1d\
+\xbc>\x18\x85`6\x98\xe4~(\xc1O\xbf\x19!\x83\
+\x80j\x16\x8d\xcbD\x9fW2\xeb\xc7~\x14\xf4H\xe2\
+P\xa7\xc5\x1dR\xf7\xd6\x03\xd3\xe8\xf3wYj\xfd\x1e\
+\x96\x16F\x16\xa6>c>'\x16A\x18[?\x19\x10\
+V\xc2*\xbe\xd1\xcc\xea&c\x96\x9f\x0f\x84\x99-\xbc\
+\x5ct\x09\x7f\x7f\xdf\xff\xde,M\xe8\xfdyh\xff\xc6\
+\x89\xfb\xf3+\xce=Qf\xc5\xdc\x5c\x16+\xee%\xfe\
+u\xae\xf5\xaf/_Yo\xdf\xfc\xed-\xf9\xe9\x9e\x9b\
+\xd8\x95g\x96]\xad/\x92\xb8\xb8\xd4\xf7\xb3B\xb3\x8d\
+{\xca? \xcb,\x19G\xc0\x1a\xcfc\xa07\xeb\xba\
+E\x1e\xba\xef\xb2\xc8}\x95\x07\xf3\xd2}\xdf8\xbfp\
+\xe30K\x8b\x9a\xfa\x1f\xf2\xea\xc3e\xb0\xd2\x7f\xc4z\
+c.4V\x12\x06K\x8d-\xad\x93\xea\xb6\xd9\xf9\xe0\
+B\xe6\x1cd\x85A\xb4\xd1\x17\x15z7\xf1U\xec\xc6\
+i\xa4\xb7\xce\xear\xf5s\xa5\xd5\xece^n \xb0\
+j\xeb\xb5k\x8c1\x94\xa5p\xe8^\xac\xef\x8d\xa3`\
+\x91\xdf\x14Y\xfa\xa7\xf52\xd1\xdb\x00\xe4\xe6\xd6\x9b\x8a\
+r_d\x1d\xda\x80\x91S\xac\xabK\x94\xa3EL(\
+\xfd\x12,v6e\xa8I|~\x91m\xcf\xdc\xe6\xf9\
+ \x03\xe4\xf1T\xdf\xcdb\xce\x95 ]$\x07\xd9j\
+\xdaH~\xa5\xc2\xdeN\xab\xb0\xd6E\x98\xc7\xab\xca\xe2\
+/-\xd8\x9b\x05\xc7\x068 ]X\x90\xd9\xae\xb5\x15\
+X\xab<3\xf3\x80\x03\x0eZ\x0b\x8a%\x0b\x0e\x16\xb0\
+\xe3\x1c\x8a\xa5:\xe4\x07k\xb4\xb9\xd2m\x92e{\xe2\
+\x0er\xe9\x99\xdb\xa6\xda\xea[\x7ff\x0e\xae\xa8\x0c\x0f\
+\x80\xfaN\x09\x9cQ;\xb7\xa2\xb6\xbb\xa7\x0ex\xa7\xdc\
+;t\xaasf\xef\xd8h\x0e\xd3\xfa(\xc6\x8e\x80\x16\
+\x1e:y\x0c\xc5\x9b\xa7\x10\xe6\xd6\x1f\x16C\x0e\x85\xc6\
+\x9f1sA\x8a:\xa6\xedg|J\xe9|\x9e\xeb\
+\xa2]om\xd5\xf4\x00[\xaa\x9a\xfd\xc3\xacQO\x05\
+\xf3\xd9\x03\xac\xe1Ak\x0a7\xd6.\xdd\xfe\xb2\x1f\x8b\
+\x92\x14\xecl(\xc9f\x8f\xcf\x81\x92\x04\xce<\x09\xa5\
+E\xf5\xfe\x19\xa2A\x0e!\x1b|i\xe5\x17Y\xb4\xfb\
+\x05\x02\x9c\xc0LQh\xd3\xa7y\
+\xe1\xff;\xbd\x1e\x1c\xda\x81$\xf7\xfa\xe27\xe9\xa5\xce\
+L\xafsV\xa6{z\xb1\x1f\x14\xe4me\xf3\x13\xbb\
+\x08\xb5\x01\xb3\xc5\x8f\x08\xf5P\xacaB\xa0\x84\xa8b\
+>\x10\x9b\x13\xc2\xc4\xd8f\x1c(*)\xe2\x071\x9f\
+2\x87 S\x81\xf6\xfc\x01;\x1c\x0eq\xc4\xeb\xfb\x8e\
+\xa7\xc0\x92B\xaa\x17\xf31\x22\x8e\x94\x82\xc8\xa7\xf3\x0c\
+\xc0:3\xd3 @\x9f\x9dk\x1d\x9bgf\x9bB\xe7\
+\xe0\x9a\xf4\xa0\xc0\x85(\x0a\x5c\xc3\xdcaJP\xfc\xd3\
+\xb8\xf6 \xccN\x16c\x06\xb5\xf3\xf8\xe8s\xc2\xed\x94\
+\x8f\x9e\xbb\xe07^\xda)\x8e\xce\xe8\xa7\xe0N?\xce\
+SO\xb3\x0e\x13\xf6\xdd8\x87\x1c\x89$\xf6\x984\xa4\
+\x83\x91\xd1\x9esp\x0e\x15\x18\x0e\xa2\x02N\x02\x1c{\
+\x0ee\xcacp\xf8D\x0cN\x05D\x90g\xec\xb4\xc4\
+&']\x16\xcb\x8e\xc2\xdfwYN%\x17\x92\x9a\xe3\
+\xba\x84b\x90c%J\xfc\x94\xe0\x94K\x09\x1e+a\
+\xc5T\x228T\x19$\x01<\xa6\x0e\xe1\x03\x97F\x9e\
+\xe0=\xf8\xcc\x1f[]\x06\x13\x13\x07\xfc\xe2*Z\xf9\x0bm.\x13\
+\xbc\xde\xadb P\xd3\xd0S.\xee\xd6\xba\x1dt?\
+l\xa6\xf7\x97\x05\x06\xefW\x84\x01 \x0d\x9d\xdcO\x05\
+l\xed\xbf\x8c\x91\x8e\xbbT\x83FE\xac\xafJ\x9b\xfb\
+\xc7\xb6\x15\xd6\x11G\x81N\xf2\x13V\x8f\xc6\x03h\xf4\
+\xd5\xc74\xb1~\x0d\x0a\x0b#\x0b\xd3\x09c\x13N,\
+\x820\xb6^\x1b\x12\x96\xc6J\xbd^\xcf\xf2\xaaG\x9a\
+]u\x8c\x99)\xbc]4\xe1\xfcx\xde\xff\xdd\xae\x8c\
+c\xfd14\x7f\xb3\x89\xc7\xfdK\xcd#Sf\xc4\xcc\
+|P\xcf\x1fe\xfe]\xa6\xf5?\xdf^[\x1f\xde\xff\
+\xfb\x03y\xfd\xc8I\x1c\xda3\xc3\xae7\xb38\xca\x97\
+\xfaq(T\xd3x\xa4\xfd\x01[f\xc8(\x04\xd5h\
+\x1e\x81\xbc\x1a\xd7\xcd\xb3\xc0\xfd\x98\x86\xeeu\xe6\xcf\x0b\
+\xf7\xb7j\xf3s7\x0a\xd2$\xdfK\xff$\xd7\x9f\x96\
+\xfeZ\xff\x1e\xe9\xad\xb9\xf4QZ\xe8\x0c\xd5GZ\x83\
+\xa7Eir\xd5\xb9\xb12\x07[\x81\x1fn\xf5\xacd\
+\xef6\xba\x89\xdc(\x09\xf5\xceY/\xd7o\xcaUM\
+\xdff\xc5\x16\x1ck\x8f^=F\x9fCi\x02)u\
+\xb6y4\x8f\xfcEv\x97\xa7\xc9\x1f\xd6\xdbX\xef|\
+\xb0\x9bY\xefK\xc9c\x9954\x01c'\xdf\x94\x17\
+Mz\x83\x18W\xfa\x87\xbf8\x98\x94\x91\xc6\xd1\xd5,\
+\xdd]\xba\xd5\xf3\xa0\x02\x84\xb8D\x9fV1y\xc5O\
+\x16\xf1\xa0\xda^\xd6\xb3_.\xe1h\xa6\xa5[\xeb<\
+\xc8\xa2u\x89\xf8[\x0b\xe6fAD\x85\x0dH\x16\x16\
+D\xb6[m\xf9\xd6:KM?\xd0\x804jA)\
+dA\xcc\x05\x1c\xe7P\x0a\xed]\xbe3F\x1d+\xdd\
+*X\xd6\xf9\xb4\x13K/\xdd:\xd4\x96om\xce\xac\
+\x12[\x9d\xca\xca\xbcVg\xaa\xdek\x95\xa9\xf0zw\
+\x98\xab`\x83\x8a\xa3TUf\xa7\xa3\xb4T\xe5\xd3*\
+\x1b\xc3\x19E)I\xc6\xdcQ\x08\xaa@\x8fY\xbf[\
+\x8cB\xa5&Qs\xd3\xc8d\x093[\x0c\x11\xafN\
+':\x8e\xa3u\x1dH\x0f\x16\x80\xfd*1\x1f\xe5\xc2\
+^\x06\xdc/F9LQI\x8fL\x91\xbaL*\xef\
+\xecP\x07q\x89\x9b\xb3\xad\xb9\xb1\xc3\x18T\x90\x8a\xd6\
+E_\xb63W\xb0\xa0\x0ad\x82\xd7\x07\xa6\xecn/\
+\xe3\x88x\xf5\x9f\xe5\xbf\x81{gb\xe7\x03\xbd\x01\xda\
+\xa2\xe5a\x11\xea\xe01C\xc7P\x90g=w\xa2\x1c\
+\xcc\xb1\x82\xa2\x0dV\xa1<$\xd1\xc0n\xb6\x9f\xdd~\
+\xf6\x22V\x83\x8b\xb0\x05\x1c7\x18VPsb`$\
+Adh\x0d\x8dG\xd4w\xb9\x8a\xfa\xf0cC]\x01\
+v\xcdu1\xe2\xc0\xc9\x9aPz\xbf2\xd0\xd3F\x0e\
+\xc5`\x99\xca\xe7\x0d\x0fw$\xa7J\x891\x81\xc3\x9d\
+bB\x0d\xc1st\xdfn\x08\x1e\x9b8\xd8\x83#!\
+}\x08<\xe6S\x93\xc7qs0~\x16\xe8P,\xa4\
+b%:\xf0\x88\xe18\x03\x0f\x1cy\x12\xc0\xa1\x8cz\
+d\x98\xff\x0fC\x87\x1a\x1e2\xda\xfc\x81\xe8$<\x18\
+\xa26f\x12\xab\xe7\x04\xcf\x11y\xcc\xd7q\xec\xc9\xf2\
+\xba\x92\xa2\x9e\xc4l\xd0\xb7\xf6Q\xee\xd2\x1c\x1b\xae^\
+\xfc\x0fQ\x96\x81s\
+\x00\x00\x08\xfb\
+\x00\
+\x00(sx\x9c\xddZmo\xdb8\x12\xfe\xde_!\
+8_\xb6\xa8E\xf1]\xa4j\xbb\xe8m\xd0\xe2\x80\x16\
+\xbb\xd8\xb6w\x1f\x0e\x8b\x83\x22\xd1\xb6\xb6\xb2dHJ\
+\xec\xf4\xd7\xdfP\x96d\xc9q\xdc$\xdb\xba\xc5\xb9(\
+ \x0d\x87\x9c\xe1\xc3g\x86\xc3\x88\x93W\xdbU\xea\xdc\
+\x98\xa2L\xf2l:\x22\x08\x8f\x1c\x93Ey\x9cd\x8b\
+\xe9\xe8\xd3\xc77\xae\x1a9e\x15fq\x98\xe6\x99\x99\
+\x8e\xb2|\xf4j\xf6lR\xde,\x9e9\x8e\x03\x9d\xb3\
+2\x88\xa3\xe9hYU\xeb\xc0\xf3\xd6\xd7E\x8a\xf2b\
+\xe1\xc5\x91gR\xb32YUz\x04\x11o\xb4W\x8f\
+\xf6\xeaQa\xc2*\xb91Q\xbeZ\xe5YY\xf7\xcc\
+\xca\x8b\x9er\x11\xcf;\xed\xcdf\x836\xacV\x22Z\
+k\x0fS\x8fR\x174\xdc\xf26\xab\xc2\xad;\xec\x0a\
+>\x1e\xebJ1\xc6\x1e\xb4\xed5\x1f\xa6\x15l\xd3$\
+\xfb|\xaf3uk\xdf:`\xb8\x86\xff]\x87V\x80\
+\xca\xfc\xba\x88\xcc\x1cz\x1a\x94\x99\xca\xbb\xfcx\xd95\
+\xba\x18\xc5U\xdc\x1b\x06\x06-\xa3pm\x06v[\xe1\
+\x0e\xafpe\xcau\x18\x99\xd2k\xe5u\xff\xde\xa2\x92\
+Z\x90\xc4\xd3\x11\xcc\x88\xfaR\xd6\xefK\x93,\x96\xd5\
+t$\xf9z[\x0b6I\x5c-{\xef\xadSA\x9c\
+G\xd6\xcat\xf4.,M\xf1\xab]4\xf3{\x18\xa3\
+\x16\x9f\xd6n\xd0'\x12\xa2\xce/F\xc9H\xf9X\xf9\
+z\xecPL\x89\x8b\x89K\xc4\xf3\xd1\x0czM\xba\xe1\
+\xed\xd8\xf1Mb6v,\xc7Y\x87\x0b D\x9a\x17\
+\xd3\xd1\xc5\xbc\xfe\x8dv\x0dWy\x11\x9b\xa2m\x92\xf5\
+o\xd0\x94\x03\x0cIu\x0b\xe6\x1bq~\xf5\x97\x89\xaa\
+*OM\x11f\x11L\x80\xe0\xa6eQ\xc0d\x8f\xc9\
+\xaf\x93\xd8\x1ck\xe8\xe6h\xdd\xeb\x0c\x1dm-\x97a\
+\x9co\xa6#z\xd8\xb8I2hp\x1b\x9c)\x16\xfe\
+=\x1a\xed\xd2\x10N:\x15X\xbd\x0e(\xed\x8bF\x5c\
+.\xf3\x8d\x9d\xcbt4\x0f\xd3\xd2\x1c\x8e\xf7%\xcfW\
+\xd3\x91BBRq\xd8\x16m\xa7#v\xc7\xc7\x08f\
+\xc5\x08R\x8aQr\xdf\x04\xb6G&\xde4\x1d\xc3\xa4\
+iZ\x85\xdbd\x95|1\xf1~}\xf6V\xaf\x8b\x02\
+r\x85\x9b\x86\xb7\xa6\x18\xd2\xb4\xa7\x05<\xbc\xb6)\xc5\
+-\xf2\x0a\xf2\x86%\x1a\xe4+\xaffSl\xe6\xe5\x1e\
+)\xfb\x06#\xa8\x9ai\xd0\x0a\xc1i\xc2\xe2m\x11\xc6\
+\x09\xf4\xdf\xe9\xed4\x87-\xccW~\xd3\xc72\xb4\xca\
+\xd7\xad.@]\xdd\xa6\xc0\x08+tk\x12\x06\x17X\
+\x12\x1d\xe1\x97\xb5\xa8\xe1D@^\x8e\xf6}\xf2\xf9\xbc\
+4\xd5\x1e\x93\xd6\xaa\xed\x01\xb6t\xe3\xfd\xc3\xac1_\
+G\xf3\xab\x07X#G\xadi\xd2Y\x9bx\xc3i?\
+\x16%%\xf9\xd9PR\xc0\x83\xf3\xa1\xa4\x803OB\
+i\xd1\xbc\x7f\x84\xc4QBr\x87\xb0[\x85U\x91l\
+\x7f\x81\x5c(\x09\xd7\x8c\x8c1\xfc#cWp\x8c\xb4\
+\xd4\x1c\x1e}\x86\x98/9}\xde\xf9rKm\xd2\xc1\
+\x88`\xc8\x9b\x9dt\x0bR\x895\x12\x5c\x13\xbd\xd7%\
+\xa0K%\xf2\xf5>\xbcA\x17\xa4B\x11D%c\xfb\
+9\xb6\xfe}\xca\x92\x0av\xbckH\xe4\x1f\xec\xae\xf1\
+[\xf6\xa9K\x1bG\xd7\x9a\xa8\xfd \xf5&\x17,\x0b\
+\x03\x9b\xf2\xc5\x11Nt\xc8\x1d\xc7\xc8\xceMS\xe4\x13\
+\xa2\xb4\x1e\xce\x0df\xc1\x08\xa3l0\xb7#\xba\xc4\xe2\
+ \x90\xe6R\xe8;s\xbb\x8b=\xec\xa7>%\x1c2\
+\xad\x1e\xc3\xa3\xaf$\x84\x81\x1e[1cBj\x9f\xda\
+g\x10\xc2\x0aQ2\x16\x5c &\xb0\x952\xa5\x11\x97\
+R\xc8\xe7\xdf\x02B\xfe\xad <\x1e\x8c.>g8\
+\xbag\x0eH\x97>-$\xbf\x02\xb5\x8bO\xad\x19\xed\
+v\xde\x87.\xfc#2\x80\xe4\x0cQ\xaa8\x1b\xbb\x8a\
+\x22\xce\x05lv\xcf\xbfB\xf2{\x02\xe2x\xf0\x0c\x02\
+\xed\xa7\x8bJ\xa8H5\x11Xwx\xf8>E`G\
+\x0a\xc0\x83#\xc1\xa8\xd0\xdf(\xee\xdc\xfe2\x7f\x95\x10\
+}\xa6=.\xfe\xa0\xe7Y#\xd0U\xe7\x8eA\x97=\
+-\x0a\xff\xdf\xe9\xf5\xe0\xd4\x0e$\xf9j,\xdeK/\
+}fz\x9d\xb32\xdd\xd1\x8b\x7f\xa7$\xefjW\x9c\
+XE\xa8\x0d\xb8+\xbfG\xaa\x87b\x8dP\x0a%D\
+\x93\xf3\x81\xd8\x82R.\xc7.\x17@Q\xc5\xb08\xc8\
+\xf9\x8c#\x8am\x05:\x88\x07\x82\x04\x9c\xf7\xa8?\x8c\
+\x1d_\x83%\x8d\xf5 \xe7\x13L\xe1\xe4&\xa9z:\
+\xcf\x00\xac33\xcd\xc5\xe7\xe7Z\xcf\xe6\x99\xd9\xa6\xf1\
+9\xb8\xa6|(p!\x8b\x02\xd7\x88@\x5cKF~\
+\x18\xd7\x1e\x84\xd9\xc9b\xcc\xa2v\x9e\x18\xfd\x99p;\
+\x15\xa3\xe7.\xf8m\x94\xf6\x8a\xa33\xc6)\x84\xd3\xf7\
+\x8b\xd4\xd3\xac#\x94\x7f3\xcea\xa4\xb0\x22>W\x96\
+t02\xdeq\x0e\xce\xa1\x92\xc0AT\xc2I@\x10\
+\x1f1\xae}\x0e\x87O\xcc\xe1T@%\xfd\x89\x83\x96\
+\xba\xf4d\xc8\x12\xd5S\xf8\xfb!+\x98\x12R1{\
+\x5cWP\x0c\x0a\xa2e\x8d\x9f\x96\x82\x09\xa5 b\x15\
+\xcc\x98)\x0c\x87*\x8b$\x80\xc7\xf5!|\x10\xd2\xd8\
+\x97b\x00\x9f\xfdc\x8b\xaf\xa9d\x07\xf0Q\x9b\x09\x84\
+\x7f\x00\x1fF\xd4\xa7\xe2\xe9\xa7\x84\xf3\x9f\x13~@)\
+\xc7\x9ezb\xb7\x18s\x85\x08\xf1%\x1e\xae\x87\x8f\x81\
+\xce\x1cVo\xb8v\x04\xcay)\xf0\xc1YA2\xc4\
+\xa8O\xc4\xa3\x88\x85\xeb\xea\xcc\xf7\xb58\xe0\x95&@\
+\x05\xe03\x84\xa5\x96\xd6\x09\xc2\xff\xfe\xe1\x01f\xc2\xdc\
+\xde\xdf\xf7\xbe\x1an\xe4\xc9\x9c#gf\xdc\x0f\xd8&\
+\xfc\xef\xb7G\xc8\x93\x8b\x08g\x07\xf5\x1d\x92\xdci.\
+B\xfa\xb6\xf5\x09\xe48\x0e\xda?c}\x22\xcf^\x9d\
+\xb0\x1fP\x9b\x9cf\xdd\xc4\xb3\x1f\xa1\xea\xa7\x95\xa9\xc2\
+8\xac\xc2g\xdd8\xadD\xb4_\x9b&E<\x0f\xfe\
+\xb8|\xd3\xcd!\x8a\x82\x7f\xe7\xc5\xe7\xbdy\xab\x10^\
+\xe5\xd7\xe0U\x87\xae\xfd\xee\x15\x05\x96Da5KV\
+\xe1\xc2\xd8o\xd6/\xb6\xab\x14\xacw\x0d\x03\xe5\xeav\
+m\xf6\x83\xee\x86-\xcc\xee\x9b\xf4\xd1\xcf\xf8q\xb4J\
+l'\xefC\x95\xa4\xe9?\xad\x91\x1e\xd6\xcd\xa0I\x95\
+\x9aYms\xf7\xb8o\x85y\xa4Id\xb2\xf2\x84\xd5\
+;\xe3\x014f\xf6>\xcf\x9c\xdf\xa2\xca!\xd8!,\
+\xe0<\x10\xd4\xa1\x98\x10\xe7\x05\x86_m\xac\xd6\x1b\xf4\
+\xaco\x14\xe4\xc5\xacg\xcc\xba\xf0z\xd1\xe5\x82\xbb~\
+\xffg\xb3\xb2\x9f\x1d\xff<\xe6\xbf]\xc7\xbb\xfdk\xcd\
+;\xa6\xec\x88\x85\xfdp[>\xca\xfc\x9b\xc2\x98__\
+_:\xef\xde\xfe\xfe\x8e\xbex\xa4\x13\x87\xf6\xec\xb0\xeb\
+\xeb\xab4)\x97\xe6q(4n<\xd2\xfe\x11[v\
+\xc8$\x06\xd5d\x9e\x80\xbc\x19\xd7+\x8b\xc8{\x9f\xc7\
+\xdee\x11\xce+\xef\x8ff\xf1K/\x89\xf2\xac\xdcI\
+\xffK/?,\xc3\xb5\xf9Wb6\xf6rAm\xa1\
+7\xd4\x10i\x93\xd6\xdf\x80g\xbd\x8b\x11s\xb0\x15\x85\
+\xf1\xc6\x5c\xd5\xec\xdd$\x9f\x13/\xc9b\xb3E\xeb\xe5\
+\xfaU=\xab\xe9\xeb\xa2\xda@`\xed\xd0k\xc7\x18r\
+(\xcf \x1f_]?\x9aG\xe1\xa2\xb8-\xf3\xecO\
+\xe7uj\xb6!\xd8-\x9c\xb7\xb5\xe4\xb1\xcc:\xe6\x80\
+\xb5S^\xd7\x17\x1a\x06\x83\xd8P\xfaG\xb88p\xca\
+J\xd3dv\x95o'^\xf3|Ta\x9d\x86\x999\
+\xadR\x80\xc50[\xa4G\xd5v\xb2\x81\xfdz\x0aw\
+<\xad\xc3\xda\x94Q\x91\xack\xc4_;\xe0\x9b3O\
+sX\x80l\xe1@f\xbb1N\xe8\xac\x8b\xdc\xf6\x03\
+\x0d\xc8\xc1\x0e\xec\xa3N\x9ao\x00\xc79\xec\xa3\xbb\x90\
+\xef\x8d\xd1\xe6J\xafI\x96m2\xee\xe5\xd2\x89\xd7\xa6\
+\xda\xfa\xad2\xdbf\xff\x82\x1c\x19\xd4\xd7f\xa6\xa35\
+d\x22S\xdc\xb4[t\xb3_\xcca\x11\xdc\xfa9\xc8\
+l\x1eM_\xd6\x92M}5c *\x93/&\xe0\
+x\xbd}iw\x81\xe6\xf2F@\x10\x15\xbb\xf6y\xb8\
+J\xd2\xdb\xa0\x84\xfd\xde\x05C\xc9\xfc\xe5\x1c2ip\
+1\x9f\xdbDV\xbf\xf4v\xa1\xb2*\xf2\xcf\xd6ff\
+\xda\x1d\xc9^\xd4`\x88\x08\xc9\xfdv\xaf\xbe\x9d\x8e\xa8\
+B\x0c3\xc5\xdaB\xd9\xee+v~J\xb1\xd1lR\
+\xc1\xe4\xb2\x16\xfd\xee\xaaM\x91\xdb\xa9Y7\x07eM\
+\xad\xac\xfa\x1fz\xefZ<\xb09\x83LUw\x83\xb4\
+o\xad\xd6\xf0Z\xaa\x0cA\xac'\x1a\xce)\xbdw\xa2\
+-\x0c\xbb\xd7\xdd\xe5\x98\x800\xc4\xf7\x05\xbcu\xd1\x0e\
+\xadq{ o\xaf\xd0@5\xe4S\xdd\xddb\xe9\xee\
+\xcd0\x84\xa9\xcf5\xdb\x03\x08\x85\x93V\x8c*\xb5\x07\
+\x90A\x05\xa5\x14fm\xed0Y\x87\xd5\xf2\xae\xfb\xf5\
+J\xb4\xceb|\xc4YX\xf9F`\x91\x8d\xc2u\x00\
+\xb1[\xf5e\x7f\xe5I\x16\xc0>j\x8aV\xda\xe1\xd0\
+\xf8\x03S|\xef\x10 \x0d\x1c\x5c}\x7f\xcc4\xe2\x84\
+`\xee\x10{\x1c\x92>\x14\x7fL\x22\x09\xc7T\xa9a\
+G\x84\x92\x90a\xee\x8f9\x1cy}M\xb0t\xa8@\
+R\x01\x14j\xcc\xb1-\x0c\x95\xe6\x0e\x85ZPs*\
+(\x9c\x88\x91\x14BH\xe90\x05\xbc\x14p:\x1fs\
+\x824`\x84\xb5\xc3(\x02\x81\x8f\xf9\x98\xd9\x1a\x94\xc1\
+\x12;\x14ZY=\x1eT\x94\x9a3\xce\x88\xa3\x91\x00\
+\x974\x05\xf7\x98\xfd\xc6\x22\xc1.\xf8\x02%\xa4\xd4\xcc\
+\xba,)\x93\xdd\x9d\x03\xbbj\x16Q\x8dw\xa5\xe4\xc4\
+\xd6&\xb3g\xff\x03Y$\xd5R\
+\x00\x00\x09\xb3\
+\x00\
+\x00.9x\x9c\xedZmo\xdb8\x12\xfe\xde_!\
+\xb8_\x1a\xd4\x92\xf8.\xd2\x89S\xe4\xb6hq@\x8a\
+]l\xdb\xbd;\x1c\x16\x07E\xa2mmd\xc9\x90\xe4\
+\xd8\xe9\xaf\xbf\xa1d\xbd\xd9\x8e\xf3\xd2\xc4-\x0e\xe7 \
+\xb14\x1cr\x863\xcf\x0c\x87!\xcf\xde\xad\xe7\xb1u\
+\xa3\xb3\x1eH\x87\x0b\xc2\xb7\xdb\x82\xf5x@wt\
+\x0cn\xf7Q7z\xad\xf7\xccx\xd3\xb4\xcf\x18\x9b\xa6\
+\xb9\xbf\x8e\xe6\xd17\x1d\xb6\x8ei\xc5-\xb3\x0cr\x84\
+\x1d\xfb\xb7:\xeb\xc3\xb3\xc3\x05\xf8[\x9aTbgi\
+\x01\xf9\xc2 \x0c\xf2\x94[\xc2(\xd4\x93\xbc5\x91y\
+\x83\x11d\x091h\x9d\xfb\xd9\xb5\xce\xaav\xb0Vq\
+\x1b\x83SS\xc0\xe9$NW\xa3\x9b(\x8f\xaeb}\
+:\xa8\x19\xcc\x10\x17Y\x96\xae\xc8g\x9d\x84\x0d9\xd3\
+\x93\x7f\x82H\x07u)\xff\xeaS\xd2,\x02\x0d\xc7\x03\
+\x7fY\xa4\xedx\xf5\x0c\xf2\x22\x0d\xae\xef\x18\xbda\x8a\
+\xf2\x92m<(\xb2\xa5\xde\xcc\x00\xe6\xb0\xf0\x8bY\xcd\
+kY\x05 3\x87\xe4\x01~\x85^\xb1~\x83\x1cz\
+b\x95\x86\xd1o\xb0D'\x15Gl^m\xe2\xd0!\
+:\x19\xb4\xbdA\x83O\x96t<,\xb9\xf4\xe4\x909\
+\x88R\x8frb]Z\xc0L\x90G\xa4\xe2C\x98\x17\
+\x16\x08S\xc2\x054l\xd8%\x1b\xda\xc0\x8f\xb0\x87<\
+i\xfdb\x09Gy\x14)\x84\x866v\x04Q\x82\x09\
+e\x88\x92b\xe6\x89!\xd00\xf7\x18\xc3{\xc5}\xb3\
+:Jm\xdc2\x89\xe2\xd8\xce\x96\xb1\x1e\xe9\x1b\x9d\xa4\
+ax\x9a\x17Yz\xad\xab\x80\x19!\x10\xc2k\x12$\
+[\xfdW\x1a%\xa3,]&5\xe3\xe85B\x93\x09\
+B5\xd3&LG\xf8\xd4\x0c\xdd\xb4\x96r\x9a\xb6\x8e\
+\x1e\xc6?\xc6\xd8XH\xb6\x81\x17\x98\xdf\xad0\xf4\x9d\
+\x80\xc2/\x0a(\xfc\xbc\x80\x22w\x01J\xf4\xd1t\x8f\
+\xe3\xb6\xfdQ\xf9\x11/\x8a\xa7:\xa8D/\xd8\xc8\x00\
+\x14\x80\xc9\xe1\xc9\xe6\xe5\xa3\x8d\x89\xc3;\xe4\x8aZ\xb3\
+\xf6\xd0\xd6z\x19\x12\xcd3{\x99\x5c\xbeh\xda\xb8|\
+F/CUp\x97\x97\xf1\xffs\xc6\x13r\x86G\xee\
+B\x93\x11\xecg\x1f3?4\xbe\xee\x82\xa6\xdfB=\
+\xe9\xb5\xfe\x03\xa7.v\xe6k\x88vY\x02\x81j\x02\
+\xab\xc0(\x0e\xa4V\xed\x8en\xe9d\x92\xeb\xa2]\x98\
+k\xa9\xa6\x07\xc8R\x8d\xbe\x0f\x91F=\x15L\xae\x1e\
+ m\xdb:\x954\x85;\xd6\xe9O\xfb\xb1V\x92\x82\
+\x1d\xcdJ\xb2\x93#^\xdeJ\x12\x0a\x97'Yi\xba\
+y\xff\xd2F\xf9\xdc/\xb2h\x0da\x0e\x06`\x8aB\
+L\xc3\x0f\x86|\xc9\x90\xa3\x84b\xf0\xe8Q\x87z\x82\
+\x916\xd8o\x89)y\x91\x83\x11T\xed\x0du\x0dT\
+\x81\x94\xc3\x99\xc2\xaa\xe5\xc5\xc0K\x84\xe3\xa9\xb6\xb8\x04\
+^\xa0r\x89\x1d\x22(m\xe7X\xeb\xf75\x89\x0a\xd8\
+n-\xcd.\xc2lY~M\xbe6E\xeb^_c\
+\xd9\x0eR\xee\xb0F3\xc8\xa1P\xfe\xef\xc1Dc\xb9\
+\xfd62sS\x04\xf2\x0a\x96J\xf5\xe7\x06\xb3\xa0\x90\
+\xb9hon{x\xb1\xb1\x03w\x14\x13\x5c\xed\xccm\
+\xd7\xf6\xb0\x99\xf3\x08fP\xe7+H\x8f\x9e'\x05\x84\
+\x81\x1a\x1a2\xa5\x5c(\x8f\x98g \x82\x87\x08\x1er\
+\xc6\x1d\xca\x91\xa1R\xa9\x1c&\x04\x17'\xcfaB\xf6\
+\x5c&\xdc\x1f\x8c6:f8\xdaG\x0eH\x9b<-\
+$\xef1\xb5\x8d\x0e\xf9\x8c4\xfb\xbe\x87:\xfe\x11\x19\
+@0\xea\x10\x22\x19\x1d\xda\x928\x8cq\xd8q\x9d\xdc\
+\x03\xf2;\x02b\x7f\xf0\xf4\x02\xed\xa7\x8bJ(|\x14\
+\xe6H5\xf6\xf0<\xe2\x80\x1c\xc1\xc1\x1e\xcc\xe1P\xc1\
+\xa8g\x8a;\xbb\xeb\xe6{\x01\xd1E\xda\xe3\xe2\x0fz\
+\x1e5\x02my\xec\x18\xb4\xe9\xd3\xa2\xf0\x7f\x1d^\x0f\
+N\xed\x00\x92{c\xf1Nx\xa9#\xc3\xeb\x98\x95i\
+\x05/\xf6BI\xdeV6?\xe0E\xa8\x0d\x98-^\
+\x22\xd5C\xb1\x86\x09\x81\x12b\x93\xf3\x01\xd8\x9c\x10&\
+`?\xc6\x01\xa2\x92\x22\xbe\x95\xf3)\x83\xbd\x9c\xa9@\
+{\xf1\x80\x1d\xae\x08i\xe4\x09\xde3\x9f\xf9g\
+\x8b\xa7\x88\xa0[\xe6#&\x13po\xcb|\xc8!\x1e\
+\xe1O\xdf%\x1c\x7f\x9f\xf0\x03J9\xfa\xd4\x1d\xbb\xb1\
+1\x93\x0e\xc6\x9e@}\x7fx\x08\xe0\xcc\xc0{}\xdf\
+a(\xe7\x05G[{\x05A\x1dJ<\xcc\x1f\x05,\
+TVg\x9e\xa7\xf8\x16\xae\x14\x06(\x00\x9e!,\x95\
+0J`\xf6\xfd\x9b\x07\x98\x09\xb5;\xff\xdf\xbb7\xdc\
+\xf0\x931\x87\x8f\x8c\xb8\x1f\xb0Lx/\xb7F\x88\x83\
+N\x84\xbd\x83|\x81$w\x18\x8b\x90\xbeM}\x029\
+\x8e\x01\xf7\xcfX\x9f\x88\xa3W'\xf4\x07\xd4&\x87Q\
+w\xe6\x9a\x9b\x10\xe5\xd3\xf4Uk0s\xcb\xa2\x8e\xc8\
+\xde\x01\xa1i\xcdtPP\x82\xa0\xe8\xb1\xdb\xf4W\x1e\
+\xfeQ:\xa4\xdc\xe2C\xa6\xcc#W\x16WC\xc6\xad\
+o\xd6\xdcBCna1\x14\x96\x0d\x7f%\xfc\xf5\xa0\
+\x90\xb5\xbe5\xfd7\xf3\xaf\xcd]~6Gk\xd5\xe5\
+\x9e\xad\xa3\xb5\xd3\x03\x87\xc9W\x98\x13\xdc?\xe0#\xdd\
+\xc3\xbd\xc0_\xf4\xce\xf6\xf6\x9f\xf8\xd9\xf3\xa8\xd0Y\x1c\
+\xc1\xd7\x88\xed\x1e\xfbm\x08\xa1\x9f\xcf\xfc,\xf3oG\
+I\x9a\xe8.\xb5\xf2\xce\x08\x9dVg|U{y$\
+\x1c\xc5f\x88\xfat8\x8c\xf2\x05\x18|\x14%F\x8d\
+\xd3\x9d\xd3c\x9d\xf8\xf0e_\xf9\xc1\xf5\xb4\xd4o\xe4\
+\x07\xc1r\xbe4G\xb0\xf5\xe5\x16wZ~\xcdu\xe1\
+\x87~\xe1\xb7\x9e\xac)\xbc>0<\xcb\xc2\xc9\xe8\xf7\
+\xf7\x1f\x1a\x18\x06\xc1\xe8\x1fiv\xdd\x22\xc80\xf8W\
+\xe9\x12\x80\xd5\x04\x88\xb9?\x13\x8cL\x1e\xf0\x8b\xf3h\
+\xeeO\xb5\xb9\xf3\xf6v=\x8f\x01@MC\x8f\xb9\xb8\
+]\xe8v\xd0j\xd8LWw\xda\xf6^\x03\x0c\x03\xb0\
+4tr?\x17\xe0\xda\xbf\x1b!\x9dp\xd9\x0c\x1a\x15\
+\xb1>/eV\x8fm+\xcc#\x8e\x02\x9d\xe4\x07\xa4\
+\xee\x8c\x07\xa6\xd1\xe7\x9f\xd2\xc4\xfa5(,\x8c,L\
+G\x8c\x8d8\xb1\x08\xc2\xd8zk@X\x0a+\xf9z\
+=\xcb\x1b\x89iv\xde\x11fT\xb8\x986\xe9|W\
+\xef\x7f\xaf\xe6&\xb0\xfe\xdc\xa7\xbfq\xe2n\xff\x92s\
+G\x94\x19137\xbf\xf2G\x89\xff\x90i\xfd\xcb\xc5\
+{\xeb\xf2\xe3o\x97\xe4\xed#\x95\xd8\x96g\x86],\
+\xaf\xe2(\x9f\xe9\xc7Ya\xa3\xc6#\xe5\xef\x91e\x86\
+\x8cB`\x8d&\x11\xd07\xe3\xbay\x16\xb8\x9f\xd2\xd0\
+}\x9f\xf9\x93\xc2\xfd}\xe3\xfc\xdc\x8d\x824\xc9+\xea\
+\x7f\xc8\xfb\xcf3\x7f\xa1\xff\x88\xf4\xca\xdcN,%t\
+\x86\xea[Z\xc7\xe5]\xb2\xf3\xce\xc5\xca\x09\xc8\x0a\xfc\
+p\xa5\xafJ\xf4\xae\xa2\xeb\xc8\x8d\x92P\xaf\x9d\xc5l\
+\xf1\xae\x9c\xd5\xf8\x22+V\x10X\x95\xf5\xea1\xfa\x18\
+J\x13XR\xaf\x96\x8f\xc6\x91?\xcdn\xf34\xf9\xd3\
+\xba\x88\xf5\xda\x07\xb9\x99\xf5\xb1\xa4<\x16Y\xfb\x140\
+r\xf2ey#\xb27\x88\x09\xa5\xbf\xf9\xd3-\xa5\x0c\
+5\x8e\xce\xaf\xd2\xf5\x99\xbby\xde\xcb\x00).\xd1\x87\
+Y\xcc\xba\xe2'\xd3x/[E\xeb\xc9/\xa7\xb0\xa3\
+i\x19\xd6:\x0f\xb2hQZ\xfc\xc2\x02\xdd,\xc8\xa8\
+\xe0\x80djAf\xbb\xd1\x96o-\xb2\xd4\xf4\x03\x0e\
+XF-(\x85,\xc8\xb9`\xc7\x09\x94BU\xc8w\
+\xc6\xa8s\xa5\xbbI\x96\xf5z\xda\xc9\xa5gn\x9dj\
+\xcb\xb7v\xcd\xec\x5c@\xb9\xe3\x96\xc8\xc1[P\xcc\xa1\
+\xdbK\x17\xf8\xab\xd8Y\xb9\xca\xc5\xea\xd0\xca\xb5\x7f\xa1\
+j\x95\xa8V)[\xc3\xfa\x02I\xf9\xcd\xeb\xf6\x02b\
+]\xbb\xd5K\x94\x04\xd8\xcc\x93P\x9a\xd7\xef\
+\x9f b\x94\x10\xd6\xc1\xdf\x96aU$\xdb\x9f \x08\
+z\x98I\x8a\xc7\x08\xfe\xc3c\x9b3\xe4HO2x\
+\xf4\xa9C}\x8f\x91W\xed^\xee\x88\x8e6\xc8\xc1\x08\
+\x02fK\xdd\x02\xd5C\xd2\xe1Lb\xb9\xe7\xc5\xc0K\
+<\xc7\x97{\xbf\x06^\xa0r\x81\x1d\xe2Q\xba\xd7\xb1\
+\xd9\xdf\xe7,\xa9 \xd7\xad!\x84\x7f\xd4\xf9\xe2\xb7\xec\
+s\x1b/\x06\xcf\x1a\x8b\xfd\x22&\xbd\x05\x8bBA:\
+~9`\x13-r\xc3\x18i\xdd$q|\x8c\x85\x94\
+}\xdd@\x0b\x8a)\xa1=\xdd\x06x\xb1\xc6\x81;\x92\
+y\x5c\x1e\xe9v\x8c=dR\x9f`\x06!V\x8e\xe1\
+\xd1\x17\x1e\xb8\x81\x1ck2\xa5\xdc\x93>\xd1\xcf@\x84\
+\x13\x22x\xcc\x19w(G\x9aJ\x85t\x98\xe7q\xef\
+\xd5\xb7\x80\x90}+\x08\x87\x9d\xd1F\xe7tG\xfb\xcc\
+\x0ei\x93\xa7\xb9\xe4=P\xdb\xe8\xd4\x99\x916\xe5>\
+\xf4\xe0\x1f\x11\x01\xcd\x0b\
+\xff\xdf\xcd\xeb\xc1\xa1\x1d\x8c\xe4^_\xfc\xaay\xc93\
+\x9b\xd79+\xd3\x9dy\xb1\xef\x14\xe4mi\xf3\x13\xa7\
+\x08\xb5\x01\xb3\xbd\xef\x11\xea\xa1X\xc3\x84@\x09Q\xc7\
+|0lN\x08\xf3\xc66\xe3`\xa2\x82\x22~\x10\xf3\
+)s\x08\xd2\x15h\xcf\x1f\xb0\xc3\xa1\xd1#~\xdfw\
+|\x09\x92$\x92\xbd\x98\x8f\x11q\x84\xf0\x88x\xba\x9d\
+\x01Xg\xb64\x1b\x9d\xdf\xd6:2\xcflm\x12\x9d\
+\xc3\xd6\x84\x0f\x05.DQ\xb05\xcc\x1d&=\x8a\x7f\
+\x98\xad=\x08\xb3\x93\xc5\x98F\xed<>\xfa\x9cp;\
+\xe5\xa3\xe7.\xf8\xb5\x97v\x8a\xa33\xfa)\xb8\xd3\xf7\
+\xf3\xd4\xd3V\x87\x09\xfbf6\x87\x1c\x81\x04\xf6\x99\xd0\
+F\x07+\xa3\x9d\xcdA\x1f\xeaahD=\xe8\x048\
+\xf6\x1d\xca\xa4\xcf\xa0\xf9D\x0c\xba\x02\xe2\x91g\xec\xb4\
+\xc4&']\x16\x8b\x0e\xc3?wYN\x05\xf7\x04\xd5\
+\xed\xba\x80b\x90c\xe9\x19\xfc\xa4\xc7)\x17\x02\xe3O;\xb7Zf +\x0a\xe3\x8d\xba6\
+\xd6\xbbIn\x127\xc9b\xb5uV\x8b\xd5k\xa3\xd5\
+\xe4MQm\xc0\xb1v\xe85k\xf4m(\xcf \xa5\
+^\xaf\x1fmG\xe1\xbc\xb8+\xf3\xec/\xebM\xaa\xb6\
+!\xc8-\xacw\x86\xf2X\xcb\x1a\xda\x80\x96S\xae\xcd\
+e\x94\xde\x22\xda\x95\xfe\x15\xce\x0f6\xa5\xa9i2\xbd\
+\xce\xb7\x97n\xfd<\xc8\x00!.S\xa7Yt^\x09\
+\xb3y:\xc8\xb6\xa3\xf5\xe4\x1b\x15\x8evj\xdcZ\x95\
+Q\x91\xac\x0c\xe2o,\xd8\x9b\x05\x11\x15\x0e \x9b[\
+\x10\xd9n\x95\x15Z\xab\x22\xd7\xf3\x80\x03\xd2\xa8\x05\xa5\
+\x90\x051\x17p\x9cA)\xb4s\xf9\xce\x1aM\xact\
+\xeb`\xd9\xe4\xd3N,\xbdt\x9bPk\xde\xf69\xb3\
+NlM*3y\xad\xc9T\xbd\xd7:S\xe1\xd5\xf6\
+0W\xc1\x01UG\xa9\xcad\xa7\xa3\xb4T\xe7\xd3:\
+\x1bC\x8f\x22\xa5 c\xeeH\x04U\xa0\xcf\xac?-\
+F\xa1R\x13h\x7fo\x05x\xf5n1D\xbc&\x9d\
+\xa84MVM =P\x00\x87ub>\xca\x85\xbd\
+\x0c\xb8SF:LRA\x8fD\x91\xa6L2\xf7z\
+\xa8\x83\xb8\xc0mo\xabo\xf50\x06\x15\xa4\xa4M\xd1\
+Wl\xf55-\xa8\x02\x99\xc7\x9b\x86\xa9\xb8\xdb\xd18\
+\x22~\xf3g\xf9\xaf\xe0\xde\xd9\xd8\xf9@o\x81\xb6\xa8\
+i\x16\xa1\x0e\x1e3t\x0c\x05y\xd6{'\xd2\xc1\x1c\
+K(\xda@\x0b\xe9#\x81\x06Ns\xff\xd9\xedG+\
+\xb1\x1cT\xc2\xf6\xa0\xdd`XB\xcd\x89\xc1\x22\x09\x22\
+C:\x1c\xdd\xe4\xaa\x9a\xe6\xc7\x86\xba\x02\xe4\xea+e\
+\xc4\x81\xce\x9aPz?3\x98\xa7\x8d\x1c\x8aA2\x15\
+\xcf\x1b\x1e\xee\x08N\xa5\xf4\xc6\x04\x9a;\xc9<9\x04\
+\xcf\xd1\x9d\xbc!xl\xe2`\x1fZB\xfa\x10x\xf4\
+\xa7&\x9f\xe3\xb61~\x16\xe8P\xec\x09\xc9\x0c:\xf0\
+\x88\xa1\x9d\x81\x07\x8e|\x01\xe0PF}2l\xff\x0f\
+C\x87j;d\x14\x1d\xdd\xa5\x1c\x82\x07C\xd4\xc6L\
+`\xf9\x9c\xe092\x1e\xfdu\x1c\xfb\xc2\x5cW\x92\xd4\
+\x17\x98\x0d\xfaV\x1b\xe5*\xb5\xad\x9bs\xe8\x1e\x02s\
+\x1b\x18\xf8\xa0FW\xc5m\xf3\xf7\x87F=(Ol\
+\xf3\x0cJB\x87\x91^\x18\xca\xc6\x5c8\xed\x91\xca\xe4\
+\x8b\x0a\x18\x02E\xb56\xf5\x95\xd4\x00;\x84\xef\xc6g\
+\xe12I\xef\x82\x12@\xb6AP2\xab[K\x84b\
+u:\x9d\xd5;\xda\xa5)\xcc=\xe67\xce\xa1\x0dX\
+8\x14A~\xeb\x9a\x84\xd6O@\xca\x9b^V\xa0\x5c\
+\xd6\xf6\xb7\xcd\x05\xe2\x22\xd7\xaa\xe9m\xf6\xfefc\x98\
+E\xf7\x16\xdb\xb1\xc4\x03\x99S\xa8\xe1\xcd4h\x88\xb4\
+\xd4\xe9\x8bK\xdd\x95M_\xfc\x0fT\xf0\xf3\x8e\
+"
+
+qt_resource_name = b"\
+\x00\x05\
+\x00o\xa6S\
+\x00i\
+\x00c\x00o\x00n\x00s\
+\x00\x15\
+\x05\xe4ug\
+\x00L\
+\x00a\x00s\x00e\x00r\x00R\x00e\x00c\x00o\x00m\x00p\x00u\x00t\x00e\x00P\x00a\x00d\
+\x00.\x00s\x00v\x00g\
+\x00\x16\
+\x0cx\x07\x87\
+\x00L\
+\x00a\x00s\x00e\x00r\x00S\x00e\x00l\x00e\x00c\x00t\x00B\x00a\x00s\x00e\x00R\x00e\
+\x00f\x00.\x00s\x00v\x00g\
+\x00\x13\
+\x02nI\xe7\
+\x00L\
+\x00a\x00s\x00e\x00r\x00_\x00W\x00o\x00r\x00k\x00b\x00e\x00n\x00c\x00h\x00.\x00s\
+\x00v\x00g\
+\x00\x12\
+\x07\xc2>\x07\
+\x00L\
+\x00a\x00s\x00e\x00r\x00C\x00r\x00e\x00a\x00t\x00e\x00P\x00a\x00d\x00.\x00s\x00v\
+\x00g\
+\x00\x11\
+\x07%\xe9'\
+\x00L\
+\x00a\x00s\x00e\x00r\x00S\x00a\x00v\x00e\x00P\x00r\x00o\x00g\x00.\x00s\x00v\x00g\
+\
+\x00\x13\
+\x02\x10\x16\x07\
+\x00L\
+\x00a\x00s\x00e\x00r\x00C\x00r\x00e\x00a\x00t\x00e\x00P\x00r\x00o\x00g\x00.\x00s\
+\x00v\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\xf0\x00\x01\x00\x00\x00\x01\x00\x000\xd9\
+\x00\x00\x01\x83W\x0f\xd4q\
+\x00\x00\x00r\x00\x01\x00\x00\x00\x01\x00\x00\x14\xa8\
+\x00\x00\x01\x82\xd5.^\xdd\
+\x00\x00\x00\x10\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x83W\x13\x97\xd3\
+\x00\x00\x00\xc8\x00\x01\x00\x00\x00\x01\x00\x00'\x22\
+\x00\x00\x01\x83W\x15\xbc\xa3\
+\x00\x00\x00\x9e\x00\x01\x00\x00\x00\x01\x00\x00\x1e#\
+\x00\x00\x01\x83W\x12C\xca\
+\x00\x00\x00@\x00\x01\x00\x00\x00\x01\x00\x00\x0a\x17\
+\x00\x00\x01\x83W\x18),\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/freecad/LaserCladdingWorkbench/pad.py b/freecad/LaserCladdingWorkbench/pad.py
new file mode 100644
index 0000000..4b0746e
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/pad.py
@@ -0,0 +1,195 @@
+import FreeCAD
+import FreeCAD as App
+import FreeCADGui as Gui
+import numpy as np
+import math
+import Part
+from shapely.geometry import Polygon, Point
+from pyslm import hatching as hatching
+from pyslm.geometry.geometry import LayerGeometryType
+from typing import List
+import rdp
+from .utils import *
+from freecad.LaserCladdingWorkbench.path import LaserPath
+
+
+
+class LaserPad:
+ def __init__(self, obj, face):
+ obj.addProperty("App::PropertyFloatConstraint", "tool_width", "Parameters", "Width of the circular effect area of Laser in mm")
+ obj.tool_width = (2.3, 0.01, 20.0, 0.1)
+
+ obj.addProperty("App::PropertyFloatConstraint", "hatch_angle", "Hatching Parameters", "Hatch Angle")
+ obj.hatch_angle = (0.0, 0.0, 180.0, 1.0)
+
+ obj.addProperty("App::PropertyFloat", "hatch_volume_offset", "Parameters", "Offset between internal and external boundary")
+ obj.hatch_volume_offset = 2.3*0.5
+
+ obj.addProperty("App::PropertyFloat", "hatch_spot_compensation", "Parameters", "Additional offset to account for laser spot size")
+ obj.hatch_spot_compensation = 2.3*0.5
+
+ obj.addProperty("App::PropertyInteger", "hatch_inner_contours", "Parameters", "Inner Contours")
+ obj.hatch_inner_contours = 1
+
+ obj.addProperty("App::PropertyInteger", "hatch_outer_contours", "Parameters", "Outer Contours")
+ obj.hatch_outer_contours = 0
+
+ obj.addProperty("App::PropertyFloat", "hatch_contour_offset", "Parameters", "Contour Offset")
+ obj.hatch_contour_offset = 2.3
+
+ obj.addProperty("App::PropertyLinkGlobal","ref_body","Reference","body")
+ obj.ref_body = face[0]
+
+ obj.addProperty("App::PropertyString","ref_surface","Reference","face")
+ obj.ref_surface = face[1]
+
+ obj.addExtension("App::GroupExtensionPython")
+ obj.Proxy = self
+ self._create_path(obj)
+
+ def _create_path(self, obj):
+ print("Create_path")
+ print("obj.ref_surface", obj.ref_surface)
+ # if len(obj.Group):
+ # obj.removeObjectsFromDocument()
+ face = obj.ref_body.getSubObject(obj.ref_surface)
+ myHatcher = hatching.Hatcher()
+ myHatcher.hatchDistance = obj.tool_width
+ myHatcher.hatchAngle = obj.hatch_angle
+ myHatcher.volumeOffsetHatch = obj.hatch_volume_offset
+ myHatcher.spotCompensation = obj.hatch_spot_compensation
+ myHatcher.numInnerContours = obj.hatch_inner_contours
+ myHatcher.numOuterContours = obj.hatch_outer_contours
+ myHatcher.contourOffset = obj.hatch_contour_offset
+ myHatcher.hatchSortMethod = hatching.AlternateSort()
+
+ polygon_coords = get_coords_from_shape(face)
+ layer = myHatcher.hatch(polygon_coords)
+
+ contours_geoms = layer.getContourGeometry()
+ hatch_geoms = layer.getHatchGeometry()
+
+ contours = create_contour_lines(contours_geoms)
+ hatchlines = create_hatch_lines(hatch_geoms)
+
+ proj_contours = project_to_face(contours, face)
+ proj_hatchlines = project_to_face(hatchlines, face)
+ # DEBUG Part.show(Part.makeCompound(hatchlines))
+ obj.purgeTouched()
+
+ for pc in proj_contours:
+ p = Part.makeCompound(pc)
+ contours_comp = obj.newObject("Part::Feature", "Contours")
+ LaserPath(contours_comp)
+ contours_comp.Shape = p
+ contours_comp.ViewObject.LineColor = (0.5, 0.8, 0.9)
+
+ print("Creating Hatch Part")
+ hatches_comp = obj.newObject("Part::Feature", "Hatchlines")
+ p = Part.makeCompound([])
+ for pc in proj_hatchlines:
+ p.add(Part.makeCompound(pc))
+
+ LaserPath(hatches_comp)
+ hatches_comp.Shape = p
+ hatches_comp.ViewObject.LineColor = (0.9, 0.2, 0.2)
+ print("Group: ", obj.Group)
+
+ def onChanged(self, fp, prop):
+ '''Do something when a property has changed'''
+ App.Console.PrintMessage("Change property: " + str(prop) + "\n")
+
+ def execute(self, fp):
+ App.Console.PrintMessage("Recompute LaserPad\n")
+ # self._create_path(fp)
+
+
+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.addExtension("Gui::ViewProviderGroupExtensionPython")
+ self.Object = obj.Object
+ obj.Proxy = self
+
+ def claimChildren(self):
+ """Return objects that will be placed under it in the tree view."""
+ App.Console.PrintMessage("Reclaim children\n")
+
+ claimed = []
+ App.Console.PrintMessage(self.__dict__)
+ for o in self.Object.OutList:
+ if o.TypeId == 'Part::Feature':
+ claimed.append(o)
+ return claimed
+
+ def attach(self, vobj):
+ self.Object = vobj.Object
+ self.ViewObject = vobj
+
+ def getDisplayModes(self, obj):
+ "'''Return a list of display modes.'''"
+ return ["Standard"]
+
+ def getDefaultDisplayMode(self):
+ "'''Return the name of the default display mode. It must be defined in getDisplayModes.'''"
+ return "Standard"
+
+ 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 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
diff --git a/freecad/LaserCladdingWorkbench/path.py b/freecad/LaserCladdingWorkbench/path.py
new file mode 100644
index 0000000..b5edf87
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/path.py
@@ -0,0 +1,17 @@
+import FreeCAD as App
+import FreeCADGui as Gui
+
+class LaserPath:
+ def __init__(self, obj):
+ obj.addProperty("App::PropertyEnumeration", "pathtype", "Parameter", "How this path is converted to robot")
+ obj.pathtype = ["LIN", "CIRC", "SPLINE"]
+
+ # obj.addExtension("App::GroupExtensionPython")
+ # 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):
+ App.Console.PrintMessage("Recompute LaserPath\n")
diff --git a/lasercladding/job.py b/freecad/LaserCladdingWorkbench/program.py
similarity index 66%
rename from lasercladding/job.py
rename to freecad/LaserCladdingWorkbench/program.py
index ab334ce..ae005c1 100644
--- a/lasercladding/job.py
+++ b/freecad/LaserCladdingWorkbench/program.py
@@ -1,18 +1,44 @@
+import FreeCAD
import FreeCAD as App
+import numpy as np
+import math
+import time
+import Part
+import Draft
-class LaserJob:
+class LaserProgram:
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]
+ obj.addProperty("App::PropertyFloat", "laser_power", "Laser Parameter", "Laser Power output (0.4 means 4 Volts)")
+ obj.laser_power = 0.4
+
+ obj.addProperty("App::PropertyInteger", "laser_pilot_out", "Laser Parameter", "Pilot Laser output")
+ obj.laser_pilot_out = 4
+
+ obj.addProperty("App::PropertyInteger", "laser_real_out", "Laser Parameter", "Laser output")
+ obj.laser_real_out = 3
+
+ obj.addProperty("App::PropertyInteger", "laser_gas_out", "Laser Parameter", "Laser inert gas")
+ obj.laser_gas_out = 7
+
+ obj.addProperty("App::PropertyInteger", "laser_powder_out", "Laser Parameter", "Laser Powder Bucket")
+ obj.laser_powder_out = 9
+
+ obj.addProperty("App::PropertyFloat", "laser_feedrate", "Laser Parameter", "Process Velocity (Feedrate m/s)")
+ obj.laser_feedrate = 0.022500
+
+ obj.addProperty("App::PropertyFloat", "laser_speed", "Laser Parameter", "Velocity reaching (m/s)")
+ obj.laser_speed = 0.15
+
+ obj.addProperty("App::PropertyFile", "progpath", "Export Parameters", "Where to store the Program")
+ obj.progpath = "/home/jk/test_export_workbench.src"
+
+ obj.addExtension("App::GroupExtensionPython")
+ obj.Proxy = self
def onChanged(self, fp, prop):
'''Do something when a property has changed'''
@@ -20,41 +46,33 @@ class LaserJob:
def execute(self, fp):
'''Do something when doing a recomputation, this method is mandatory'''
- App.Console.PrintMessage("Recompute Python Box feature\n")
-
+ App.Console.PrintMessage("Recompute LaserPad\n")
-
-
-class ViewProviderLaserJob:
+class ViewProviderLaserProgram:
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")
+ obj.Proxy = self
- obj.addExtension("Gui::ViewProviderGroupExtensionPython", None)
+ def attach(self, vobj):
+ obj = vobj.Object
- obj.Proxy = self
+ def getDisplayModes(self,obj):
+ "'''Return a list of display modes.'''"
+ return ["Standard"]
+
+ def getDefaultDisplayMode(self):
+ "'''Return the name of the default display mode. It must be defined in getDisplayModes.'''"
+ return "Standard"
- 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'''
diff --git a/freecad/LaserCladdingWorkbench/utils.py b/freecad/LaserCladdingWorkbench/utils.py
new file mode 100644
index 0000000..9d29a2c
--- /dev/null
+++ b/freecad/LaserCladdingWorkbench/utils.py
@@ -0,0 +1,160 @@
+import FreeCAD
+import FreeCAD as App
+import FreeCADGui as Gui
+import numpy as np
+import math
+import Part
+from shapely.geometry import Polygon, Point
+from pyslm import hatching as hatching
+from pyslm.geometry.geometry import LayerGeometryType
+from typing import List
+import rdp
+
+def project_to_face(compounds, face):
+ # proj = Part.makeCompound([])
+ proj = []
+ for c in compounds:
+ projection_result = []
+ for e in c.Edges:
+ projection_result.append(face.makeParallelProjection(e, App.Vector(0, 0, 1)))
+ # proj.add(Part.makeCompound(projection_result))
+ proj.append(projection_result)
+ return proj
+
+
+def map_wire(wire, surface):
+ """Map wire on target surface
+ Input wire must be on XY plane"""
+ plane = Part.Plane().toShape()
+ mapped_edges = []
+ for e in wire.Edges:
+ c, fp, lp = plane.curveOnSurface(e)
+ mapped_edges.append(c.toShape(surface, fp, lp))
+ return Part.Wire(mapped_edges)
+
+
+def path2DToPathList(shapes: List[Polygon]) -> List[np.ndarray]:
+ """
+ Returns the list of paths and coordinates from a cross-section (i.e. Trimesh Path2D). This is required to be
+ done for performing boolean operations and offsetting with the internal PyClipper package.
+ :param shapes: A list of :class:`shapely.geometry.Polygon` representing a cross-section or container of
+ closed polygons
+ :return: A list of paths (Numpy Coordinate Arrays) describing fully closed and oriented paths.
+ """
+ paths = []
+
+ for poly in shapes:
+ coords = np.array(poly.exterior.coords)
+ paths.append(coords)
+
+ for path in poly.interiors:
+ coords = np.array(path.coords)
+ paths.append(coords)
+
+ return paths
+
+
+def get_polypoints_from_edges(edges):
+ # print("START NEW POLYGON")
+ poly_points = []
+ # print("edges:", len(edges))
+ for edge in Part.__sortEdges__(edges):
+ if type(edge.Curve) is Part.Circle and edge.Closed:
+ # print("Edge is Circle")
+ c = edge.Curve
+ center = Point(c.Center.x, c.Center.y)
+ radius = c.Radius
+ circle = center.buffer(radius)
+ poly_points = circle.exterior.coords
+ elif type(edge.Curve) in [Part.Ellipse, Part.BSplineCurve, Part.Circle]:
+ # print("Edge is Ellipse, BSpline or unclosed Circle")
+ n = math.floor(edge.Length/2.3)
+ if n > 200:
+ n = 200
+ if edge.Closed:
+ # print("Edge is closed Ellipse or BSpline")
+ for v in edge.discretize(Number=n):
+ poly_points.append((v.x, v.y))
+ else:
+ # print("Edge is unclosed Circle")
+ # for Circles at least 64 steps for full circle
+ for v in edge.discretize(Number=n, First=edge.FirstParameter, Last=edge.LastParameter):
+ poly_points.append((v.x, v.y))
+ else:
+ # print("Line Vertexes:", len(edge.Vertexes))
+ # print("Line Vertexes:", [(v.Point.x, v.Point.y) for v in edge.Vertexes])
+ for v in edge.Vertexes:
+ poly_points.append((v.Point.x, v.Point.y))
+ u = np.unique(poly_points, axis=0)
+ # print("UNIQUE:", u)
+ # print("END NEW POLYGON")
+ return poly_points
+
+
+def tuple_is_equal(t1, t2):
+ if math.isclose(t1[0], t2[0]) and math.isclose(t1[1], t2[1]):
+ return True
+ return False
+
+
+def get_polygon_from_subshape(subshape):
+ # print("START NEW POLYGON")
+ polygon = []
+ for edge in Part.__sortEdges__(subshape.Edges):
+ poly_points = []
+ if type(edge.Curve) in [Part.Ellipse, Part.BSplineCurve, Part.Circle]:
+ n = math.floor(edge.Length/2.3)
+ for v in edge.discretize(Number=n, First=edge.FirstParameter, Last=edge.LastParameter):
+ poly_points.append((v.x, v.y))
+ else:
+ # print("Line Vertexes:", len(edge.Vertexes))
+ # print("Line Vertexes:", [(v.Point.x, v.Point.y) for v in edge.Vertexes])
+ for v in edge.Vertexes:
+ poly_points.append((v.Point.x, v.Point.y))
+
+ if len(polygon):
+ if tuple_is_equal(poly_points[0], polygon[-1]):
+ polygon.extend(poly_points[1:])
+ else:
+ polygon.extend(poly_points)
+ else:
+ polygon.extend(poly_points)
+ # print("END NEW POLYGON")
+ # return LinearRing(polygon)
+ return polygon
+
+
+def get_coords_from_shape(face):
+ outerpoly = get_polygon_from_subshape(face.OuterWire)
+ inner_polys = []
+ for inner_wire in face.SubShapes[1:]:
+ tmp = get_polygon_from_subshape(inner_wire)
+ inner_polys.append(tmp)
+ poly = Polygon(outerpoly, holes=inner_polys)
+ return path2DToPathList([poly])
+
+
+def create_hatch_lines(geoms):
+ hatchlines = []
+ for geom in geoms:
+ if geom.type() == LayerGeometryType.Hatch:
+ # print("Hatch with {} coords".format(len(geom.coords)))
+ hatches = np.vstack([geom.coords.reshape(-1, 2, 2)])
+ for line in hatches:
+ p0 = line[0]
+ p1 = line[1]
+ pp = Part.makeLine(App.Vector(p0[0], p0[1],0), App.Vector(p1[0],p1[1],0))
+ hatchlines.append(pp)
+ return hatchlines
+
+
+def create_contour_lines(geoms):
+ contours = []
+ for geom in geoms:
+ if geom.type() == LayerGeometryType.Polygon:
+ # print("Contour with {} coords".format(len(geom.coords)))
+ coords = rdp.rdp(geom.coords, epsilon=0.3, algo="iter", return_mask=False)
+ # print("Simplfied Poly:", len(coords))
+ pp = Part.makePolygon([App.Vector(x,y,0) for (x,y) in coords])
+ contours.append(pp)
+ return contours
diff --git a/lasercladding/__init__.py b/lasercladding/__init__.py
deleted file mode 100644
index 94f824b..0000000
--- a/lasercladding/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# module lasercladding
-
-from .commands import *
-from .path import *
-from .job import *
-from .kuka import *
diff --git a/lasercladding/commands.py b/lasercladding/commands.py
deleted file mode 100644
index 5d363cf..0000000
--- a/lasercladding/commands.py
+++ /dev/null
@@ -1,77 +0,0 @@
-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())
diff --git a/lasercladding/kuka.py b/lasercladding/kuka.py
deleted file mode 100644
index 40b24b0..0000000
--- a/lasercladding/kuka.py
+++ /dev/null
@@ -1,170 +0,0 @@
-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("E6POS startp\n")
- 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("BAS (#TOOL,6) ;Initialicing the defaults for Vel and so on \n\n")
- srcfile.write("BAS (#BASE,2) ;Initialicing the defaults for Vel and so on \n\n")
- srcfile.write("PTP {A1 -1.9, A2 -105.76, A3 79.97, A4 178.83, A5 -20.3, A6 -4.37, E1 -90, E2 0}\n")
- srcfile.write(";------------- main part ------------\n")
- srcfile.write("startp=$POS_ACT\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 startp:{} C_VEL; 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
diff --git a/lasercladding/pad.py b/lasercladding/pad.py
deleted file mode 100644
index d24347a..0000000
--- a/lasercladding/pad.py
+++ /dev/null
@@ -1,149 +0,0 @@
-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
-
-
diff --git a/lasercladding/path.py b/lasercladding/path.py
deleted file mode 100644
index b702db7..0000000
--- a/lasercladding/path.py
+++ /dev/null
@@ -1,16 +0,0 @@
-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")