From 9b23de480d9cafe7d85b47131040c0b3ec63a32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Kurlbaum?= Date: Mon, 19 Sep 2022 22:49:51 +0200 Subject: [PATCH] new style workbench with macros integrates --- .gitignore | 1 + InitGui.py | 47 - Resources/LaserCladding.qrc | 6 - .../Resources/LaserCladding.qrc | 11 + .../Resources/icons/LaserCreatePad.svg | 325 ++++++ .../Resources/icons/LaserCreateProg.svg | 362 ++++++ .../Resources/icons/LaserRecomputePad.svg | 362 ++++++ .../Resources/icons/LaserSaveProg.svg | 357 ++++++ .../Resources/icons/LaserSelectBaseRef.svg | 373 ++++++ .../Resources}/icons/Laser_Workbench.svg | 0 .../LaserCladdingWorkbench/__init__.py | 0 freecad/LaserCladdingWorkbench/commands.py | 170 +++ freecad/LaserCladdingWorkbench/init_gui.py | 63 + freecad/LaserCladdingWorkbench/kuka.py | 265 +++++ freecad/LaserCladdingWorkbench/lc_resource.py | 1029 +++++++++++++++++ freecad/LaserCladdingWorkbench/pad.py | 195 ++++ freecad/LaserCladdingWorkbench/path.py | 17 + .../LaserCladdingWorkbench/program.py | 74 +- freecad/LaserCladdingWorkbench/utils.py | 160 +++ lasercladding/__init__.py | 6 - lasercladding/commands.py | 77 -- lasercladding/kuka.py | 170 --- lasercladding/pad.py | 149 --- lasercladding/path.py | 16 - 24 files changed, 3736 insertions(+), 499 deletions(-) create mode 100644 .gitignore delete mode 100644 InitGui.py delete mode 100644 Resources/LaserCladding.qrc create mode 100644 freecad/LaserCladdingWorkbench/Resources/LaserCladding.qrc create mode 100644 freecad/LaserCladdingWorkbench/Resources/icons/LaserCreatePad.svg create mode 100644 freecad/LaserCladdingWorkbench/Resources/icons/LaserCreateProg.svg create mode 100644 freecad/LaserCladdingWorkbench/Resources/icons/LaserRecomputePad.svg create mode 100644 freecad/LaserCladdingWorkbench/Resources/icons/LaserSaveProg.svg create mode 100644 freecad/LaserCladdingWorkbench/Resources/icons/LaserSelectBaseRef.svg rename {Resources => freecad/LaserCladdingWorkbench/Resources}/icons/Laser_Workbench.svg (100%) rename Init.py => freecad/LaserCladdingWorkbench/__init__.py (100%) create mode 100644 freecad/LaserCladdingWorkbench/commands.py create mode 100644 freecad/LaserCladdingWorkbench/init_gui.py create mode 100644 freecad/LaserCladdingWorkbench/kuka.py create mode 100644 freecad/LaserCladdingWorkbench/lc_resource.py create mode 100644 freecad/LaserCladdingWorkbench/pad.py create mode 100644 freecad/LaserCladdingWorkbench/path.py rename lasercladding/job.py => freecad/LaserCladdingWorkbench/program.py (66%) create mode 100644 freecad/LaserCladdingWorkbench/utils.py delete mode 100644 lasercladding/__init__.py delete mode 100644 lasercladding/commands.py delete mode 100644 lasercladding/kuka.py delete mode 100644 lasercladding/pad.py delete mode 100644 lasercladding/path.py 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson + + + + + box + plane + rectangle + + + A box floating above a projection of its lower face + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson + + + + + box + plane + rectangle + + + A box floating above a projection of its lower face + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson + + + + + box + plane + rectangle + + + A box floating above a projection of its lower face + + + + + + + + + + + + R + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson + + + + + box + plane + rectangle + + + A box floating above a projection of its lower face + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_2DShapeView.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson + + + + + box + plane + rectangle + + + A box floating above a projection of its lower face + + + + + + (1,3,0) + 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")