├── sivicncdriver ├── ui │ ├── __init__.py │ ├── rc │ │ ├── run.png │ │ ├── up.png │ │ ├── .directory │ │ ├── close.png │ │ ├── down.png │ │ ├── left.png │ │ ├── load.png │ │ ├── origin.png │ │ ├── reload.png │ │ ├── right.png │ │ ├── save.png │ │ ├── upload.png │ │ ├── work.png │ │ ├── connect.png │ │ ├── siviIcon.png │ │ ├── warning.png │ │ └── writing.png │ ├── translate │ │ ├── SiviCNCDriver_fr_FR.qm │ │ └── SiviCNCDriver_fr_FR.ts │ ├── ui.pro │ ├── ressources.qrc │ ├── preprocessor_window.ui │ ├── preprocessor_window.py │ ├── preprocessor.py │ ├── view3d.py │ └── interface.py ├── gcode │ ├── __init__.py │ ├── arc_calculator.py │ ├── gcode.py │ └── gcode_maker.py ├── serial │ ├── __init__.py │ ├── serial_list.py │ ├── thread_read.py │ ├── thread_send.py │ └── serial_manager.py ├── __main__.py ├── configs │ └── example.json ├── __init__.py ├── license_dialog_text ├── app.py ├── settings.py └── gcodes │ ├── hello_world.ngc │ └── gear1.ngc ├── run.py ├── icon.ico ├── docs ├── images │ ├── configuration.png │ ├── gcode_viewer.png │ └── send_command.png ├── modules.rst ├── index.rst ├── sivicncdriver.rst ├── sivicncdriver.gcode.rst ├── make.bat ├── sivicncdriver.serial.rst ├── sivicncdriver.ui.rst ├── conf.py └── Getting started.rst ├── MANIFEST.in ├── requirements.txt ├── make_ui.sh ├── setup_cx_freeze.py ├── setup.py ├── ino_setup.iss ├── .gitignore └── README.rst /sivicncdriver/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sivicncdriver/gcode/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sivicncdriver/serial/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from sivicncdriver.app import main 2 | 3 | main() -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/icon.ico -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/run.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/up.png -------------------------------------------------------------------------------- /docs/images/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/docs/images/configuration.png -------------------------------------------------------------------------------- /docs/images/gcode_viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/docs/images/gcode_viewer.png -------------------------------------------------------------------------------- /docs/images/send_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/docs/images/send_command.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | PreviewsShown=true 3 | Timestamp=2017,7,31,23,29,32 4 | Version=4 5 | -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/close.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/down.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/left.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/load.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/origin.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/reload.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/right.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/save.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/upload.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/work.png -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | sivicncdriver 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | sivicncdriver 8 | -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/connect.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/siviIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/siviIcon.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/warning.png -------------------------------------------------------------------------------- /sivicncdriver/ui/rc/writing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/rc/writing.png -------------------------------------------------------------------------------- /sivicncdriver/ui/translate/SiviCNCDriver_fr_FR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klafyvel/SiviCNCDriver/HEAD/sivicncdriver/ui/translate/SiviCNCDriver_fr_FR.qm -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | include LICENSE 3 | include requirements.txt 4 | recursive-include sivicncdriver/configs example.json 5 | recursive-include sivicncdriver/ui/translate *.qm -------------------------------------------------------------------------------- /sivicncdriver/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the __main__ module of the sivicncdriver package. 3 | """ 4 | from sivicncdriver.app import main 5 | 6 | if __name__ == '__main__': 7 | main() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | matplotlib==2.0.2 3 | numpy==1.22.0 4 | pyparsing==2.2.0 5 | PyQt5==5.9 6 | pyserial==3.4 7 | python-dateutil==2.6.1 8 | pytz==2017.2 9 | sip==4.19.3 10 | six==1.10.0 11 | -------------------------------------------------------------------------------- /make_ui.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd sivicncdriver/ui 4 | pyrcc5 ressources.qrc -o ressources_rc.py 5 | pyuic5 preprocessor_window.ui --import-from=sivicncdriver.ui -o preprocessor_window.py 6 | pyuic5 main_window.ui --import-from=sivicncdriver.ui -o main_window.py -------------------------------------------------------------------------------- /sivicncdriver/configs/example.json: -------------------------------------------------------------------------------- 1 | {"x_drive": 2, "x_ratio": 11.1111, "x_play": 0.0, "x_reverse": false, "x_min_time": 32, "y_drive": 2, "y_ratio": 10.101, "y_play": 0.0, "y_reverse": false, "y_min_time": 32, "z_drive": 1, "z_ratio": 177.7777, "z_play": 0.0, "z_reverse": false, "z_min_time": 8} -------------------------------------------------------------------------------- /sivicncdriver/ui/ui.pro: -------------------------------------------------------------------------------- 1 | SOURCES += \ 2 | interface.py \ 3 | main_window.py \ 4 | preprocessor.py \ 5 | preprocessor_window.py \ 6 | ressources_rc.py \ 7 | view3d.py 8 | 9 | TRANSLATIONS += translate/SiviCNCDriver_fr_FR.ts 10 | 11 | FORMS += \ 12 | main_window.ui \ 13 | preprocessor_window.ui 14 | 15 | RESOURCES += \ 16 | ressources.qrc 17 | 18 | DISTFILES += 19 | -------------------------------------------------------------------------------- /sivicncdriver/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The SiviCNCDriver Package 3 | ========================= 4 | 5 | This is the SiviCNCDriver package. To run the application directly you should 6 | use the ``sivicnc`` command in a shell. Alternately you can use the ``main`` 7 | function of the package which doesn't take any parameter. 8 | 9 | 10 | 11 | :Example: 12 | 13 | >>> from sivicncdriver.app import main 14 | >>> main() 15 | 16 | """ 17 | 18 | __version__ = "0.1.9" 19 | __all__ = ['app'] 20 | -------------------------------------------------------------------------------- /setup_cx_freeze.py: -------------------------------------------------------------------------------- 1 | from cx_Freeze import setup, Executable 2 | import sivicncdriver 3 | 4 | # On appelle la fonction setup 5 | addtional_mods = ['numpy.core._methods', 'numpy.lib.format'] 6 | setup( 7 | name = "SiviCNCDriver", 8 | version = sivicncdriver.__version__, 9 | description = 'A software to control my CNC', 10 | executables = [Executable("siviCNCDriver.py")], 11 | options = {'build_exe': { 12 | 'includes': addtional_mods, 13 | 'namespace_packages' : ['mpl_toolkits'], 14 | }}, 15 | ) -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. SiviCNCDriver documentation master file, created by 2 | sphinx-quickstart on Sun Aug 13 23:20:38 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to SiviCNCDriver's documentation! 7 | ========================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | Getting started 14 | Code documentation 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /sivicncdriver/license_dialog_text: -------------------------------------------------------------------------------- 1 | SiviCNCDriver 2 | Copyright (C) 2017 Hugo LEVY-FALK 3 | 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 7 | 8 | You should have received a copy of the GNU General Public License along with this program. If not, see . -------------------------------------------------------------------------------- /sivicncdriver/ui/ressources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | rc/close.png 4 | rc/writing.png 5 | rc/connect.png 6 | rc/down.png 7 | rc/left.png 8 | rc/load.png 9 | rc/origin.png 10 | rc/reload.png 11 | rc/right.png 12 | rc/run.png 13 | rc/save.png 14 | rc/siviIcon.png 15 | rc/up.png 16 | rc/upload.png 17 | rc/work.png 18 | rc/warning.png 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/sivicncdriver.rst: -------------------------------------------------------------------------------- 1 | sivicncdriver package 2 | ===================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | sivicncdriver.gcode 10 | sivicncdriver.serial 11 | sivicncdriver.ui 12 | 13 | Submodules 14 | ---------- 15 | 16 | sivicncdriver\.app module 17 | ------------------------- 18 | 19 | .. automodule:: sivicncdriver.app 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | sivicncdriver\.settings module 25 | ------------------------------ 26 | 27 | .. automodule:: sivicncdriver.settings 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | 33 | Module contents 34 | --------------- 35 | 36 | .. automodule:: sivicncdriver 37 | :members: 38 | :undoc-members: 39 | :show-inheritance: 40 | -------------------------------------------------------------------------------- /sivicncdriver/app.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | from PyQt5.QtWidgets import QApplication 5 | from PyQt5.QtCore import * 6 | 7 | from sivicncdriver import settings 8 | from sivicncdriver.ui.interface import MainWindow 9 | 10 | 11 | def main(): 12 | """ 13 | The main function of the application. 14 | 15 | It will create a QApplication and a main window then run the application and 16 | exit. 17 | """ 18 | app = QApplication(sys.argv) 19 | 20 | qtTranslator = QTranslator() 21 | translate_path = os.path.join( 22 | settings.TRANSLATE_DIR, 23 | "SiviCNCDriver_" + QLocale.system().name() 24 | ) 25 | qtTranslator.load(translate_path) 26 | app.installTranslator(qtTranslator) 27 | 28 | window = MainWindow() 29 | window.show() 30 | sys.exit(app.exec()) 31 | -------------------------------------------------------------------------------- /docs/sivicncdriver.gcode.rst: -------------------------------------------------------------------------------- 1 | sivicncdriver\.gcode package 2 | ============================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | sivicncdriver\.gcode\.arc\_calculator module 8 | -------------------------------------------- 9 | 10 | .. automodule:: sivicncdriver.gcode.arc_calculator 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | sivicncdriver\.gcode\.gcode module 16 | ---------------------------------- 17 | 18 | .. automodule:: sivicncdriver.gcode.gcode 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | sivicncdriver\.gcode\.gcode\_maker module 24 | ----------------------------------------- 25 | 26 | .. automodule:: sivicncdriver.gcode.gcode_maker 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: sivicncdriver.gcode 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=SiviCNCDriver 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /sivicncdriver/serial/serial_list.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import glob 3 | import serial 4 | 5 | 6 | def serial_ports(): 7 | """Lists serial ports 8 | 9 | :raises EnvironmentError: 10 | On unsupported or unknown platforms 11 | :returns: 12 | A list of available serial ports 13 | """ 14 | if sys.platform.startswith('win'): 15 | ports = ['COM' + str(i + 1) for i in range(256)] 16 | 17 | elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): 18 | # this is to exclude your current terminal "/dev/tty" 19 | ports = glob.glob('/dev/tty[A-Za-z]*') 20 | 21 | elif sys.platform.startswith('darwin'): 22 | ports = glob.glob('/dev/tty.*') 23 | 24 | else: 25 | raise EnvironmentError('Unsupported platform') 26 | 27 | result = [] 28 | for port in ports: 29 | try: 30 | s = serial.Serial(port) 31 | s.close() 32 | result.append(port) 33 | except (OSError, serial.SerialException): 34 | pass 35 | return result 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sivicncdriver 3 | 4 | setup(name='SiviCNCDriver', 5 | version=sivicncdriver.__version__, 6 | description='A software to control my CNC', 7 | long_description=open('README.rst').read(), 8 | url='http://github.com/klafyvel/SiviCNCDriver', 9 | author='klafyvel', 10 | author_email="sivigik@gmail.com", 11 | license='GPL', 12 | include_package_data=True, 13 | packages=find_packages(), 14 | install_requires= open('requirements.txt').read().split('\n'), 15 | classifier=[ 16 | 'Development Status :: 4 - Beta', 17 | 'Intended Audience :: Manufacturing', 18 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 19 | 'Natural Language :: French', 20 | 'Operating System :: POSIX :: Linux', 21 | 'Programming Language :: Python :: 3.6', 22 | 'Programming Language :: Python :: 3', 23 | 'Topic :: Utilities', 24 | ], 25 | entry_points = { 26 | 'gui_scripts': [ 27 | 'sivicnc = sivicncdriver.__main__:main', 28 | ], 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /docs/sivicncdriver.serial.rst: -------------------------------------------------------------------------------- 1 | sivicncdriver\.serial package 2 | ============================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | sivicncdriver\.serial\.serial\_list module 8 | ------------------------------------------ 9 | 10 | .. automodule:: sivicncdriver.serial.serial_list 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | sivicncdriver\.serial\.serial\_manager module 16 | --------------------------------------------- 17 | 18 | .. automodule:: sivicncdriver.serial.serial_manager 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | sivicncdriver\.serial\.thread\_read module 24 | ------------------------------------------ 25 | 26 | .. automodule:: sivicncdriver.serial.thread_read 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | sivicncdriver\.serial\.thread\_send module 32 | ------------------------------------------ 33 | 34 | .. automodule:: sivicncdriver.serial.thread_send 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: sivicncdriver.serial 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /sivicncdriver/serial/thread_read.py: -------------------------------------------------------------------------------- 1 | import serial 2 | 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtGui import * 5 | from PyQt5 import QtGui 6 | from PyQt5.QtWidgets import * 7 | 8 | from sivicncdriver import settings 9 | from sivicncdriver.settings import logger 10 | 11 | 12 | class ReadThread(QThread): 13 | """ 14 | A thread to read the serial link. 15 | """ 16 | 17 | read = pyqtSignal() 18 | 19 | def __init__(self): 20 | """ 21 | The __init__ method. 22 | """ 23 | QThread.__init__(self) 24 | self.user_stop = False 25 | self.read_allowed = True 26 | 27 | def __del__(self): 28 | self.wait() 29 | 30 | @pyqtSlot(bool) 31 | def set_read_allowed(self, st): 32 | """ 33 | Allows or not the thread to read. 34 | 35 | :param st: Is it allowed ? 36 | """ 37 | logger.debug("Set reading allowed to {}".format(st)) 38 | self.read_allowed = st 39 | 40 | @pyqtSlot() 41 | def stop(self): 42 | """ 43 | A simple slot to tell the thread to stop. 44 | """ 45 | self.user_stop = True 46 | 47 | def run(self): 48 | """ 49 | Runs the thread. 50 | 51 | The commands are sent using the serial manager. If an error occurs or if 52 | the thread is stopped by the user, then it quits. 53 | """ 54 | while not self.user_stop: 55 | if self.read_allowed: 56 | self.read.emit() 57 | self.msleep(50) 58 | -------------------------------------------------------------------------------- /docs/sivicncdriver.ui.rst: -------------------------------------------------------------------------------- 1 | sivicncdriver\.ui package 2 | ========================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | sivicncdriver\.ui\.interface module 8 | ----------------------------------- 9 | 10 | .. automodule:: sivicncdriver.ui.interface 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | sivicncdriver\.ui\.main\_window module 16 | -------------------------------------- 17 | 18 | .. automodule:: sivicncdriver.ui.main_window 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | sivicncdriver\.ui\.preprocessor module 24 | -------------------------------------- 25 | 26 | .. automodule:: sivicncdriver.ui.preprocessor 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | sivicncdriver\.ui\.preprocessor\_window module 32 | ---------------------------------------------- 33 | 34 | .. automodule:: sivicncdriver.ui.preprocessor_window 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | sivicncdriver\.ui\.ressources\_rc module 40 | ---------------------------------------- 41 | 42 | .. automodule:: sivicncdriver.ui.ressources_rc 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | sivicncdriver\.ui\.view3d module 48 | -------------------------------- 49 | 50 | .. automodule:: sivicncdriver.ui.view3d 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | Module contents 57 | --------------- 58 | 59 | .. automodule:: sivicncdriver.ui 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | -------------------------------------------------------------------------------- /sivicncdriver/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | from pathlib import Path 5 | import shutil 6 | 7 | from logging.handlers import RotatingFileHandler 8 | 9 | 10 | APP_DIR = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | DEBUG = False 13 | 14 | TRANSLATE_DIR = os.path.join(APP_DIR, "ui", "translate") 15 | 16 | if DEBUG: 17 | DATA_DIR = APP_DIR 18 | else: 19 | DATA_DIR = os.path.join(str(Path.home()), "SiviCNCDriver", "") 20 | if not os.path.exists(DATA_DIR): 21 | os.makedirs(DATA_DIR) 22 | 23 | if DEBUG: 24 | CONFIG_DIR = os.path.join(APP_DIR, "configs", "") 25 | else: 26 | CONFIG_DIR = os.path.join(DATA_DIR, "configs", "") 27 | if not os.path.exists(CONFIG_DIR): 28 | shutil.copytree(os.path.join(APP_DIR, "configs", ""), CONFIG_DIR) 29 | 30 | if DEBUG: 31 | FILE_DIR = os.path.join(APP_DIR, 'gcodes', '') 32 | else: 33 | FILE_DIR = str(Path.home()) 34 | 35 | # Logger stuff 36 | logger = logging.getLogger() 37 | 38 | if DEBUG: 39 | logger.setLevel(logging.DEBUG) 40 | else: 41 | logger.setLevel(logging.INFO) 42 | 43 | formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(message)s') 44 | 45 | # File log 46 | 47 | file_handler = RotatingFileHandler( 48 | os.path.join(DATA_DIR, 'log.txt'), 'a', 1000000, 1) 49 | 50 | 51 | if DEBUG: 52 | file_handler.setLevel(logging.DEBUG) 53 | else: 54 | file_handler.setLevel(logging.INFO) 55 | 56 | file_handler.setFormatter(formatter) 57 | logger.addHandler(file_handler) 58 | 59 | if DEBUG: 60 | stream_handler = logging.StreamHandler() 61 | stream_handler.setLevel(logging.DEBUG) 62 | stream_handler.setFormatter(formatter) 63 | logger.addHandler(stream_handler) 64 | -------------------------------------------------------------------------------- /ino_setup.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "SiviCNCDriver" 5 | #define MyAppVersion "0.1.2" 6 | #define MyAppPublisher "Sivigik" 7 | #define MyAppURL "http://sivigik.com/" 8 | #define MyAppExeName "siviCNCDriver.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. 12 | ; Do not use the same AppId value in installers for other applications. 13 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 14 | AppId={{693E75BE-4908-4D1D-88E8-4E2A56766A13} 15 | AppName={#MyAppName} 16 | AppVersion={#MyAppVersion} 17 | ;AppVerName={#MyAppName} {#MyAppVersion} 18 | AppPublisher={#MyAppPublisher} 19 | AppPublisherURL={#MyAppURL} 20 | AppSupportURL={#MyAppURL} 21 | AppUpdatesURL={#MyAppURL} 22 | DefaultDirName={pf}\{#MyAppName} 23 | DisableProgramGroupPage=yes 24 | LicenseFile=LICENSE 25 | OutputDir=build 26 | OutputBaseFilename=setupSiviCNCDriver 27 | SetupIconFile=icon.ico 28 | Compression=lzma 29 | SolidCompression=yes 30 | 31 | [Languages] 32 | Name: "english"; MessagesFile: "compiler:Default.isl" 33 | Name: "french"; MessagesFile: "compiler:Languages\French.isl" 34 | 35 | [Tasks] 36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 37 | 38 | [Files] 39 | Source: "build\exe.win32-3.6\siviCNCDriver.exe"; DestDir: "{app}"; Flags: ignoreversion 40 | Source: "build\exe.win32-3.6\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 41 | Source: "LICENSE"; DestDir: "{app}"; Flags: ignoreversion 42 | Source: "icon.ico"; DestDir: "{app}"; Flags: ignoreversion 43 | Source: "README.rst"; DestDir: "{app}"; Flags: ignoreversion isreadme 44 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 45 | 46 | [Icons] 47 | Name: "{commonprograms}\{#MyAppName}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";IconFilename: "{app}\icon.ico" 48 | Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";IconFilename: "{app}\icon.ico"; Tasks: desktopicon 49 | Name: "{commonprograms}\{#MyAppName}\Uninstall SiviCNCDriver"; Filename: "{uninstallexe}" 50 | 51 | [Run] 52 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 53 | 54 | -------------------------------------------------------------------------------- /sivicncdriver/serial/thread_send.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import * 2 | from PyQt5.QtGui import * 3 | from PyQt5 import QtGui 4 | from PyQt5.QtWidgets import * 5 | 6 | from sivicncdriver import settings 7 | from sivicncdriver.settings import logger 8 | 9 | 10 | class SendThread(QThread): 11 | """ 12 | A thread to send a list of instructions without blocking the main thread. 13 | """ 14 | update_progress = pyqtSignal(int) 15 | read_allowed = pyqtSignal(bool) 16 | 17 | def __init__(self, serial_manager, gcode): 18 | """ 19 | The __init__ method. 20 | 21 | :param serial_manager: The main window's serial manager 22 | :param gcode: An iterable of gcode instructions 23 | :type serial_manager: SerialManager 24 | :type gcode: iterable 25 | """ 26 | QThread.__init__(self) 27 | self.gcode = gcode 28 | self.serial_manager = serial_manager 29 | self.user_stop = False 30 | self.error = False 31 | self.confirmed = True 32 | 33 | def __del__(self): 34 | self.wait() 35 | 36 | @pyqtSlot() 37 | def stop(self): 38 | """ 39 | A simple slot to tell the thread to stop. 40 | """ 41 | self.user_stop = True 42 | 43 | @pyqtSlot(bool) 44 | def confirm(self, st): 45 | """ 46 | Receive confirmation from the readThread. 47 | 48 | :param st: Everything ok ? 49 | """ 50 | self.error = not st 51 | self.confirmed = True 52 | logger.debug("Confirmation : {}.".format(st)) 53 | 54 | def run(self): 55 | """ 56 | Runs the thread. 57 | 58 | The commands are sent using the serial manager. If an error occurs or if 59 | the thread is stopped by the user, then it quits. 60 | """ 61 | n = 0 62 | gen = (i for i in self.gcode) 63 | try: 64 | while not self.error and not self.user_stop: 65 | if self.confirmed: 66 | l = next(gen) 67 | self.read_allowed.emit(False) 68 | if self.serial_manager.sendMsg(l): 69 | self.update_progress.emit(n) 70 | self.confirmed = False 71 | self.read_allowed.emit(True) 72 | else: 73 | self.error = True 74 | self.read_allowed.emit(True) 75 | n += 1 76 | except StopIteration: 77 | pass 78 | -------------------------------------------------------------------------------- /sivicncdriver/gcode/arc_calculator.py: -------------------------------------------------------------------------------- 1 | """ 2 | The arc_calculator module 3 | ========================= 4 | 5 | It creates small segments from an arc given with G-codes parameters : origin, 6 | end, path to center and sense of rotation. 7 | 8 | :Example: 9 | 10 | >>> from arc_calculator import arc_to_segments 11 | >>> a = arc_to_segments((0,0),(5,0),(10,0)) 12 | >>> for x,y in a: 13 | ... print((x,y)) 14 | ... 15 | (0.0, 6.12323e-16) 16 | (0.09966, -0.993334) 17 | (0.39469, -1.94708) 18 | (0.87331, -2.8232) 19 | (1.51646, -3.58677) 20 | (2.29848, -4.20735) 21 | (3.1882, -4.66019) 22 | (4.15015, -4.92725) 23 | (5.14598, -4.99787) 24 | (6.136, -4.86924) 25 | (7.08072, -4.54649) 26 | (7.94249, -4.04249) 27 | (8.68696, -3.37733) 28 | (9.28444, -2.57752) 29 | (9.71111, -1.67495) 30 | (10, 0) 31 | 32 | """ 33 | from math import atan2, cos, sin, sqrt, pi 34 | from decimal import * 35 | 36 | from sivicncdriver.settings import logger 37 | 38 | 39 | getcontext().prec = 6 40 | 41 | 42 | def arc_to_segments(start, vect_to_center, end, clockwise=False, length=1,): 43 | """ 44 | Creates small segments from an arc. 45 | 46 | Uses Decimal for better precision. It yields the vertices. 47 | 48 | :param start: The starting position 49 | :param vect_to_center: A vector to go to the center of the arc from the 50 | starting position 51 | :param end: The ending position 52 | :param clockwise: Should it go clockwise ? 53 | :param length: length of the segments 54 | :type start: A float tuple 55 | :type vect_to_center: A float tuple 56 | :type end: A float tuple 57 | :type clockwise: bool 58 | :type length: float 59 | :return: None, it yields vertices. 60 | """ 61 | 62 | v = Decimal(vect_to_center[0]), Decimal(vect_to_center[1]) 63 | e = Decimal(end[0]), Decimal(end[1]) 64 | s = Decimal(start[0]), Decimal(start[1]) 65 | radius = (v[0]**2 + v[1]**2).sqrt() 66 | start_angle = Decimal(atan2(-v[1], -v[0])) % Decimal(2*pi) 67 | center = (s[0]+v[0], s[1]+v[1]) 68 | end_angle = Decimal(atan2(e[1]-center[1], e[0]-center[0])) 69 | 70 | if clockwise: 71 | arc_length = -Decimal(2*pi) + (end_angle - start_angle) 72 | else: 73 | arc_length = (end_angle - start_angle) 74 | 75 | arc_length = arc_length % Decimal(2*pi) 76 | if arc_length < 0 and not clockwise: 77 | arc_length += Decimal(2*pi) 78 | elif arc_length > 0 and clockwise: 79 | arc_length -= Decimal(2*pi) 80 | nb_step = int(abs(arc_length*radius/Decimal(length))) 81 | 82 | if abs(arc_length * radius) < length: 83 | yield start 84 | yield end 85 | else: 86 | d_angle = arc_length / nb_step 87 | angle = start_angle 88 | for _ in range(nb_step): 89 | yield ( 90 | float(center[0] + radius*Decimal(cos(angle))), 91 | float(center[1] + radius*Decimal(sin(angle))) 92 | ) 93 | angle += d_angle 94 | yield end 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log.txt* 2 | 3 | # Created by https://www.gitignore.io/api/qt,python,sublimetext 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | docs/_build 72 | docs/_static 73 | docs/_templates 74 | 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # dotenv 92 | .env 93 | 94 | # virtualenv 95 | .venv 96 | venv/ 97 | ENV/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | ### Qt ### 110 | # C++ objects and libs 111 | 112 | *.slo 113 | *.lo 114 | *.o 115 | *.a 116 | *.la 117 | *.lai 118 | *.dll 119 | *.dylib 120 | 121 | # Qt-es 122 | 123 | /.qmake.cache 124 | /.qmake.stash 125 | *.pro.user 126 | *.pro.user.* 127 | *.qbs.user 128 | *.qbs.user.* 129 | *.moc 130 | moc_*.cpp 131 | moc_*.h 132 | qrc_*.cpp 133 | ui_*.h 134 | Makefile* 135 | *build-* 136 | 137 | # QtCreator 138 | 139 | *.autosave 140 | 141 | # QtCtreator Qml 142 | *.qmlproject.user 143 | *.qmlproject.user.* 144 | 145 | # QtCtreator CMake 146 | CMakeLists.txt.user* 147 | 148 | 149 | ### SublimeText ### 150 | # cache files for sublime text 151 | *.tmlanguage.cache 152 | *.tmPreferences.cache 153 | *.stTheme.cache 154 | 155 | # workspace files are user-specific 156 | *.sublime-workspace 157 | 158 | # project files should be checked into the repository, unless a significant 159 | # proportion of contributors will probably not be using SublimeText 160 | # *.sublime-project 161 | 162 | # sftp configuration file 163 | sftp-config.json 164 | 165 | # Package control specific files 166 | Package Control.last-run 167 | Package Control.ca-list 168 | Package Control.ca-bundle 169 | Package Control.system-ca-bundle 170 | Package Control.cache/ 171 | Package Control.ca-certs/ 172 | Package Control.merged-ca-bundle 173 | Package Control.user-ca-bundle 174 | oscrypto-ca-bundle.crt 175 | bh_unicode_properties.cache 176 | 177 | # Sublime-github package stores a github token in this file 178 | # https://packagecontrol.io/packages/sublime-github 179 | GitHub.sublime-settings 180 | 181 | # End of https://www.gitignore.io/api/qt,python,sublimetext 182 | 183 | ### Vim ### 184 | # swap 185 | [._]*.s[a-v][a-z] 186 | [._]*.sw[a-p] 187 | [._]s[a-v][a-z] 188 | [._]sw[a-p] 189 | # session 190 | Session.vim 191 | # temporary 192 | .netrwhist 193 | *~ 194 | # auto-generated tag files 195 | tags 196 | 197 | -------------------------------------------------------------------------------- /sivicncdriver/ui/preprocessor_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 561 10 | 298 11 | 12 | 13 | 14 | Preprocessor 15 | 16 | 17 | 18 | traceIcon.svgtraceIcon.svg 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Parameters 34 | 35 | 36 | 37 | 38 | 39 | Remove numbering (NXX) 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | 49 | Remove comments 50 | 51 | 52 | true 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Minimize bounding box 63 | 64 | 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Run preprocessor 76 | 77 | 78 | 79 | :/rc/rc/work.png:/rc/rc/work.png 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Output 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 105 | 106 | 107 | false 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /sivicncdriver/gcode/gcode.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module to parse G-codes. 3 | """ 4 | 5 | from sivicncdriver.settings import logger 6 | 7 | __all__ = ['parse'] 8 | 9 | 10 | class Stack: 11 | """ 12 | A Simple LIFO. 13 | """ 14 | 15 | def __init__(self, in_str): 16 | """ 17 | __init__ method. 18 | 19 | :param in_str: The string which is to be stacked. 20 | :type in_str: str 21 | """ 22 | self.in_str = in_str 23 | self.next = None 24 | 25 | def __str__(self): 26 | return self.in_str 27 | 28 | def token(self): 29 | """ 30 | Yields the elements of the stack. 31 | """ 32 | while len(self.in_str) > 0: 33 | yield self.pop() 34 | 35 | def pop(self): 36 | """ 37 | Pop an element. 38 | 39 | :return: The first element of the string. 40 | :rtype: str 41 | """ 42 | r = self.peek() 43 | self.next = None 44 | return r 45 | 46 | def peek(self): 47 | """ 48 | Peek the next element to be popped without popping it. 49 | 50 | :return: The next element 51 | :rtype: str 52 | """ 53 | if not self.next: 54 | self.next = self.in_str[0] 55 | self.in_str = self.in_str[1:] 56 | return self.next 57 | 58 | def is_empty(self): 59 | """ 60 | Returns True if the stack is empty. 61 | """ 62 | return len(self.in_str) <= 0 63 | 64 | SEPARATOR = ('\n', ' ', '(', '=') 65 | 66 | 67 | def parse_iterator(gcode): 68 | """ 69 | Function used by the ``parse`` function. 70 | 71 | :param gcode: The gcode which is to be parsed. 72 | :type gcode: str 73 | """ 74 | stack = Stack(gcode + '\n') 75 | line = 0 76 | for c in stack.token(): 77 | if c in ' %': 78 | continue 79 | elif c is '(': 80 | com = '' 81 | while stack.peek() is not ')': 82 | com += stack.pop() 83 | stack.pop() # removes the ')' 84 | yield ('__comment__', com, line) 85 | elif c is '\n': 86 | yield ('__next__', '__next__', line) 87 | line += 1 88 | else: 89 | try: 90 | yield (c, parse_value(stack), line) 91 | except ValueError: 92 | yield ('__error__', c, line) 93 | 94 | 95 | def parse(gcode): 96 | """ 97 | Parse gcode. 98 | 99 | It yields a dict for each line with : 100 | 101 | name 102 | name of the code (G, M) 103 | value 104 | an integer 105 | args 106 | a dict with the code arguments, ex : {'Y':3.0} 107 | 108 | :param gcode: The gcode which is to be parsed. 109 | :type gcode: str 110 | """ 111 | name = "" 112 | value = 0 113 | args = {} 114 | for i in parse_iterator(gcode): 115 | if i[0] == '__error__': 116 | yield {'name': '__error__', 'line': i[2], 'value': i[1]} 117 | elif not i[0] == '__next__': 118 | if i[0] == '__new_var__': 119 | name = '' 120 | value = None 121 | for a in i[1].keys(): 122 | OpNode.var[a] = i[1][a] 123 | yield { 124 | 'name': '__new_var__', 125 | 'var': a, 126 | 'value': i[1][a], 127 | 'line': i[2] 128 | } 129 | if i[0] in ('G', 'M'): 130 | name = i[0] 131 | value = i[1] 132 | elif i[0] in 'XYZIJKN': 133 | args[i[0]] = i[1] 134 | elif i[0] == '__comment__': 135 | if name is "": 136 | name = 'comment' 137 | args['comment'] = i[1] 138 | elif name is not "": 139 | yield {'name': name, 'value': value, 'args': args, 'line': i[2]} 140 | name = "" 141 | value = 0 142 | args = {} 143 | yield {'name': name, 'value': value, 'args': args, 'line': i[2]} 144 | 145 | 146 | def parse_value(stack): 147 | """ 148 | Parse a value in the stack. 149 | :param stack: The parser's stack. 150 | :return: The value. 151 | """ 152 | r = '' 153 | while not (stack.is_empty() or (stack.peek() in SEPARATOR and r)): 154 | r += stack.pop() 155 | return float(r) 156 | -------------------------------------------------------------------------------- /sivicncdriver/ui/preprocessor_window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'preprocessor_window.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.9 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | class Ui_dialog(object): 12 | def setupUi(self, dialog): 13 | dialog.setObjectName("dialog") 14 | dialog.resize(561, 298) 15 | icon = QtGui.QIcon() 16 | icon.addPixmap(QtGui.QPixmap("traceIcon.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 17 | dialog.setWindowIcon(icon) 18 | dialog.setModal(True) 19 | self.verticalLayout_4 = QtWidgets.QVBoxLayout(dialog) 20 | self.verticalLayout_4.setObjectName("verticalLayout_4") 21 | self.widget_2 = QtWidgets.QWidget(dialog) 22 | self.widget_2.setObjectName("widget_2") 23 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_2) 24 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 25 | self.horizontalLayout.setObjectName("horizontalLayout") 26 | self.widget = QtWidgets.QWidget(self.widget_2) 27 | self.widget.setObjectName("widget") 28 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget) 29 | self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) 30 | self.verticalLayout_3.setObjectName("verticalLayout_3") 31 | self.groupBox = QtWidgets.QGroupBox(self.widget) 32 | self.groupBox.setObjectName("groupBox") 33 | self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox) 34 | self.verticalLayout.setObjectName("verticalLayout") 35 | self.chk_del_num = QtWidgets.QCheckBox(self.groupBox) 36 | self.chk_del_num.setChecked(True) 37 | self.chk_del_num.setObjectName("chk_del_num") 38 | self.verticalLayout.addWidget(self.chk_del_num) 39 | self.chk_del_comments = QtWidgets.QCheckBox(self.groupBox) 40 | self.chk_del_comments.setChecked(True) 41 | self.chk_del_comments.setObjectName("chk_del_comments") 42 | self.verticalLayout.addWidget(self.chk_del_comments) 43 | self.chk_optimize_bounding_box = QtWidgets.QCheckBox(self.groupBox) 44 | self.chk_optimize_bounding_box.setToolTip("") 45 | self.chk_optimize_bounding_box.setChecked(True) 46 | self.chk_optimize_bounding_box.setObjectName("chk_optimize_bounding_box") 47 | self.verticalLayout.addWidget(self.chk_optimize_bounding_box) 48 | self.verticalLayout_3.addWidget(self.groupBox) 49 | self.btn_run_preproc = QtWidgets.QPushButton(self.widget) 50 | icon1 = QtGui.QIcon() 51 | icon1.addPixmap(QtGui.QPixmap(":/rc/rc/work.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 52 | self.btn_run_preproc.setIcon(icon1) 53 | self.btn_run_preproc.setObjectName("btn_run_preproc") 54 | self.verticalLayout_3.addWidget(self.btn_run_preproc) 55 | self.horizontalLayout.addWidget(self.widget) 56 | self.groupBox_2 = QtWidgets.QGroupBox(self.widget_2) 57 | self.groupBox_2.setObjectName("groupBox_2") 58 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) 59 | self.verticalLayout_2.setObjectName("verticalLayout_2") 60 | self.output = QtWidgets.QTextEdit(self.groupBox_2) 61 | self.output.setObjectName("output") 62 | self.verticalLayout_2.addWidget(self.output) 63 | self.horizontalLayout.addWidget(self.groupBox_2) 64 | self.verticalLayout_4.addWidget(self.widget_2) 65 | self.buttonBox = QtWidgets.QDialogButtonBox(dialog) 66 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) 67 | self.buttonBox.setCenterButtons(False) 68 | self.buttonBox.setObjectName("buttonBox") 69 | self.verticalLayout_4.addWidget(self.buttonBox) 70 | 71 | self.retranslateUi(dialog) 72 | QtCore.QMetaObject.connectSlotsByName(dialog) 73 | 74 | def retranslateUi(self, dialog): 75 | _translate = QtCore.QCoreApplication.translate 76 | dialog.setWindowTitle(_translate("dialog", "Preprocessor")) 77 | self.groupBox.setTitle(_translate("dialog", "Parameters")) 78 | self.chk_del_num.setText(_translate("dialog", "Remove numbering (NXX)")) 79 | self.chk_del_comments.setText(_translate("dialog", "Remove comments")) 80 | self.chk_optimize_bounding_box.setText(_translate("dialog", "Minimize bounding box")) 81 | self.btn_run_preproc.setText(_translate("dialog", "Run preprocessor")) 82 | self.groupBox_2.setTitle(_translate("dialog", "Output")) 83 | 84 | from sivicncdriver.ui import ressources_rc 85 | -------------------------------------------------------------------------------- /sivicncdriver/gcode/gcode_maker.py: -------------------------------------------------------------------------------- 1 | """ 2 | A bunch of command to easily create G-Codes. 3 | """ 4 | 5 | 6 | def step(axis, n): 7 | """ 8 | Moves the given axis of n steps. 9 | 10 | :param axis: The axis 11 | :param n: number of steps 12 | :type axis: str 13 | :type n: int 14 | """ 15 | return "S0 {}{}".format(axis, n) 16 | 17 | 18 | def step_x(n): 19 | """ 20 | Moves the X axis oh n steps. 21 | :param n: The number of steps 22 | :type n: int 23 | """ 24 | return step('X', n) 25 | 26 | 27 | def step_y(n): 28 | """ 29 | Moves the Y axis oh n steps. 30 | :param n: The number of steps 31 | :type n: int 32 | """ 33 | return step('Y', n) 34 | 35 | 36 | def step_z(n): 37 | """ 38 | Moves the Z axis oh n steps. 39 | :param n: The number of steps 40 | :type n: int 41 | """ 42 | return step('Z', n) 43 | 44 | 45 | def start_continuous(axis, direction="forward"): 46 | """ 47 | Start a continuous movement in the given direction. 48 | :param axis: The axis which is to move. 49 | :param direction: The direction. 50 | :type axis: str 51 | :type direction: str 52 | """ 53 | if direction == "forward": 54 | return "S1 {}".format(axis) 55 | else: 56 | return "S2 {}".format(axis) 57 | 58 | 59 | def start_continuous_x_forward(): 60 | """ 61 | Start a continuous movement on X axis forward. 62 | """ 63 | return start_continuous("X") 64 | 65 | 66 | def start_continuous_y_forward(): 67 | """ 68 | Start a continuous movement on Y axis forward. 69 | """ 70 | return start_continuous("Y") 71 | 72 | 73 | def start_continuous_z_forward(): 74 | """ 75 | Start a continuous movement on Z axis forward. 76 | """ 77 | return start_continuous("Z") 78 | 79 | 80 | def start_continuous_x_backward(): 81 | """ 82 | Start a continuous movement on X axis backward 83 | """ 84 | return start_continuous("X", "backward") 85 | 86 | 87 | def start_continuous_y_backward(): 88 | """ 89 | Start a continuous movement on Y axis backward 90 | """ 91 | return start_continuous("Y", "backward") 92 | 93 | 94 | def start_continuous_z_backward(): 95 | """ 96 | Start a continuous movement on Z axis backward 97 | """ 98 | return start_continuous("Z", "backward") 99 | 100 | 101 | def stop(axis): 102 | """ 103 | Stop any movement on the given axis. 104 | """ 105 | return "S3 {}".format(axis) 106 | 107 | 108 | def stop_x(): 109 | """ 110 | Stop any movement on the X axis. 111 | """ 112 | return stop("X") 113 | 114 | 115 | def stop_y(): 116 | """ 117 | Stop any movement on the Y axis. 118 | """ 119 | return stop("Y") 120 | 121 | 122 | def stop_z(): 123 | """ 124 | Stop any movement on the Z axis. 125 | """ 126 | return stop("Z") 127 | 128 | 129 | def emergency_stop(): 130 | """ 131 | Stop every axis. 132 | """ 133 | return "M112" 134 | 135 | 136 | def set_origin(): 137 | """ 138 | Register the current position as the origin. 139 | """ 140 | return "G92 X000 Y000 Z000" 141 | 142 | 143 | def goto_origin(): 144 | """ 145 | Go to the origin. 146 | """ 147 | return "G28" 148 | 149 | 150 | def config_as_gcode(**kwargs): 151 | """ 152 | Make a set of commands to save the configuration. 153 | 154 | :param x_ratio: The X axis ratio (mm/step) 155 | :param y_ratio: The Y axis ratio (mm/step) 156 | :param z_ratio: The Z axis ratio (mm/step) 157 | 158 | :param x_drive: X axis drive mode (0:normal, 1:full torque, 2:half step) 159 | :param y_drive: Y axis drive mode (0:normal, 1:full torque, 2:half step) 160 | :param z_drive: Z axis drive mode (0:normal, 1:full torque, 2:half step) 161 | 162 | :param x_play: X axis play 163 | :param y_play: Y axis play 164 | :param z_play: Z axis play 165 | 166 | :param x_reverse: Should the X axis be reversed ? 167 | :param y_reverse: Should the Y axis be reversed ? 168 | :param z_reverse: Should the Z axis be reversed ? 169 | 170 | :param x_min_time: The minimal duration between 2 pulse for the x axis in 171 | milliseconds. 172 | :param y_min_time: The minimal duration between 2 pulse for the y axis in 173 | milliseconds. 174 | :param z_min_time: The minimal duration between 2 pulse for the z axis in 175 | milliseconds. 176 | 177 | :type x_ratio: float 178 | :type y_ratio: float 179 | :type z_ratio: float 180 | 181 | :type x_drive: int 182 | :type y_drive: int 183 | :type z_drive: int 184 | 185 | :type x_play: float 186 | :type y_play: float 187 | :type z_play: float 188 | 189 | :type x_reverse: bool 190 | :type y_reverse: bool 191 | :type z_reverse: bool 192 | 193 | :type x_min_time: int 194 | :type y_min_time: int 195 | :type z_min_time: int 196 | """ 197 | 198 | r = [] 199 | r.append("M92 X{x_ratio} Y{y_ratio} Z{z_ratio}".format(**kwargs)) 200 | 201 | drive = [5, 6, 7] 202 | r.append("S{} X".format(drive[kwargs["x_drive"]])) 203 | r.append("S{} Y".format(drive[kwargs["y_drive"]])) 204 | r.append("S{} Z".format(drive[kwargs["z_drive"]])) 205 | 206 | r.append("S8 X{x_play} Y{y_play} Z{z_play}".format(**kwargs)) 207 | 208 | reverse = {True: 9, False: 10} 209 | r.append("S{} X".format(reverse[kwargs["x_reverse"]])) 210 | r.append("S{} Y".format(reverse[kwargs["y_reverse"]])) 211 | r.append("S{} Z".format(reverse[kwargs["z_reverse"]])) 212 | 213 | r.append("S11 X{x_min_time} Y{y_min_time} Z{z_min_time}".format(**kwargs)) 214 | 215 | return '\n'.join(r) 216 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # SiviCNCDriver documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Aug 13 23:20:38 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.coverage', 36 | 'sphinx.ext.mathjax', 37 | 'sphinx.ext.viewcode', 38 | 'sphinx.ext.githubpages'] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'SiviCNCDriver' 54 | copyright = '2017, Klafyvel' 55 | author = 'Klafyvel' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '0.1.9' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '0.1.9' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | # If true, `todo` and `todoList` produce output, else they produce nothing. 82 | todo_include_todos = False 83 | 84 | 85 | # -- Options for HTML output ---------------------------------------------- 86 | 87 | # The theme to use for HTML and HTML Help pages. See the documentation for 88 | # a list of builtin themes. 89 | # 90 | html_theme = "sphinx_rtd_theme" 91 | 92 | # Theme options are theme-specific and customize the look and feel of a theme 93 | # further. For a list of options available for each theme, see the 94 | # documentation. 95 | # 96 | # html_theme_options = {} 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ['_static'] 102 | 103 | # Custom sidebar templates, must be a dictionary that maps document names 104 | # to template names. 105 | # 106 | # This is required for the alabaster theme 107 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 108 | html_sidebars = { 109 | '**': [ 110 | 'about.html', 111 | 'navigation.html', 112 | 'relations.html', # needs 'show_related': True theme option to display 113 | 'searchbox.html', 114 | 'donate.html', 115 | ] 116 | } 117 | 118 | 119 | # -- Options for HTMLHelp output ------------------------------------------ 120 | 121 | # Output file base name for HTML help builder. 122 | htmlhelp_basename = 'SiviCNCDriverdoc' 123 | 124 | 125 | # -- Options for LaTeX output --------------------------------------------- 126 | 127 | latex_elements = { 128 | # The paper size ('letterpaper' or 'a4paper'). 129 | # 130 | # 'papersize': 'letterpaper', 131 | 132 | # The font size ('10pt', '11pt' or '12pt'). 133 | # 134 | # 'pointsize': '10pt', 135 | 136 | # Additional stuff for the LaTeX preamble. 137 | # 138 | # 'preamble': '', 139 | 140 | # Latex figure (float) alignment 141 | # 142 | # 'figure_align': 'htbp', 143 | } 144 | 145 | # Grouping the document tree into LaTeX files. List of tuples 146 | # (source start file, target name, title, 147 | # author, documentclass [howto, manual, or own class]). 148 | latex_documents = [ 149 | (master_doc, 'SiviCNCDriver.tex', 'SiviCNCDriver Documentation', 150 | 'Klafyvel', 'manual'), 151 | ] 152 | 153 | 154 | # -- Options for manual page output --------------------------------------- 155 | 156 | # One entry per manual page. List of tuples 157 | # (source start file, name, description, authors, manual section). 158 | man_pages = [ 159 | (master_doc, 'sivicncdriver', 'SiviCNCDriver Documentation', 160 | [author], 1) 161 | ] 162 | 163 | 164 | # -- Options for Texinfo output ------------------------------------------- 165 | 166 | # Grouping the document tree into Texinfo files. List of tuples 167 | # (source start file, target name, title, author, 168 | # dir menu entry, description, category) 169 | texinfo_documents = [ 170 | (master_doc, 'SiviCNCDriver', 'SiviCNCDriver Documentation', 171 | author, 'SiviCNCDriver', 'One line description of project.', 172 | 'Miscellaneous'), 173 | ] 174 | 175 | 176 | # Sort members by type 177 | autodoc_member_order = 'groupwise' -------------------------------------------------------------------------------- /sivicncdriver/ui/preprocessor.py: -------------------------------------------------------------------------------- 1 | """ 2 | The preprocessor module 3 | ======================= 4 | 5 | Provides the PreprocessorDialog class. 6 | """ 7 | import os 8 | 9 | from PyQt5.QtWidgets import * 10 | from PyQt5.QtCore import * 11 | from PyQt5.QtGui import * 12 | 13 | from sivicncdriver.ui.preprocessor_window import Ui_dialog 14 | from sivicncdriver import settings 15 | from sivicncdriver.settings import logger 16 | from sivicncdriver.gcode.gcode import parse 17 | from sivicncdriver.gcode.arc_calculator import arc_to_segments 18 | 19 | 20 | class PreprocessorDialog(QDialog, Ui_dialog): 21 | """ 22 | The preprocessor dialog. 23 | """ 24 | 25 | def __init__(self, gcode, parent=None): 26 | """ 27 | The __init__ method. 28 | :param gcode: The gcode which is to be preprocessed. 29 | :param parent: Qt stuff. 30 | :type gcode: str 31 | """ 32 | super(PreprocessorDialog, self).__init__(parent=parent) 33 | self.setupUi(self) 34 | self.gcode = gcode 35 | self.original = gcode 36 | self.has_been_run_once = False 37 | 38 | self.btn_run_preproc.clicked.connect(self.run_preprocessor) 39 | self.buttonBox.accepted.connect(self.accept) 40 | self.buttonBox.rejected.connect(self.cancel) 41 | 42 | @pyqtSlot() 43 | def run_preprocessor(self): 44 | """ 45 | Runs the preprocessor on the G-Code. 46 | """ 47 | self.remove_useless() 48 | self.has_been_run_once = True 49 | 50 | def remove_useless(self): 51 | """ 52 | Remove useless things in the code according to the UI options. 53 | """ 54 | rm_nums = self.chk_del_num.isChecked() 55 | rm_comm = self.chk_del_comments.isChecked() 56 | minimize = self.chk_optimize_bounding_box.isChecked() 57 | if minimize: 58 | x, y = self.get_minimize_bounding_box() 59 | r = '' 60 | last_g00 = None 61 | line = 0 62 | for i in parse(self.gcode): 63 | if 'M' in i['name'] or 'G' in i['name']: 64 | if not rm_nums and 'N' in i['args']: 65 | r += 'N' + str(int(i['args']['N'])) + ' ' 66 | r += i['name'] + str(int(i['value'])) + ' ' 67 | for a in i['args']: 68 | if (not minimize and a in 'XY') or (a in 'ZIJKF'): 69 | r += a + str(i['args'][a]) + ' ' 70 | elif minimize and a == 'X': 71 | r += a + str(i['args'][a]-x) + ' ' 72 | elif minimize and a == 'Y': 73 | r += a + str(i['args'][a]-y) + ' ' 74 | if i['name'] == 'G' and i['value'] == 0: 75 | last_g00 = line 76 | r += '\n' 77 | line += 1 78 | if 'comment' in i['args'] and not rm_comm: 79 | logger.debug(i) 80 | r += '(' + i['args']['comment'] + ')\n' 81 | line += 1 82 | if last_g00: 83 | r = r.split('\n') 84 | r[last_g00] = 'G00 X0.0000 Y0.0000' 85 | r = '\n'.join(r) 86 | self.output.setText(r) 87 | self.gcode = r 88 | 89 | def get_minimize_bounding_box(self): 90 | """ 91 | Computes a new origin for the drawing. 92 | """ 93 | min_x, min_y = 0, 0 94 | set_x, set_y = False, False 95 | x_o, y_o = 0, 0 96 | x, y = 0, 0 97 | z = 0 98 | r = '' 99 | for t in parse(self.gcode): 100 | if t['name'] is not 'G': 101 | continue 102 | if 'Z' in t['args']: 103 | z = t['args']['Z'] 104 | if (z > 0 105 | or t['value'] not in (1, 2, 3) 106 | or not ('X' in t['args'] or 'Y' in t['args'])): 107 | x_o = t['args'].get('X', x_o) 108 | y_o = t['args'].get('Y', y_o) 109 | continue 110 | if 'X' in t['args']: 111 | x = t['args']['X'] 112 | if not set_x: 113 | min_x = x 114 | set_x = True 115 | if x < min_x: 116 | logger.debug( 117 | "min_x becomes {x} from {t}".format(**locals())) 118 | min_x = min(min_x, x) 119 | if 'Y' in t['args']: 120 | y = t['args']['Y'] 121 | if not set_y: 122 | min_y = y 123 | set_y = True 124 | if y < min_y: 125 | logger.debug( 126 | "min_y becomes {y} from {t}".format(**locals())) 127 | min_y = min(min_y, y) 128 | 129 | if t['value'] in (2, 3): 130 | i = t['args'].get('I', 0) 131 | j = t['args'].get('J', 0) 132 | k = t['args'].get('K', 0) 133 | clockwise = (t['value'] == 2) 134 | segments = arc_to_segments( 135 | (x_o, y_o), 136 | (i, j), 137 | (x, y), 138 | clockwise 139 | ) 140 | for xc, yc in segments: 141 | if not set_x: 142 | min_x = xc 143 | set_x = True 144 | if not set_y: 145 | min_y = yc 146 | set_y = True 147 | if xc < min_x: 148 | logger.debug( 149 | "min_x becomes {xc} from {t}".format(**locals())) 150 | if yc < min_y: 151 | logger.debug( 152 | "min_y becomes {yc} from {t}".format(**locals())) 153 | min_x = min(min_x, xc) 154 | min_y = min(min_y, yc) 155 | x_o, y_o = x, y 156 | logger.debug( 157 | "Minimizing by setting origin to {},{}".format(min_x, min_y)) 158 | return min_x, min_y 159 | 160 | @pyqtSlot() 161 | def accept(self): 162 | if not self.has_been_run_once: 163 | self.run_preprocessor() 164 | super(PreprocessorDialog, self).accept() 165 | 166 | @pyqtSlot() 167 | def cancel(self): 168 | self.gcode = self.original 169 | self.reject() 170 | -------------------------------------------------------------------------------- /sivicncdriver/serial/serial_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | The serial_manager module 3 | ========================= 4 | 5 | Provides a class to handle the CNC machine through a serial object. 6 | """ 7 | 8 | import serial 9 | import time 10 | 11 | from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal 12 | 13 | from sivicncdriver.settings import logger 14 | 15 | __all__ = ["SerialManager"] 16 | 17 | 18 | class SerialManager(QObject): 19 | """ 20 | A class to manage the serial port. 21 | 22 | It will try to send what it receive and send via the send_print signal. When 23 | it receive a 'ok' from the serial it will send the send_confirm signal. 24 | """ 25 | send_print = pyqtSignal(str, str) 26 | send_confirm = pyqtSignal(bool) 27 | serial_fatal_error = pyqtSignal() 28 | 29 | def __init__(self, serial, fake_mode=False): 30 | """ 31 | The __init__ method. 32 | 33 | :param serial: The serial port. 34 | :param fake_mode: if True, then the manager will not use the serial 35 | object, and will always respond 'ok'. 36 | """ 37 | super(SerialManager, self).__init__() 38 | self.serial = serial 39 | self.fake_mode = fake_mode 40 | self.is_open = self.serial.isOpen() 41 | self.something_sent = False 42 | 43 | def open(self, baudrate, serial_port, timeout): 44 | """ 45 | Opens the serial port with the given parameters. 46 | 47 | :param baudrate: The baudrate. 48 | :param serial_port: The port to be used. 49 | :param timeout: Timeout for reading and writing. 50 | """ 51 | logger.info("Opening {} with baudrate {}, timeout {}".format( 52 | repr(serial_port), baudrate, timeout)) 53 | self.send_print.emit("Opening {} with baudrate {}, timeout {}".format( 54 | repr(serial_port), baudrate, timeout), "info") 55 | self.serial.port = serial_port 56 | self.serial.baudrate = baudrate 57 | self.serial.timeout = timeout 58 | self.serial.write_timeout = timeout 59 | try: 60 | self.serial.open() 61 | self.is_open = True 62 | except serial.serialutil.SerialException: 63 | self.is_open = False 64 | logger.error("Could not open serial port.") 65 | self.send_print.emit("Could not open serial port.", "error") 66 | return False 67 | logger.debug(self.serial.timeout) 68 | return True 69 | 70 | def close(self): 71 | """ 72 | Closes the serial port. 73 | """ 74 | logger.info("Closing serial port.") 75 | self.send_print.emit("Closing serial port.", "info") 76 | self.is_open = False 77 | self.serial.close() 78 | 79 | def sendMsg(self, msg): 80 | """ 81 | Sends a message using the serial port if fake_mode is False. 82 | 83 | :param msg: The message to be sent. 84 | :returns: True if no error occurred, else False. 85 | """ 86 | if not self.fake_mode and not (self.serial and self.serial.isOpen()): 87 | self.send_print.emit("Error, no serial port to write.", "error") 88 | logger.error("Serial manager has no serial opened to send data.") 89 | return False 90 | elif self.fake_mode: 91 | self.send_print.emit(msg, "operator") 92 | logger.info( 93 | "Sending {} through fake serial port".format(repr(msg))) 94 | self.something_sent = True 95 | return True 96 | else: 97 | logger.info("Sending {} through {}".format(repr(msg), self.serial)) 98 | self.send_print.emit(msg, "operator") 99 | if msg[-1] != '\n': 100 | msg += '\n' 101 | try: 102 | self.serial.write(bytes(msg, encoding='utf8')) 103 | except serial.serialutil.SerialException as e: 104 | logger.error("Serial error : {}".format(e)) 105 | self.send_print.emit("Serial error while writing.", "error") 106 | except OSError as e: 107 | logger.error("Serial error : {}".format(e)) 108 | self.send_print.emit("Serial error while writing.", "error") 109 | self.serial_fatal_error.emit() 110 | return True 111 | 112 | @pyqtSlot() 113 | def readMsg(self): 114 | """ 115 | Reads a line from the serial port. And emit the send_print or 116 | send_confirm signals if necessary. 117 | """ 118 | if self.fake_mode: 119 | if self.something_sent: 120 | logger.info("Received {}".format(repr("ok"))) 121 | self.send_print.emit("ok", "machine") 122 | self.something_sent = False 123 | self.send_confirm.emit(True) 124 | return 125 | elif not (self.serial and self.serial.isOpen()): 126 | self.send_print.emit("Error, no serial port to read.", "error") 127 | logger.error("Serial manager has no serial opened to read data.") 128 | self.send_confirm.emit(False) 129 | return 130 | try: 131 | waiting = self.serial.in_waiting 132 | except OSError as e: 133 | logger.error("Serial error : {}".format(e)) 134 | self.send_print.emit("Serial error while reading.", "error") 135 | self.serial_fatal_error.emit() 136 | return 137 | if not waiting: 138 | return 139 | txt = "" 140 | try: 141 | txt = self.serial.readline().decode('ascii') 142 | except serial.serialutil.SerialException as e: 143 | logger.error("Serial error : {}".format(e)) 144 | self.send_print.emit("Serial error while reading.", "error") 145 | except UnicodeDecodeError as e: 146 | logger.error("Serial error : {}".format(e)) 147 | self.send_print.emit("Serial error while reading.", "error") 148 | except OSError as e: 149 | logger.error("Serial error : {}".format(e)) 150 | self.send_print.emit("Serial error while reading.", "error") 151 | self.serial_fatal_error.emit() 152 | 153 | if txt: 154 | if "error" in txt.lower(): 155 | self.send_print.emit(txt, "error") 156 | else: 157 | self.send_print.emit("m> {}".format(txt), "machine") 158 | self.send_confirm.emit("ok" in txt.lower()) 159 | -------------------------------------------------------------------------------- /docs/Getting started.rst: -------------------------------------------------------------------------------- 1 | What is SiviCNCDriver ? 2 | ======================= 3 | 4 | SiviCNCDriver, as its name lets you guess, is designed to drive a CNC. What does it do ? 5 | 6 | - Provides a basic tool to view and edit G-Codes files. You can see which G-Code line draws which path and perform some basic edits with the preprocessor, such as finding an origin to the coordinate system which minimize the bounding box of the drawing. 7 | - Allows you to control manually your CNC, by : 8 | - Sending your own G-Codes; 9 | - Sending `custom G-Codes`_ so the machine performs continuous movements, or step-by-step movements; 10 | - Sending automatic commands so the machine performs some goings and comings and you can measure the play or the steps/mm. 11 | - Sends as `custom G-Codes`_ and store as JSON configuration files for your machine. 12 | 13 | Screenshots 14 | =========== 15 | 16 | .. image:: images/gcode_viewer.png 17 | :alt: G-Codes viewer 18 | 19 | .. image:: images/configuration.png 20 | :alt: Machine configuration 21 | 22 | .. image:: images/send_command.png 23 | :alt: Commands sending 24 | 25 | Installation 26 | ============ 27 | Using pip 28 | --------- 29 | On any operating system with a python and pip installated, use pip (you may need superuser privilege) :: 30 | 31 | pip install sivicncdriver 32 | 33 | Then you should be able to run the program with a simple:: 34 | 35 | sivicnc 36 | 37 | 38 | You can get the development version using pip, although it is not recommended. :: 39 | 40 | pip install git+git://github.com/Klafyvel/SiviCNCDriver 41 | 42 | Binary distribution (Windows) 43 | ----------------------------- 44 | If, for some reasons, you can't or don't want to use pip, a binary is available here_ . 45 | 46 | .. _here: https://github.com/Klafyvel/SiviCNCDriver/releases/latest 47 | 48 | Contribute 49 | =========== 50 | 51 | The project has its own Git repository on GitHub_. 52 | 53 | .. _github: https://github.com/Klafyvel/SiviCNCDriver 54 | 55 | You nill need ``virtualenv`` :: 56 | 57 | pip install --user virtualenv 58 | 59 | Create a directory in which we will work. :: 60 | 61 | mkdir SiviCNCDriver 62 | cd SiviCNCDriver 63 | 64 | Clone the project :: 65 | 66 | git clone https://github.com/Klafyvel/SiviCNCDriver.git 67 | 68 | Then create the virtual environment :: 69 | 70 | virtualenv ENV 71 | 72 | Activate it :: 73 | 74 | source ENV/bin/activate 75 | 76 | Download the dependencies :: 77 | 78 | cd SiviCNCDriver 79 | pip install -r requirements.txt 80 | 81 | You can code ! To test the code, run the application as package :: 82 | 83 | python -m sivicncdriver 84 | 85 | If you need to re-create the ui after editing it with QtCreator, you can use `make_ui.sh` or directly `pyuic5`. 86 | 87 | 88 | Custom G-Codes 89 | ============== 90 | 91 | SiviCNCDriver uses several custom G-Codes, they may change in the future. 92 | 93 | 94 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 95 | |Command | Explanation | 96 | +======================+==============================================================================================================+ 97 | |``S0 Xnnn Ynnn Znnn`` | Perform a straight line with ``nnn`` in steps on the given axes. A negative number make the axis go backward.| 98 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 99 | |``S1 X Y Z`` | Trigger continuous advancement forward on the given axes. | 100 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 101 | |``S2 X Y Z`` | Trigger continuous advancement backward on the given axes. | 102 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 103 | |``S3 X Y Z`` | Stop continuous advancement (if exists) on the given axes. | 104 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 105 | |``S5 X Y Z`` | Set driving mode to normal on the given axes. | 106 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 107 | |``S6 X Y Z`` | Set driving mode to max torque on the given axes. | 108 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 109 | |``S7 X Y Z`` | Set driving mode to half steps on the given axes. | 110 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 111 | |``S8 Xnnn Ynnn Znnn`` | Set the play of the given axes, with ``nnn`` in millimeters. | 112 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 113 | |``S9 X Y Z`` | Set the given axes sense to reverse. | 114 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 115 | |``S10 X Y Z`` | Set the given axes sense to normal. | 116 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 117 | |``S11 Xnnn Ynnn Znnn``| Set the minimal duration between two pulses for the given axes. | 118 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 119 | 120 | 121 | License 122 | ======= 123 | 124 | SiviCNCDriver 125 | Copyright (C) 2017 Hugo LEVY-FALK 126 | 127 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 128 | 129 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 130 | 131 | You should have received a copy of the GNU General Public License along with this program. If not, see . 132 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | SiviCNCDriver 3 | ============= 4 | 5 | .. image:: https://readthedocs.org/projects/sivicncdriver/badge/?version=latest 6 | :target: http://sivicncdriver.readthedocs.io/en/latest/?badge=latest 7 | :alt: Documentation Status 8 | 9 | SiviCNCDriver, a software to control my CNC. 10 | 11 | :Info: This is a Python program to control a CNC. 12 | :Author: Hugo LEVY-FALK 13 | :Date: 2017-08 14 | :Version: 0.1.9 15 | 16 | .. index: README 17 | .. contents:: Table of Contents 18 | 19 | ------ 20 | 21 | Goal 22 | ==== 23 | 24 | SiviCNCDriver, as its name lets you guess, is designed to drive a CNC. What does it do ? 25 | 26 | - Provides a basic tool to view and edit G-Codes files. You can see which G-Code line draws which path and perform some basic edits with the preprocessor, such as finding an origin to the coordinate system which minimize the bounding box of the drawing. 27 | - Allows you to control manually your CNC, by : 28 | - Sending your own G-Codes; 29 | - Sending `custom G-Codes`_ so the machine performs continuous movements, or step-by-step movements; 30 | - Sending automatic commands so the machine performs some goings and comings and you can measure the play or the steps/mm. 31 | - Sends as `custom G-Codes`_ and store as JSON configuration files for your machine. 32 | 33 | Screenshots 34 | =========== 35 | 36 | .. image:: docs/images/gcode_viewer.png 37 | :alt: G-Codes viewer 38 | 39 | .. image:: docs/images/configuration.png 40 | :alt: Machine configuration 41 | 42 | .. image:: docs/images/send_command.png 43 | :alt: Commands sending 44 | 45 | Installation 46 | ============ 47 | Using pip 48 | --------- 49 | On any operating system with a python and pip installated, use pip (you may need superuser privilege) :: 50 | 51 | pip install sivicncdriver 52 | 53 | Then you should be able to run the program with a simple:: 54 | 55 | sivicnc 56 | 57 | 58 | You can get the development version using pip, although it is not recommended. :: 59 | 60 | pip install git+git://github.com/Klafyvel/SiviCNCDriver 61 | 62 | Binary distribution (Windows) 63 | ----------------------------- 64 | If, for some reasons, you can't or don't want to use pip, a binary is available here_ . 65 | 66 | .. _here: https://github.com/Klafyvel/SiviCNCDriver/releases/latest 67 | 68 | Contribute 69 | =========== 70 | 71 | The project has its own Git repository on GitHub_. 72 | 73 | .. _github: https://github.com/Klafyvel/SiviCNCDriver 74 | 75 | You nill need ``virtualenv`` :: 76 | 77 | pip install --user virtualenv 78 | 79 | Create a directory in which we will work. :: 80 | 81 | mkdir SiviCNCDriver 82 | cd SiviCNCDriver 83 | 84 | Clone the project :: 85 | 86 | git clone https://github.com/Klafyvel/SiviCNCDriver.git 87 | 88 | Then create the virtual environment :: 89 | 90 | virtualenv ENV 91 | 92 | Activate it :: 93 | 94 | source ENV/bin/activate 95 | 96 | Download the dependencies :: 97 | 98 | cd SiviCNCDriver 99 | pip install -r requirements.txt 100 | 101 | You can code ! To test the code, run the application as package :: 102 | 103 | python -m sivicncdriver 104 | 105 | If you need to re-create the ui after editing it with QtCreator, you can use `make_ui.sh` or directly `pyuic5`. 106 | 107 | 108 | Custom G-Codes 109 | ============== 110 | 111 | SiviCNCDriver uses several custom G-Codes, they may change in the future. 112 | 113 | 114 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 115 | |Command | Explanation | 116 | +======================+==============================================================================================================+ 117 | |``S0 Xnnn Ynnn Znnn`` | Perform a straight line with ``nnn`` in steps on the given axes. A negative number make the axis go backward.| 118 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 119 | |``S1 X Y Z`` | Trigger continuous advancement forward on the given axes. | 120 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 121 | |``S2 X Y Z`` | Trigger continuous advancement backward on the given axes. | 122 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 123 | |``S3 X Y Z`` | Stop continuous advancement (if exists) on the given axes. | 124 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 125 | |``S5 X Y Z`` | Set driving mode to normal on the given axes. | 126 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 127 | |``S6 X Y Z`` | Set driving mode to max torque on the given axes. | 128 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 129 | |``S7 X Y Z`` | Set driving mode to half steps on the given axes. | 130 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 131 | |``S8 Xnnn Ynnn Znnn`` | Set the play of the given axes, with ``nnn`` in millimeters. | 132 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 133 | |``S9 X Y Z`` | Set the given axes sense to reverse. | 134 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 135 | |``S10 X Y Z`` | Set the given axes sense to normal. | 136 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 137 | |``S11 Xnnn Ynnn Znnn``| Set the minimal duration between two pulses for the given axes. | 138 | +----------------------+--------------------------------------------------------------------------------------------------------------+ 139 | 140 | 141 | License 142 | ======= 143 | 144 | SiviCNCDriver 145 | Copyright (C) 2017 Hugo LEVY-FALK 146 | 147 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 148 | 149 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 150 | 151 | You should have received a copy of the GNU General Public License along with this program. If not, see . 152 | -------------------------------------------------------------------------------- /sivicncdriver/ui/view3d.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | 3 | matplotlib.use('Qt5Agg') 4 | from PyQt5.QtCore import * 5 | from PyQt5.QtWidgets import * 6 | 7 | import numpy as np 8 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg 9 | from matplotlib.figure import Figure 10 | from mpl_toolkits.mplot3d import Axes3D 11 | from mpl_toolkits.mplot3d.art3d import Line3DCollection as LineCollection 12 | 13 | 14 | from sivicncdriver import settings 15 | from sivicncdriver.settings import logger 16 | from sivicncdriver.gcode.gcode import parse 17 | from sivicncdriver.gcode.arc_calculator import arc_to_segments 18 | 19 | 20 | import matplotlib.pyplot as pl 21 | 22 | _translate = QCoreApplication.translate 23 | 24 | 25 | class View3D(FigureCanvasQTAgg): 26 | """ 27 | Prints G-Codes in 3D. 28 | """ 29 | parse_error = pyqtSignal(int) 30 | 31 | def __init__(self, parent=None, width=5, height=4, dpi=100): 32 | fig = Figure(figsize=(width, height), dpi=dpi) 33 | self.axes = fig.add_subplot(111, projection='3d') 34 | 35 | super(View3D, self).__init__(fig) 36 | self.setParent(parent) 37 | self.axes.mouse_init() 38 | 39 | super(View3D, self).setSizePolicy( 40 | QSizePolicy.Expanding, 41 | QSizePolicy.Expanding 42 | ) 43 | 44 | super(View3D, self).updateGeometry() 45 | 46 | self.segments_x = [] 47 | self.segments_y = [] 48 | self.segments_z = [] 49 | 50 | self.lines = {} 51 | 52 | self.data = [] 53 | 54 | def get_bounds(self): 55 | """ 56 | Returns the maximum and the minimum value on each axis. 57 | """ 58 | return { 59 | 'min_x': min(sum(self.segments_x, []), default=0), 60 | 'max_x': max(sum(self.segments_x, []), default=0), 61 | 'min_y': min(sum(self.segments_y, []), default=0), 62 | 'max_y': max(sum(self.segments_y, []), default=0), 63 | 'min_z': min(sum(self.segments_z, []), default=0), 64 | 'max_z': max(sum(self.segments_z, []), default=0) 65 | } 66 | 67 | def compute_data(self, gcode): 68 | """ 69 | Computes the paths generated by a gcode file. 70 | 71 | :param gcode: The gcode. 72 | :type gcode: str 73 | """ 74 | current_pos = [0, 0, 0] 75 | 76 | self.segments_x = [] 77 | self.segments_y = [] 78 | self.segments_z = [] 79 | 80 | segment_no = 0 81 | 82 | for t in parse(gcode): 83 | if t['name'] == '__error__': 84 | self.parse_error.emit(t['line']) 85 | break 86 | 87 | if t['name'] is not 'G': 88 | continue 89 | 90 | current_line = t['line'] 91 | 92 | x, y, z = current_pos 93 | x = t['args'].get('X', x) 94 | y = t['args'].get('Y', y) 95 | z = t['args'].get('Z', z) 96 | 97 | x_o, y_o, z_o = current_pos 98 | 99 | if t['value'] in (0, 1): 100 | self.segments_x.append([x_o, x]) 101 | self.segments_y.append([y_o, y]) 102 | self.segments_z.append([z_o, z]) 103 | self.lines[segment_no] = t['line'] 104 | segment_no += 1 105 | elif t['value'] in (2, 3): 106 | i = t['args'].get('I', 0) 107 | j = t['args'].get('J', 0) 108 | 109 | clockwise = (t['value'] == 2) 110 | 111 | nb_segments = 0 112 | segments = arc_to_segments( 113 | (x_o, y_o), 114 | (i, j), 115 | (x, y), 116 | clockwise 117 | ) 118 | for x_c, y_c in segments: 119 | self.segments_x.append([x_o, x_c]) 120 | self.segments_y.append([y_o, y_c]) 121 | x_o, y_o = x_c, y_c 122 | nb_segments += 1 123 | 124 | points_z = np.linspace(z_o, z, nb_segments+1) 125 | for z_count in range(nb_segments): 126 | self.segments_z.append( 127 | [points_z[z_count], points_z[z_count+1]]) 128 | self.lines[segment_no] = t['line'] 129 | segment_no += 1 130 | 131 | current_pos = x, y, z 132 | 133 | def draw(self, **kwargs): 134 | """ 135 | :param reverse_x: Should the x axis be reversed ? (default : False) 136 | :param reverse_y: Should the y axis be reversed ? (default : False) 137 | :param reverse_z: Should the z axis be reversed ? (default : False) 138 | :param highlight_line: A line which is to be highlighted. (default : 139 | None) 140 | 141 | :type highlight_line: int 142 | :type reverse_x: bool 143 | :type reverse_y: bool 144 | :type reverse_z: bool 145 | """ 146 | self.axes.clear() 147 | 148 | self.axes.set_xlabel(_translate('View3D', 'X Axis')) 149 | self.axes.set_ylabel(_translate('View3D', 'Y Axis')) 150 | self.axes.set_zlabel(_translate('View3D', 'Z Axis')) 151 | 152 | reverse_x = kwargs.get('reverse_x', False) 153 | reverse_y = kwargs.get('reverse_y', False) 154 | reverse_z = kwargs.get('reverse_z', False) 155 | 156 | def reverse_func(x, y, z): 157 | if reverse_x: 158 | x *= -1 159 | if reverse_y: 160 | y *= -1 161 | if reverse_z: 162 | z *= -1 163 | return (x, y, z) 164 | 165 | highlight_line = kwargs.get('highlight_line', None) 166 | 167 | bounds = self.get_bounds() 168 | min_z = bounds['min_z'] * (1 if not reverse_z else -1) 169 | max_z = bounds['max_z'] * (1 if not reverse_z else -1) 170 | map_z_to_ratio = lambda z: (z - min_z) / \ 171 | (max_z - min_z) if min_z != max_z else 0 172 | map_z_to_color = lambda z: ( 173 | 1-map_z_to_ratio(z), 0.5, map_z_to_ratio(z)) 174 | 175 | segments = [] 176 | for x, y, z in zip(self.segments_x, self.segments_y, self.segments_z): 177 | segments.append(( 178 | reverse_func(x[0], y[0], z[0]), 179 | reverse_func(x[1], y[1], z[1]) 180 | )) 181 | colors = [] 182 | for l, p in enumerate(segments): 183 | if self.lines[l] == highlight_line: 184 | colors.append((0, 1, 0)) 185 | else: 186 | colors.append(map_z_to_color((p[0][2]+p[1][2])/2)) 187 | 188 | lines = LineCollection(segments, 189 | linewidths=1, 190 | linestyles='solid', 191 | colors=colors 192 | ) 193 | self.axes.add_collection3d(lines) 194 | 195 | if reverse_x: 196 | self.axes.invert_xaxis() 197 | if reverse_y: 198 | self.axes.invert_yaxis() 199 | if reverse_z: 200 | self.axes.invert_zaxis() 201 | 202 | extents = np.array([ 203 | [bounds['min_x'], bounds['max_x']], 204 | [bounds['min_y'], bounds['max_y']], 205 | [bounds['min_z'], bounds['max_z']], 206 | ]) 207 | sz = extents[:, 1] - extents[:, 0] 208 | centers = np.mean(extents, axis=1) 209 | maxsize = max(abs(sz)) 210 | r = maxsize/2 211 | for ctr, dim in zip(centers, 'xyz'): 212 | getattr(self.axes, 'set_{}lim'.format(dim))(ctr - r, ctr + r) 213 | 214 | super(View3D, self).draw() 215 | -------------------------------------------------------------------------------- /sivicncdriver/gcodes/hello_world.ngc: -------------------------------------------------------------------------------- 1 | 2 | (Header) 3 | (Generated by gcodetools from Inkscape.) 4 | (Using default header. To add your own header create file "header" in the output dir.) 5 | M3 6 | (Header end.) 7 | G21 (All units in mm) 8 | 9 | (Start cutting path id: path4175 at depth: -1.0) 10 | (path id: path4175 at depth step: -1.0) 11 | (path len: 33.78683) 12 | (Change tool to Default tool) 13 | 14 | G00 Z5.000000 15 | G00 X132.132366 Y176.140626 16 | (Subpath start) 17 | G01 Z-1.000000 F100.0(Penetrate) 18 | G01 X137.527386 Y176.140626 Z-1.000000 F400.000000 19 | G01 X137.527386 Y171.998245 Z-1.000000 20 | G01 X136.485588 Y163.676270 Z-1.000000 21 | G01 X133.149357 Y163.676270 Z-1.000000 22 | G01 X132.132366 Y171.998245 Z-1.000000 23 | G01 X132.132366 Y176.140626 Z-1.000000 24 | (Subpath end) 25 | G00 Z5.000000 26 | 27 | (End cutting path id: path4175 at depth: -1.0) 28 | 29 | (Start cutting path id: path4173 at depth: -1.0) 30 | (path id: path4173 at depth step: -1.0) 31 | (path len: 65.41737) 32 | G00 Z5.000000 33 | G00 X120.536174 Y176.140626 34 | (Subpath start) 35 | G01 Z-1.000000 F100.0(Penetrate) 36 | G01 X120.536174 Y157.958790 Z-1.000000 F400.000000 37 | G01 X115.810882 Y157.958790 Z-1.000000 38 | G01 X115.810882 Y159.905959 Z-1.000000 39 | G02 X114.742244 Y158.758010 Z-1.000000 I-8.184096 J6.547288 40 | G02 X114.000139 Y158.231642 Z-1.000000 I-2.394549 J2.589661 41 | G02 X112.871699 Y157.811922 Z-1.000000 I-2.226680 J4.259748 42 | G02 X111.569280 Y157.661136 Z-1.000000 I-1.302419 J5.549450 43 | G02 X109.171787 Y158.224054 Z-1.000000 I0.000000 J5.386971 44 | G02 X107.451700 Y159.707520 Z-1.000000 I2.039550 J4.103827 45 | G02 X106.428807 Y161.875836 Z-1.000000 I6.047706 J4.178409 46 | G02 X106.037834 Y164.680860 Z-1.000000 I9.866808 J2.805025 47 | G02 X106.509030 Y167.774266 Z-1.000000 I10.389737 J-0.000000 48 | G02 X107.600530 Y169.691408 Z-1.000000 I4.930108 J-1.537608 49 | G02 X109.379663 Y170.967164 Z-1.000000 I3.853776 J-3.495924 50 | G02 X111.594084 Y171.427735 Z-1.000000 I2.214421 J-5.093172 51 | G02 X112.751016 Y171.321029 Z-1.000000 I-0.000000 J-6.325209 52 | G02 X113.727286 Y171.030860 Z-1.000000 I-0.889035 J-4.778563 53 | G02 X114.629617 Y170.539602 Z-1.000000 I-1.788355 J-4.359122 54 | G02 X115.438812 Y169.840235 Z-1.000000 I-2.915969 J-4.191710 55 | G01 X115.438812 Y176.140626 Z-1.000000 56 | G01 X120.536174 Y176.140626 Z-1.000000 57 | (Subpath end) 58 | G00 Z5.000000 59 | 60 | (End cutting path id: path4173 at depth: -1.0) 61 | 62 | (Start cutting path id: path4173 at depth: -1.0) 63 | (path id: path4173 at depth step: -1.0) 64 | (path len: 17.29272) 65 | G00 Z5.000000 66 | G00 X115.476017 Y164.581642 67 | (Subpath start) 68 | G01 Z-1.000000 F100.0(Penetrate) 69 | G03 X115.270198 Y166.055013 Z-1.000000 I-5.376522 J0.000000 F400.000000 70 | G03 X114.818693 Y166.888479 Z-1.000000 I-2.099232 J-0.598168 71 | G03 X114.068862 Y167.456645 Z-1.000000 I-1.675354 J-1.432156 72 | G03 X113.156780 Y167.657424 Z-1.000000 I-0.912082 J-1.971274 73 | G03 X112.364493 Y167.471798 Z-1.000000 I-0.000000 J-1.783631 74 | G03 X111.680902 Y166.925687 Z-1.000000 I0.915954 J-1.847435 75 | G03 X111.283175 Y166.123724 Z-1.000000 I1.572977 J-1.279710 76 | G03 X111.085588 Y164.470020 Z-1.000000 I6.821518 J-1.653704 77 | G03 X111.280451 Y162.926289 Z-1.000000 I6.212264 J-0.000000 78 | G03 X111.693302 Y162.101174 Z-1.000000 I2.115741 J0.542782 79 | G03 X112.401779 Y161.545681 Z-1.000000 I1.650304 J1.375256 80 | G03 X113.218793 Y161.357033 Z-1.000000 I0.817014 J1.674880 81 | G03 X114.095911 Y161.552589 Z-1.000000 I0.000000 J2.064826 82 | G03 X114.831097 Y162.113575 Z-1.000000 I-0.928845 J1.979509 83 | G03 X115.266034 Y162.947474 Z-1.000000 I-1.602897 J1.366397 84 | G03 X115.476017 Y164.581642 Z-1.000000 I-6.253854 J1.634168 85 | (Subpath end) 86 | G00 Z5.000000 87 | 88 | (End cutting path id: path4173 at depth: -1.0) 89 | 90 | (Start cutting path id: path4175 at depth: -1.0) 91 | (path id: path4175 at depth step: -1.0) 92 | (path len: 18.97558) 93 | G00 Z5.000000 94 | G00 X132.318402 Y162.411232 95 | (Subpath start) 96 | G01 Z-1.000000 F100.0(Penetrate) 97 | G01 X137.353751 Y162.411232 Z-1.000000 F400.000000 98 | G01 X137.353751 Y157.958790 Z-1.000000 99 | G01 X132.318402 Y157.958790 Z-1.000000 100 | G01 X132.318402 Y162.411232 Z-1.000000 101 | (Subpath end) 102 | G00 Z5.000000 103 | 104 | (End cutting path id: path4175 at depth: -1.0) 105 | 106 | (Start cutting path id: path4171 at depth: -1.0) 107 | (path id: path4171 at depth step: -1.0) 108 | (path len: 46.48399) 109 | G00 Z5.000000 110 | G00 X98.335979 Y176.140626 111 | (Subpath start) 112 | G01 Z-1.000000 F100.0(Penetrate) 113 | G01 X103.396136 Y176.140626 Z-1.000000 F400.000000 114 | G01 X103.396136 Y157.958790 Z-1.000000 115 | G01 X98.335979 Y157.958790 Z-1.000000 116 | G01 X98.335979 Y176.140626 Z-1.000000 117 | (Subpath end) 118 | G00 Z5.000000 119 | 120 | (End cutting path id: path4171 at depth: -1.0) 121 | 122 | (Start cutting path id: path4169 at depth: -1.0) 123 | (path id: path4169 at depth step: -1.0) 124 | (path len: 48.06247) 125 | G00 Z5.000000 126 | G00 X86.082463 Y171.130081 127 | (Subpath start) 128 | G01 Z-1.000000 F100.0(Penetrate) 129 | G01 X90.807757 Y171.130081 Z-1.000000 F400.000000 130 | G01 X90.807757 Y168.972072 Z-1.000000 131 | G02 X91.596682 Y170.283854 Z-1.000000 I7.787392 J-3.790320 132 | G02 X92.209222 Y170.894437 Z-1.000000 I2.234133 J-1.628753 133 | G02 X92.989113 Y171.279906 Z-1.000000 I1.507665 J-2.068663 134 | G02 X94.007562 Y171.427735 Z-1.000000 I1.018449 J-3.434319 135 | G02 X95.086789 Y171.277322 Z-1.000000 I-0.000000 J-3.946990 136 | G02 X96.450822 Y170.733206 Z-1.000000 I-1.960247 J-6.895886 137 | G01 X94.888126 Y167.136527 Z-1.000000 138 | G03 X93.982020 Y167.442905 Z-1.000000 I-2.678275 J-6.427853 139 | G03 X93.474261 Y167.508597 Z-1.000000 I-0.507759 J-1.929481 140 | G03 X92.598974 Y167.290164 Z-1.000000 I-0.000000 J-1.862906 141 | G03 X91.936370 Y166.690040 Z-1.000000 I0.859535 J-1.614878 142 | G03 X91.431370 Y165.451832 Z-1.000000 I2.678619 J-1.814554 143 | G03 X91.155020 Y162.374027 Z-1.000000 I17.001170 J-3.077806 144 | G01 X91.155020 Y157.958790 Z-1.000000 145 | G01 X86.082463 Y157.958790 Z-1.000000 146 | G01 X86.082463 Y171.130081 Z-1.000000 147 | (Subpath end) 148 | G00 Z5.000000 149 | 150 | (End cutting path id: path4169 at depth: -1.0) 151 | 152 | (Start cutting path id: path4167 at depth: -1.0) 153 | (path id: path4167 at depth step: -1.0) 154 | (path len: 19.15381) 155 | G00 Z5.000000 156 | G00 X73.481681 Y164.519618 157 | (Subpath start) 158 | G01 Z-1.000000 F100.0(Penetrate) 159 | G03 X73.705590 Y162.839919 Z-1.000000 I6.412254 J-0.000000 F400.000000 160 | G03 X74.188616 Y161.915128 Z-1.000000 I2.345911 J0.636749 161 | G03 X75.008549 Y161.289796 Z-1.000000 I1.855316 J1.582471 162 | G03 X75.986956 Y161.071769 Z-1.000000 I0.978407 J2.086310 163 | G03 X76.976057 Y161.290608 Z-1.000000 I-0.000000 J2.344671 164 | G03 X77.772892 Y161.902724 Z-1.000000 I-0.973121 J2.091487 165 | G03 X78.251580 Y162.817636 Z-1.000000 I-1.778977 J1.513454 166 | G03 X78.479825 Y164.569230 Z-1.000000 I-6.606904 J1.751594 167 | G03 X78.257470 Y166.198965 Z-1.000000 I-6.083691 J-0.000000 168 | G03 X77.772892 Y167.111711 Z-1.000000 I-2.313614 J-0.643297 169 | G03 X76.965567 Y167.739921 Z-1.000000 I-1.863702 J-1.562219 170 | G03 X76.024161 Y167.955070 Z-1.000000 I-0.941406 J-1.952034 171 | G03 X75.024226 Y167.732063 Z-1.000000 I-0.000000 J-2.353302 172 | G03 X74.201017 Y167.099307 Z-1.000000 I1.027038 J-2.188035 173 | G03 X73.707831 Y166.172601 Z-1.000000 I1.853747 J-1.581139 174 | G03 X73.481681 Y164.519618 Z-1.000000 I5.927939 J-1.652983 175 | (Subpath end) 176 | G00 Z5.000000 177 | 178 | (End cutting path id: path4167 at depth: -1.0) 179 | 180 | (Start cutting path id: path4167 at depth: -1.0) 181 | (path id: path4167 at depth step: -1.0) 182 | (path len: 46.02642) 183 | G00 Z5.000000 184 | G00 X68.421527 Y164.507229 185 | (Subpath start) 186 | G01 Z-1.000000 F100.0(Penetrate) 187 | G02 X68.969272 Y167.287086 Z-1.000000 I7.327885 J0.000000 F400.000000 188 | G02 X70.455511 Y169.468165 Z-1.000000 I5.883491 J-2.412231 189 | G02 X72.715187 Y170.868179 Z-1.000000 I4.380784 J-4.547142 190 | G02 X75.949748 Y171.427735 Z-1.000000 I3.234561 J-9.069052 191 | G02 X79.600500 Y170.751185 Z-1.000000 I0.000000 J-10.188242 192 | G02 X81.927677 Y169.133302 Z-1.000000 I-2.127391 J-5.542717 193 | G02 X83.112991 Y167.119663 Z-1.000000 I-4.665868 J-4.102210 194 | G02 X83.552386 Y164.581642 Z-1.000000 I-7.110328 J-2.538021 195 | G02 X83.005169 Y161.775767 Z-1.000000 I-7.467223 J-0.000000 196 | G02 X81.530802 Y159.595902 Z-1.000000 I-5.864257 J2.377798 197 | G02 X79.294836 Y158.223597 Z-1.000000 I-4.244003 J4.407233 198 | G02 X75.962152 Y157.661136 Z-1.000000 I-3.332684 J9.592144 199 | G02 X72.952574 Y158.122547 Z-1.000000 I-0.000000 J10.045758 200 | G02 X70.827581 Y159.273440 Z-1.000000 I1.895957 J6.037896 201 | G02 X69.063921 Y161.571894 Z-1.000000 I4.172048 J5.027191 202 | G02 X68.421527 Y164.507229 Z-1.000000 I6.385110 J2.935335 203 | (Subpath end) 204 | G00 Z5.000000 205 | 206 | (End cutting path id: path4167 at depth: -1.0) 207 | 208 | (Start cutting path id: path4165 at depth: -1.0) 209 | (path id: path4165 at depth step: -1.0) 210 | (path len: 129.73458) 211 | G00 Z5.000000 212 | G00 X42.723869 Y176.140626 213 | (Subpath start) 214 | G01 Z-1.000000 F100.0(Penetrate) 215 | G01 X48.056877 Y176.140626 Z-1.000000 F400.000000 216 | G01 X49.979240 Y165.983107 Z-1.000000 217 | G01 X52.782171 Y176.140626 Z-1.000000 218 | G01 X58.102777 Y176.140626 Z-1.000000 219 | G01 X60.918107 Y165.983107 Z-1.000000 220 | G01 X62.840472 Y176.140626 Z-1.000000 221 | G01 X68.148674 Y176.140626 Z-1.000000 222 | G01 X64.142716 Y157.958790 Z-1.000000 223 | G01 X58.636078 Y157.958790 Z-1.000000 224 | G01 X55.448674 Y169.406155 Z-1.000000 225 | G01 X52.273674 Y157.958790 Z-1.000000 226 | G01 X46.767033 Y157.958790 Z-1.000000 227 | G01 X42.723869 Y176.140626 Z-1.000000 228 | (Subpath end) 229 | G00 Z5.000000 230 | 231 | (End cutting path id: path4165 at depth: -1.0) 232 | 233 | (Start cutting path id: path4155 at depth: -1.0) 234 | (path id: path4155 at depth step: -1.0) 235 | (path len: 98.59863) 236 | G00 Z5.000000 237 | G00 X44.621428 Y207.890626 238 | (Subpath start) 239 | G01 Z-1.000000 F100.0(Penetrate) 240 | G01 X50.239690 Y207.890626 Z-1.000000 F400.000000 241 | G01 X50.239690 Y201.528225 Z-1.000000 242 | G01 X56.378850 Y201.528225 Z-1.000000 243 | G01 X56.378850 Y207.890626 Z-1.000000 244 | G01 X62.021918 Y207.890626 Z-1.000000 245 | G01 X62.021918 Y189.708790 Z-1.000000 246 | G01 X56.378850 Y189.708790 Z-1.000000 247 | G01 X56.378850 Y197.063379 Z-1.000000 248 | G01 X50.239690 Y197.063379 Z-1.000000 249 | G01 X50.239690 Y189.708790 Z-1.000000 250 | G01 X44.621428 Y189.708790 Z-1.000000 251 | G01 X44.621428 Y207.890626 Z-1.000000 252 | (Subpath end) 253 | G00 Z5.000000 254 | 255 | (End cutting path id: path4155 at depth: -1.0) 256 | 257 | (Start cutting path id: path4157 at depth: -1.0) 258 | (path id: path4157 at depth step: -1.0) 259 | (path len: 13.20826) 260 | G00 Z5.000000 261 | G00 X74.932757 Y197.460254 262 | (Subpath start) 263 | G01 Z-1.000000 F100.0(Penetrate) 264 | G03 X74.607515 Y198.838901 Z-1.000000 I-5.421674 J-0.551363 F400.000000 265 | G03 X74.139007 Y199.556251 Z-1.000000 I-1.864612 J-0.706125 266 | G03 X73.433233 Y200.011923 Z-1.000000 I-1.410941 J-1.410947 267 | G03 X72.464690 Y200.188770 Z-1.000000 I-0.968543 J-2.563792 268 | G03 X71.387719 Y199.936001 Z-1.000000 I-0.000000 J-2.420701 269 | G03 X70.542325 Y199.233790 Z-1.000000 I1.062918 J-2.139641 270 | G03 X70.201874 Y198.591066 Z-1.000000 I1.706657 J-1.315546 271 | G03 X69.959418 Y197.460254 Z-1.000000 I5.175893 J-1.701159 272 | G01 X74.932757 Y197.460254 Z-1.000000 273 | (Subpath end) 274 | G00 Z5.000000 275 | 276 | (End cutting path id: path4157 at depth: -1.0) 277 | 278 | (Start cutting path id: path4157 at depth: -1.0) 279 | (path id: path4157 at depth step: -1.0) 280 | (path len: 66.86698) 281 | G00 Z5.000000 282 | G00 X80.067325 Y195.041799 283 | (Subpath start) 284 | G01 Z-1.000000 F100.0(Penetrate) 285 | G01 X69.947014 Y195.041799 Z-1.000000 F400.000000 286 | G03 X70.217732 Y193.885586 Z-1.000000 I5.002584 J0.561516 287 | G03 X70.604338 Y193.231056 Z-1.000000 I1.939142 J0.703934 288 | G03 X71.441707 Y192.604602 Z-1.000000 I1.812420 J1.549752 289 | G03 X72.514299 Y192.375296 Z-1.000000 I1.072593 J2.393908 290 | G03 X73.234302 Y192.468447 Z-1.000000 I-0.000000 J2.829164 291 | G03 X73.928168 Y192.747366 Z-1.000000 I-0.763939 J2.902969 292 | G03 X74.301337 Y193.024867 Z-1.000000 I-0.993815 J1.726096 293 | G03 X74.808732 Y193.578322 Z-1.000000 I-3.563521 J3.776267 294 | G01 X79.782072 Y193.119437 Z-1.000000 295 | G02 X78.443004 Y191.338716 Z-1.000000 I-7.875657 J4.528504 296 | G02 X77.028751 Y190.266895 Z-1.000000 I-4.146424 J4.002198 297 | G02 X75.349174 Y189.681374 Z-1.000000 I-2.744375 J5.170573 298 | G02 X72.402677 Y189.411136 Z-1.000000 I-2.946497 J15.928196 299 | G02 X69.837974 Y189.637252 Z-1.000000 I0.000000 J14.658024 300 | G02 X68.285100 Y190.142873 Z-1.000000 I1.031892 J5.806585 301 | G02 X66.951077 Y191.070267 Z-1.000000 I2.461380 J4.963778 302 | G02 X65.792229 Y192.486915 Z-1.000000 I4.816468 J5.122279 303 | G02 X65.079441 Y194.173987 Z-1.000000 I5.368025 J3.262103 304 | G02 X64.812443 Y196.269629 Z-1.000000 I8.090771 J2.095643 305 | G02 X65.355859 Y199.127623 Z-1.000000 I7.787260 J0.000000 306 | G02 X66.784418 Y201.267777 Z-1.000000 I5.666941 J-2.235841 307 | G02 X68.991764 Y202.624545 Z-1.000000 I4.207765 J-4.371710 308 | G02 X72.253850 Y203.177735 Z-1.000000 I3.262086 J-9.341438 309 | G02 X75.017133 Y202.915310 Z-1.000000 I0.000000 J-14.679582 310 | G02 X76.718693 Y202.321975 Z-1.000000 I-1.164675 J-6.076578 311 | G02 X78.157839 Y201.267629 Z-1.000000 I-2.753935 J-5.268389 312 | G02 X79.211565 Y199.841504 Z-1.000000 I-4.142206 J-4.162914 313 | G02 X79.812768 Y198.153858 Z-1.000000 I-5.577858 J-2.937952 314 | G02 X80.067325 Y195.612306 Z-1.000000 I-12.560451 J-2.541552 315 | G01 X80.067325 Y195.041799 Z-1.000000 316 | (Subpath end) 317 | G00 Z5.000000 318 | 319 | (End cutting path id: path4157 at depth: -1.0) 320 | 321 | (Start cutting path id: path4159 at depth: -1.0) 322 | (path id: path4159 at depth step: -1.0) 323 | (path len: 46.48399) 324 | G00 Z5.000000 325 | G00 X82.585001 Y207.890626 326 | (Subpath start) 327 | G01 Z-1.000000 F100.0(Penetrate) 328 | G01 X87.645158 Y207.890626 Z-1.000000 F400.000000 329 | G01 X87.645158 Y189.708790 Z-1.000000 330 | G01 X82.585001 Y189.708790 Z-1.000000 331 | G01 X82.585001 Y207.890626 Z-1.000000 332 | (Subpath end) 333 | G00 Z5.000000 334 | 335 | (End cutting path id: path4159 at depth: -1.0) 336 | 337 | (Start cutting path id: path4161 at depth: -1.0) 338 | (path id: path4161 at depth step: -1.0) 339 | (path len: 46.48398) 340 | G00 Z5.000000 341 | G00 X91.068206 Y207.890626 342 | (Subpath start) 343 | G01 Z-1.000000 F100.0(Penetrate) 344 | G01 X96.128360 Y207.890626 Z-1.000000 F400.000000 345 | G01 X96.128360 Y189.708790 Z-1.000000 346 | G01 X91.068206 Y189.708790 Z-1.000000 347 | G01 X91.068206 Y207.890626 Z-1.000000 348 | (Subpath end) 349 | G00 Z5.000000 350 | 351 | (End cutting path id: path4161 at depth: -1.0) 352 | 353 | (Start cutting path id: path4163 at depth: -1.0) 354 | (path id: path4163 at depth step: -1.0) 355 | (path len: 46.02642) 356 | G00 Z5.000000 357 | G00 X98.757658 Y196.257229 358 | (Subpath start) 359 | G01 Z-1.000000 F100.0(Penetrate) 360 | G02 X99.305404 Y199.037086 Z-1.000000 I7.327885 J0.000000 F400.000000 361 | G02 X100.791642 Y201.218165 Z-1.000000 I5.883491 J-2.412231 362 | G02 X103.051318 Y202.618179 Z-1.000000 I4.380783 J-4.547140 363 | G02 X106.285882 Y203.177735 Z-1.000000 I3.234564 J-9.069065 364 | G02 X109.936632 Y202.501186 Z-1.000000 I0.000000 J-10.188235 365 | G02 X112.263812 Y200.883302 Z-1.000000 I-2.127395 J-5.542729 366 | G02 X113.449123 Y198.869663 Z-1.000000 I-4.665872 J-4.102206 367 | G02 X113.888517 Y196.331642 Z-1.000000 I-7.110341 J-2.538021 368 | G02 X113.341301 Y193.525766 Z-1.000000 I-7.467233 J-0.000000 369 | G02 X111.866937 Y191.345902 Z-1.000000 I-5.864258 J2.377795 370 | G02 X109.630970 Y189.973597 Z-1.000000 I-4.244001 J4.407231 371 | G02 X106.298283 Y189.411136 Z-1.000000 I-3.332687 J9.592157 372 | G02 X103.288706 Y189.872547 Z-1.000000 I-0.000000 J10.045758 373 | G02 X101.163713 Y191.023440 Z-1.000000 I1.895957 J6.037896 374 | G02 X99.400053 Y193.321894 Z-1.000000 I4.172048 J5.027191 375 | G02 X98.757658 Y196.257229 Z-1.000000 I6.385110 J2.935335 376 | (Subpath end) 377 | G00 Z5.000000 378 | 379 | (End cutting path id: path4163 at depth: -1.0) 380 | 381 | (Start cutting path id: path4163 at depth: -1.0) 382 | (path id: path4163 at depth step: -1.0) 383 | (path len: 19.15381) 384 | G00 Z5.000000 385 | G00 X103.817815 Y196.269618 386 | (Subpath start) 387 | G01 Z-1.000000 F100.0(Penetrate) 388 | G03 X104.041724 Y194.589918 Z-1.000000 I6.412280 J0.000000 F400.000000 389 | G03 X104.524748 Y193.665128 Z-1.000000 I2.345913 J0.636748 390 | G03 X105.344683 Y193.039795 Z-1.000000 I1.855325 J1.582485 391 | G03 X106.323088 Y192.821769 Z-1.000000 I0.978405 J2.086305 392 | G03 X107.312191 Y193.040608 Z-1.000000 I-0.000000 J2.344680 393 | G03 X108.109027 Y193.652724 Z-1.000000 I-0.973119 J2.091485 394 | G03 X108.587714 Y194.567636 Z-1.000000 I-1.778977 J1.513454 395 | G03 X108.815959 Y196.319230 Z-1.000000 I-6.606904 J1.751594 396 | G03 X108.593604 Y197.948965 Z-1.000000 I-6.083691 J-0.000000 397 | G03 X108.109027 Y198.861711 Z-1.000000 I-2.313614 J-0.643297 398 | G03 X107.301699 Y199.489921 Z-1.000000 I-1.863711 J-1.562233 399 | G03 X106.360296 Y199.705070 Z-1.000000 I-0.941404 J-1.952029 400 | G03 X105.360358 Y199.482063 Z-1.000000 I-0.000000 J-2.353308 401 | G03 X104.537152 Y198.849307 Z-1.000000 I1.027032 J-2.188021 402 | G03 X104.043965 Y197.922601 Z-1.000000 I1.853747 J-1.581139 403 | G03 X103.817815 Y196.269618 Z-1.000000 I5.927939 J-1.652983 404 | (Subpath end) 405 | G00 Z5.000000 406 | 407 | (End cutting path id: path4163 at depth: -1.0) 408 | 409 | (Footer) 410 | M5 411 | G00 X0.0000 Y0.0000 412 | M2 413 | (Using default footer. To add your own footer create file "footer" in the output dir.) 414 | (end) 415 | -------------------------------------------------------------------------------- /sivicncdriver/ui/translate/SiviCNCDriver_fr_FR.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWindow 6 | 7 | 8 | SiviCNCDriver 9 | SiviCNCDriver 10 | 11 | 12 | 13 | File 14 | Fichier 15 | 16 | 17 | 18 | No file. 19 | Pas de fichier. 20 | 21 | 22 | 23 | Choose file 24 | Choisir un fichier 25 | 26 | 27 | 28 | Reload 29 | Recharger 30 | 31 | 32 | 33 | Close 34 | Fermer 35 | 36 | 37 | 38 | Redraw 39 | Redessiner 40 | 41 | 42 | 43 | Run preprocessor on file 44 | Lancer le préprocesseur sur le fichier 45 | 46 | 47 | 48 | Save as ... 49 | Enregistrer sous ... 50 | 51 | 52 | 53 | Save 54 | Enregister 55 | 56 | 57 | 58 | Display 59 | Affichage 60 | 61 | 62 | 63 | Draw axes 64 | Afficher les axes 65 | 66 | 67 | 68 | Draw steps 69 | Afficher les étapes 70 | 71 | 72 | 73 | Draw bounding box 74 | Afficher la boîte de délimitation 75 | 76 | 77 | 78 | Reverse X axis 79 | Inverser l'axe X 80 | 81 | 82 | 83 | Reverse Y axis 84 | Inverser l'axe Y 85 | 86 | 87 | 88 | Reverse Z axis 89 | Inverser l'axe Z 90 | 91 | 92 | 93 | Highlight current line 94 | Surligner la ligne courrante 95 | 96 | 97 | 98 | X size 99 | Développement selon X 100 | 101 | 102 | 103 | Y size 104 | Développement selon Y 105 | 106 | 107 | 108 | Z size 109 | Développement selon Z 110 | 111 | 112 | 113 | <html><head/><body><p>You can zoom with <span style=" font-weight:600;">Ctrl</span> + <span style=" font-weight:600;">+</span> and <span style=" font-weight:600;">Ctrl</span> + <span style=" font-weight:600;">-</span>. You can also move the view using the mouse and the keyboard arrows.</p></body></html> 114 | <html><head/><body><p>Vous pouvez zommer avec <span style=" font-weight:600;">Ctrl</span> + <span style=" font-weight:600;">+</span> et <span style=" font-weight:600;">Ctrl</span> + <span style=" font-weight:600;">-</span>. Vous pouvez également déplacer la vue avec le curseur et les flèches du clavier.</p></body></html> 115 | 116 | 117 | 118 | Serial port 119 | Port série 120 | 121 | 122 | 123 | Serial monitor 124 | Moniteur série 125 | 126 | 127 | 128 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 129 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 130 | p, li { white-space: pre-wrap; } 131 | </style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> 132 | <p style="-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html> 133 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 134 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 135 | p, li { white-space: pre-wrap; } 136 | </style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> 137 | <p style="-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html> 138 | 139 | 140 | 141 | Command : 142 | Commande : 143 | 144 | 145 | 146 | Send 147 | Envoyer 148 | 149 | 150 | 151 | EMERGENCY STOP 152 | ARRÊT D'URGENCE 153 | 154 | 155 | 156 | Send current file 157 | Envoyer le fichier 158 | 159 | 160 | 161 | Command 162 | Commande 163 | 164 | 165 | 166 | Manual command 167 | Commande manuelle 168 | 169 | 170 | 171 | X+ 172 | X+ 173 | 174 | 175 | 176 | Y+ 177 | Y+ 178 | 179 | 180 | 181 | Z+ 182 | Z+ 183 | 184 | 185 | 186 | Z- 187 | Z- 188 | 189 | 190 | 191 | Y- 192 | Y- 193 | 194 | 195 | 196 | X- 197 | X- 198 | 199 | 200 | 201 | Go to origin 202 | Aller à l'origine 203 | 204 | 205 | 206 | Automatic controls 207 | Commandes automatiques 208 | 209 | 210 | 211 | Automatic controls allow you to send commands in step in order to set the machine. 212 | Les commandes automatiques vous permettent d'envoyer des commandes directement en pas afin de régler la machine. 213 | 214 | 215 | 216 | Axis 217 | L'axe 218 | 219 | 220 | 221 | X 222 | X 223 | 224 | 225 | 226 | Y 227 | Y 228 | 229 | 230 | 231 | Z 232 | Z 233 | 234 | 235 | 236 | does 237 | effectue 238 | 239 | 240 | 241 | go 242 | aller 243 | 244 | 245 | 246 | round trip 247 | aller(s)-retour(s) 248 | 249 | 250 | 251 | of 252 | de 253 | 254 | 255 | 256 | steps. 257 | pas. 258 | 259 | 260 | 261 | Between each stage, axis 262 | Entre chaque étape, l'axe 263 | 264 | 265 | 266 | move by 267 | bouge de 268 | 269 | 270 | 271 | Custom controls 272 | Commandes personnalisées 273 | 274 | 275 | 276 | Custom controls allow you to send user-defined commands repetitively. 277 | Les commandes personnalisées permettent d'envoyer de manière répétitive un ensemble de commandes. 278 | 279 | 280 | 281 | Repeat 282 | Répéter 283 | 284 | 285 | 286 | times the commands : 287 | fois les commandes : 288 | 289 | 290 | 291 | Set origin 292 | Définir l'origine 293 | 294 | 295 | 296 | Configuration 297 | Configuration 298 | 299 | 300 | 301 | Configuration : 302 | Configuration : 303 | 304 | 305 | 306 | New configuration 307 | Nouvelle configuration 308 | 309 | 310 | 311 | Y axis 312 | Axe Y 313 | 314 | 315 | 316 | Driving mode 317 | Mode de pilotage 318 | 319 | 320 | 321 | Normal 322 | Normal 323 | 324 | 325 | 326 | Full torque 327 | Couple maximal 328 | 329 | 330 | 331 | Half step 332 | Demi pas 333 | 334 | 335 | 336 | Ratio 337 | Ratio 338 | 339 | 340 | 341 | mm/step 342 | mm/pas 343 | 344 | 345 | 346 | Play 347 | Jeu 348 | 349 | 350 | 351 | mm 352 | mm 353 | 354 | 355 | 356 | Reverse axis 357 | Inverser l'axe 358 | 359 | 360 | 361 | X axis 362 | Axe X 363 | 364 | 365 | 366 | mm 367 | mm 368 | 369 | 370 | 371 | Send configuration 372 | Envoyer la configuration 373 | 374 | 375 | 376 | Z axis 377 | Axe Z 378 | 379 | 380 | 381 | Emulate a serial port 382 | Émuler un port série 383 | 384 | 385 | 386 | List serial ports 387 | Lister les ports série 388 | 389 | 390 | 391 | Baudrate 392 | Baudrate 393 | 394 | 395 | 396 | 300 397 | 300 398 | 399 | 400 | 401 | 1200 402 | 1200 403 | 404 | 405 | 406 | 2400 407 | 2400 408 | 409 | 410 | 411 | 4800 412 | 4800 413 | 414 | 415 | 416 | 9600 417 | 9600 418 | 419 | 420 | 421 | 19200 422 | 19200 423 | 424 | 425 | 426 | 38400 427 | 38400 428 | 429 | 430 | 431 | 57600 432 | 57600 433 | 434 | 435 | 436 | 115200 437 | 115200 438 | 439 | 440 | 441 | 230400 442 | 230400 443 | 444 | 445 | 446 | 250000 447 | 250000 448 | 449 | 450 | 451 | Connect 452 | Connecter 453 | 454 | 455 | 456 | <html><head/><body><p>SiviCNCDriver is provided with &lt;3 by Klafyvel from <a href="http://sivigik.com"><span style=" text-decoration: underline; color:#4c6b8a;">sivigik.com</span></a> .</p></body></html> 457 | <html><head/><body><p>SiviCNCDriver est fournis avec des &lt;3 par Klafyvel de <a href="http://sivigik.com"><span style=" text-decoration: underline; color:#4c6b8a;">sivigik.com</span></a> .</p></body></html> 458 | 459 | 460 | 461 | License 462 | Licence 463 | 464 | 465 | 466 | About Qt 467 | À propos de Qt 468 | 469 | 470 | 471 | Select file 472 | Sélectionner un fichier 473 | 474 | 475 | 476 | Disconnect 477 | Déconnecter 478 | 479 | 480 | 481 | Error. 482 | Erreur. 483 | 484 | 485 | 486 | An error occurred during parsing. 487 | Une erreur s'est produite lors de l'analyse. 488 | 489 | 490 | 491 | Minimal pulse period 492 | Période minimale des impulsions 493 | 494 | 495 | 496 | ms 497 | ms 498 | 499 | 500 | 501 | step/mm 502 | pas/mm 503 | 504 | 505 | 506 | <html><head/><body><p>You can control the tilt of the figure with the left button of the mouse and zoom with the right button.</p></body></html> 507 | <html><head/><body><p>Vous pouvez contrôler la rotation de la figure avec le bouton gauche de la souris et zoomer avec le bouton droit.</p></body></html> 508 | 509 | 510 | 511 | Timeout 512 | Timeout 513 | 514 | 515 | 516 | Cancel sending 517 | Annuler l'envoi 518 | 519 | 520 | 521 | View3D 522 | 523 | 524 | X Axis 525 | Axe X 526 | 527 | 528 | 529 | Y Axis 530 | Axe Y 531 | 532 | 533 | 534 | Z Axis 535 | Axe Z 536 | 537 | 538 | 539 | dialog 540 | 541 | 542 | Parameters 543 | Paramètres 544 | 545 | 546 | 547 | Remove numbering (NXX) 548 | Supprimer la numérotation (NXXX) 549 | 550 | 551 | 552 | Remove comments 553 | Supprimer les commentaires 554 | 555 | 556 | 557 | Minimize bounding box 558 | Minimiser l'aire d'encadrement 559 | 560 | 561 | 562 | Run preprocessor 563 | Lancer le préprocesseur 564 | 565 | 566 | 567 | Output 568 | Sortie 569 | 570 | 571 | 572 | Preprocessor 573 | Préprocesseur 574 | 575 | 576 | 577 | -------------------------------------------------------------------------------- /sivicncdriver/ui/interface.py: -------------------------------------------------------------------------------- 1 | """ 2 | The interface module 3 | ==================== 4 | 5 | Provides the MainWindow class. 6 | """ 7 | 8 | import serial 9 | from math import atan2, sqrt, pi 10 | import os 11 | import json 12 | 13 | from PyQt5.QtCore import * 14 | from PyQt5.QtGui import * 15 | from PyQt5 import QtGui 16 | from PyQt5 import QtCore 17 | from PyQt5.QtWidgets import * 18 | 19 | from sivicncdriver import settings 20 | from sivicncdriver.settings import logger 21 | from sivicncdriver.gcode.gcode import parse 22 | from sivicncdriver.serial.serial_list import serial_ports 23 | from sivicncdriver.serial.serial_manager import SerialManager 24 | from sivicncdriver.ui.preprocessor import PreprocessorDialog 25 | from sivicncdriver.gcode.arc_calculator import arc_to_segments 26 | from sivicncdriver.serial.thread_send import SendThread 27 | from sivicncdriver.serial.thread_read import ReadThread 28 | import sivicncdriver.gcode.gcode_maker as gcode_maker 29 | 30 | from sivicncdriver.ui.main_window import Ui_MainWindow 31 | 32 | __all__ = ['MainWindow'] 33 | 34 | _translate = QtCore.QCoreApplication.translate 35 | 36 | 37 | class MainWindow(QMainWindow, Ui_MainWindow): 38 | """ 39 | The main window of the application. 40 | """ 41 | 42 | def __init__(self): 43 | """ 44 | The __init__ method. 45 | 46 | It will set the UI up, list available serial ports, list available 47 | configurations, connect the UI and so on. 48 | """ 49 | super(MainWindow, self).__init__() 50 | self.setupUi(self) 51 | self.zoom = 1 52 | self.list_serials() 53 | self.list_configs() 54 | 55 | self.set_serial_mode("manual") 56 | 57 | self.serial_manager = SerialManager(serial.Serial(timeout=0)) 58 | 59 | s = """Welcome to SiviCNCDriver, by Klafyvel from sivigik.com""" 60 | self.print(s, msg_type="info") 61 | 62 | self.connectUi() 63 | self.update_config(self.config_list.currentIndex()) 64 | 65 | self.baudrate.addItems( 66 | map(str, serial.serialutil.SerialBase.BAUDRATES)) 67 | self.baudrate.setCurrentIndex(12) 68 | 69 | self.file_loaded = False 70 | 71 | self.send_thread = None 72 | self.waiting_cmd = [] 73 | 74 | self.last_selected_path = None 75 | 76 | self.read_thread = ReadThread() 77 | 78 | def connectUi(self): 79 | """ 80 | Connects The UI signals and slots. 81 | """ 82 | logger.debug("Connecting Ui.") 83 | self.btn_serial_ports_list.clicked.connect(self.list_serials) 84 | 85 | self.btn_y_plus.pressed.connect(self.start_continuous_y_forward) 86 | self.btn_y_minus.pressed.connect(self.start_continuous_y_backward) 87 | self.btn_x_plus.pressed.connect(self.start_continuous_x_forward) 88 | self.btn_x_minus.pressed.connect(self.start_continuous_x_backward) 89 | self.btn_z_plus.pressed.connect(self.start_continuous_z_forward) 90 | self.btn_z_minus.pressed.connect(self.start_continuous_z_backward) 91 | self.btn_y_plus.released.connect(self.stop_y) 92 | self.btn_y_minus.released.connect(self.stop_y) 93 | self.btn_x_plus.released.connect(self.stop_x) 94 | self.btn_x_minus.released.connect(self.stop_x) 95 | self.btn_z_plus.released.connect(self.stop_z) 96 | self.btn_z_minus.released.connect(self.stop_z) 97 | self.btn_set_origin.clicked.connect(self.set_origin) 98 | self.btn_go_to_zero.clicked.connect(self.goto_origin) 99 | self.btn_emergency_stop.clicked.connect(self.emergency_stop) 100 | 101 | self.auto_cmd_type.currentIndexChanged.connect( 102 | self.manage_auto_cmd_number) 103 | self.btn_run_auto_cmd.clicked.connect(self.auto_cmd) 104 | self.manage_auto_cmd_number(self.auto_cmd_type.currentIndex()) 105 | 106 | self.btn_run_custom_cmd.clicked.connect(self.run_custom_cmd) 107 | 108 | self.chk_fake_serial.stateChanged.connect( 109 | self.manage_emulate_serial_port) 110 | self.btn_send_current_file.clicked.connect(self.send_file) 111 | self.btn_command.clicked.connect(self.send_cmd) 112 | 113 | self.config_list.currentIndexChanged.connect(self.update_config) 114 | self.btn_save_config.clicked.connect(self.save_config) 115 | self.btn_save_config_as.clicked.connect(self.save_config_as) 116 | self.btn_send_config.clicked.connect(self.send_config) 117 | 118 | self.btn_connect.clicked.connect(self.manage_connection) 119 | 120 | self.btn_file.clicked.connect(self.choose_file) 121 | self.btn_reload.clicked.connect(self.load_file) 122 | self.btn_save_file.clicked.connect(self.save_file) 123 | self.btn_save_as.clicked.connect(self.save_file_as) 124 | self.redraw.clicked.connect(self.draw_file) 125 | self.btn_close.clicked.connect(self.close_file) 126 | 127 | self.serial_manager.send_print.connect(self.print) 128 | 129 | self.btn_preprocessor.clicked.connect(self.run_preprocessor) 130 | 131 | self.reverse_display_x.clicked.connect(self.update_drawing) 132 | self.reverse_display_y.clicked.connect(self.update_drawing) 133 | self.reverse_display_z.clicked.connect(self.update_drawing) 134 | 135 | self.btn_license.clicked.connect(self.about_license) 136 | self.btn_about_qt.clicked.connect(self.about_qt) 137 | 138 | self.code_edit.cursorPositionChanged.connect( 139 | self.highlight_selected_path) 140 | 141 | self.view_3D.parse_error.connect(self.parse_error) 142 | 143 | self.serial_manager.serial_fatal_error.connect(self.manage_connection) 144 | 145 | def list_serials(self): 146 | """ 147 | Lists available serials ports. 148 | """ 149 | logger.debug("Listing available serial ports.") 150 | l = serial_ports() 151 | for i in range(self.serial_ports_list.count()): 152 | self.serial_ports_list.removeItem(0) 153 | for i in l: 154 | logger.info("Found {}".format(i)) 155 | self.serial_ports_list.addItem(i) 156 | 157 | def list_configs(self): 158 | """ 159 | Lists available configurations. 160 | """ 161 | for _ in range(self.config_list.count()): 162 | self.config_list.removeItem(0) 163 | logger.debug("Listing available configurations.") 164 | for f in os.listdir(settings.CONFIG_DIR): 165 | if f.endswith(".json"): 166 | logger.debug("Found {}".format(f)) 167 | self.config_list.addItem(f[:-5]) 168 | self.config_list.addItem(_translate("MainWindow", "New configuration")) 169 | 170 | def set_serial_mode(self, mode): 171 | """ 172 | Change serial mode. 173 | 174 | :param mode: can be "manual" or "file" 175 | :type mode: str 176 | """ 177 | if mode == "manual": 178 | logger.debug("Setting manual mode.") 179 | self.btn_set_origin.setEnabled(True) 180 | self.grp_cmd.setEnabled(True) 181 | self.grp_auto.setEnabled(True) 182 | # self.btn_y_plus.pressed.connect() 183 | elif mode == "file": 184 | logger.debug("Setting file mode.") 185 | self.btn_set_origin.setEnabled(False) 186 | self.grp_cmd.setEnabled(False) 187 | self.grp_auto.setEnabled(False) 188 | 189 | def reset_config(self): 190 | """ 191 | Resets the configuration. 192 | """ 193 | self.drive_x.setCurrentIndex(0) 194 | self.drive_y.setCurrentIndex(0) 195 | self.drive_z.setCurrentIndex(0) 196 | 197 | self.ratio_x.setValue(1) 198 | self.ratio_y.setValue(1) 199 | self.ratio_z.setValue(1) 200 | 201 | self.play_x.setValue(0) 202 | self.play_y.setValue(0) 203 | self.play_z.setValue(0) 204 | 205 | self.reverse_x.setChecked(False) 206 | self.reverse_y.setChecked(False) 207 | self.reverse_z.setChecked(False) 208 | 209 | self.minTime_x.setValue(5) 210 | self.minTime_y.setValue(5) 211 | self.minTime_z.setValue(5) 212 | 213 | logger.info("Reset config.") 214 | 215 | def config_as_dict(self): 216 | """ 217 | Get the configuration as a dict. 218 | 219 | :return: The configuration as a dict. 220 | :rtype: dict 221 | """ 222 | return { 223 | "x_drive": self.drive_x.currentIndex(), 224 | "x_ratio": self.ratio_x.value(), 225 | "x_play": self.play_x.value(), 226 | "x_reverse": bool(self.reverse_x.checkState()), 227 | "x_min_time": self.minTime_x.value(), 228 | "y_drive": self.drive_y.currentIndex(), 229 | "y_ratio": self.ratio_y.value(), 230 | "y_play": self.play_y.value(), 231 | "y_reverse": bool(self.reverse_y.checkState()), 232 | "y_min_time": self.minTime_y.value(), 233 | "z_drive": self.drive_z.currentIndex(), 234 | "z_ratio": self.ratio_z.value(), 235 | "z_play": self.play_z.value(), 236 | "z_reverse": bool(self.reverse_z.checkState()), 237 | "z_min_time": self.minTime_z.value(), 238 | } 239 | 240 | def run_thread(self, gcode, n=None, disable=True, allow_waiting=True): 241 | """ 242 | Run a thread to send the given gcode. 243 | 244 | :param gcode: The gcode as a list of commands. 245 | :param n: A length for the sending_process progress bar. 246 | :param disable: Disable ui elements which trigger sending. 247 | :param allow_waiting: Adds the command to the waiting queue. 248 | 249 | :type gcode: list 250 | :type n: int 251 | :type disable: bool 252 | :type allow_waiting: bool 253 | """ 254 | if self.send_thread and allow_waiting: 255 | logger.info("Thread already in use, waiting for the end.") 256 | self.waiting_cmd.append( 257 | {"gcode": gcode, "n": n, "disable": disable}) 258 | return 259 | elif self.send_thread: 260 | self.send_thread.stop() 261 | try: 262 | self.serial_manager.serial.flush() 263 | except : 264 | pass 265 | 266 | self.send_thread = SendThread(self.serial_manager, gcode) 267 | if n: 268 | self.sending_progress.setMaximum(n) 269 | else: 270 | self.sending_progress.setMaximum(len(gcode)) 271 | self.sending_progress.setValue(0) 272 | self.btn_send_current_file.setText( 273 | _translate("MainWindow", "Cancel sending")) 274 | self.btn_send_current_file.clicked.disconnect() 275 | self.btn_send_current_file.clicked.connect(self.send_thread.stop) 276 | 277 | if disable: 278 | self.tabWidget.setEnabled(False) 279 | 280 | self.send_thread.read_allowed.connect( 281 | self.read_thread.set_read_allowed) 282 | self.serial_manager.send_confirm.connect(self.send_thread.confirm) 283 | 284 | self.send_thread.finished.connect(self.sending_end) 285 | self.send_thread.update_progress.connect(self.update_progress) 286 | self.send_thread.start() 287 | 288 | # Slots 289 | @pyqtSlot(str, str) 290 | def print(self, txt, msg_type="operator"): 291 | """ 292 | Prints a message on the application console. 293 | 294 | :param txt: The message 295 | :param msg_type: The type of the message. Can be "operator", "machine", 296 | "error" or "info" 297 | :type txt: str 298 | :type msg_type: str 299 | """ 300 | 301 | msg = "{}" 302 | if msg_type == "operator": 303 | msg = ( 304 | "\n
" 305 | ">>> {}" 306 | ) 307 | elif msg_type == "machine": 308 | msg = "\n
{}" 309 | elif msg_type == "error": 310 | msg = ( 311 | "\n
" 312 | "{}" 313 | ) 314 | elif msg_type == "info": 315 | msg = ( 316 | "\n
" 317 | "{}" 318 | ) 319 | self.serial_monitor.moveCursor(QTextCursor.End) 320 | self.serial_monitor.insertHtml(msg.format(txt)) 321 | self.serial_monitor.moveCursor(QTextCursor.End) 322 | 323 | @pyqtSlot(int) 324 | def manage_auto_cmd_number(self, n): 325 | """ 326 | Enable the widgets for auto commands 327 | """ 328 | self.auto_cmd_number.setValue(1) 329 | self.auto_cmd_number.setEnabled(n != 1) 330 | self.auto_cmd_2.setEnabled(n != 1) 331 | 332 | @pyqtSlot() 333 | def auto_cmd(self): 334 | """ 335 | Sends auto commands using a thread if they are too long. 336 | """ 337 | logger.info("Sending auto command.") 338 | self.print("Sending auto command.", "info") 339 | 340 | axis = self.auto_cmd_axis.currentText() 341 | n = self.auto_cmd_number.value() 342 | step = self.auto_cmd_step.value() 343 | 344 | if self.auto_cmd_type.currentIndex() == 1: 345 | it = [gcode_maker.step(axis, step)] 346 | else: 347 | axis2 = self.auto_cmd_axis_2.currentText() 348 | step2 = self.auto_cmd_step_2.value() 349 | 350 | # AKA "Do not do this at home kids" : 351 | it = ( 352 | ( 353 | ( 354 | gcode_maker.step(axis, step), 355 | gcode_maker.step(axis, -step) 356 | )[(i % 4) >= 2], 357 | gcode_maker.step(axis2, step2) 358 | )[i % 2] for i in range(4*n-1) 359 | ) 360 | 361 | self.run_thread(it, n) 362 | 363 | @pyqtSlot() 364 | def run_custom_cmd(self): 365 | """ 366 | Sends a custom command using a thread. 367 | """ 368 | logger.info("Sending custom command.") 369 | self.print("Sending custom command.", "info") 370 | gcode = self.custom_cmd.toPlainText().split('\n') 371 | n = self.custom_cmd_number.value() 372 | l = len(gcode) 373 | 374 | it = (gcode[i % l] for i in range(n*l)) 375 | 376 | self.run_thread(it, n*l) 377 | 378 | @pyqtSlot() 379 | def send_file(self): 380 | """ 381 | Send a file using a different thread. 382 | """ 383 | logger.info("Sending file.") 384 | self.print("Sending file.", "info") 385 | gcode = self.code_edit.toPlainText().split('\n') 386 | self.run_thread(gcode) 387 | 388 | @pyqtSlot() 389 | def send_cmd(self): 390 | """ 391 | Sends an user command using a thread. 392 | """ 393 | gcode = [self.command_edit.text()] 394 | logger.info("Sending command.") 395 | self.print("Sending command.", "info") 396 | self.run_thread(gcode) 397 | 398 | @pyqtSlot() 399 | def send_config(self): 400 | """ 401 | Send a configuration to the machine. 402 | """ 403 | self.save_config() 404 | gcode = gcode_maker.config_as_gcode( 405 | **self.config_as_dict()).split('\n') 406 | logger.info("Sending configuration.") 407 | self.print("Sending configuration.", "info") 408 | self.run_thread(gcode) 409 | 410 | @pyqtSlot() 411 | def sending_end(self): 412 | """ 413 | Manages the end of upload. If some commands are waiting, run them at the 414 | end. 415 | """ 416 | 417 | self.send_thread.read_allowed.disconnect() 418 | self.serial_manager.send_confirm.disconnect() 419 | 420 | if self.send_thread and self.send_thread.user_stop: 421 | self.print("Stopped by user.", "error") 422 | logger.error("Upload stopped by user.") 423 | elif self.send_thread and self.send_thread.error: 424 | self.print("Error while sending.", "error") 425 | logger.error("Error while sending.") 426 | else: 427 | self.print("Done.", "info") 428 | logger.info("Upload done.") 429 | self.sending_progress.setValue(0) 430 | self.btn_send_current_file.setText( 431 | _translate("MainWindow", "Send current file")) 432 | self.btn_send_current_file.clicked.disconnect() 433 | self.btn_send_current_file.clicked.connect(self.send_file) 434 | self.tabWidget.setEnabled(True) 435 | 436 | self.send_thread = None 437 | 438 | if len(self.waiting_cmd) > 0: 439 | self.run_thread(**self.waiting_cmd.pop(0)) 440 | 441 | @pyqtSlot() 442 | def start_continuous_y_forward(self): 443 | self.run_thread( 444 | [gcode_maker.start_continuous_y_forward()], disable=False) 445 | 446 | @pyqtSlot() 447 | def start_continuous_y_backward(self): 448 | self.run_thread( 449 | [gcode_maker.start_continuous_y_backward()], disable=False) 450 | 451 | @pyqtSlot() 452 | def start_continuous_x_forward(self): 453 | self.run_thread( 454 | [gcode_maker.start_continuous_x_forward()], disable=False) 455 | 456 | @pyqtSlot() 457 | def start_continuous_x_backward(self): 458 | self.run_thread( 459 | [gcode_maker.start_continuous_x_backward()], disable=False) 460 | 461 | @pyqtSlot() 462 | def start_continuous_z_forward(self): 463 | self.run_thread( 464 | [gcode_maker.start_continuous_z_forward()], disable=False) 465 | 466 | @pyqtSlot() 467 | def start_continuous_z_backward(self): 468 | self.run_thread( 469 | [gcode_maker.start_continuous_z_backward()], disable=False) 470 | 471 | @pyqtSlot() 472 | def stop_y(self): 473 | self.run_thread([gcode_maker.stop_y()]) 474 | 475 | @pyqtSlot() 476 | def stop_x(self): 477 | self.run_thread([gcode_maker.stop_x()]) 478 | 479 | @pyqtSlot() 480 | def stop_z(self): 481 | self.run_thread([gcode_maker.stop_z()]) 482 | 483 | @pyqtSlot() 484 | def emergency_stop(self): 485 | self.run_thread([gcode_maker.emergency_stop()], allow_waiting=False) 486 | 487 | @pyqtSlot() 488 | def set_origin(self): 489 | self.run_thread([gcode_maker.set_origin()]) 490 | 491 | @pyqtSlot() 492 | def goto_origin(self): 493 | self.run_thread([gcode_maker.goto_origin()]) 494 | 495 | @pyqtSlot(int) 496 | def update_progress(self, s): 497 | """ 498 | Updates the progress bar. 499 | """ 500 | self.sending_progress.setValue(s) 501 | 502 | @pyqtSlot(int) 503 | def manage_emulate_serial_port(self, s): 504 | """ 505 | Enable widgets for serial port emulation. 506 | """ 507 | st = bool(s) 508 | self.serial_manager.fake_mode = st 509 | self.baudrate.setEnabled(not st) 510 | self.serial_ports_list.setEnabled(not st) 511 | self.btn_serial_ports_list.setEnabled(not st) 512 | self.btn_connect.setEnabled(not st) 513 | self.timeout.setEnabled(not st) 514 | if st: 515 | self.print("Emulating serial port.", "info") 516 | self.read_thread = ReadThread() 517 | self.read_thread.read.connect(self.serial_manager.readMsg) 518 | self.read_thread.start() 519 | else: 520 | self.print("Exiting serial port emulation.", "info") 521 | self.read_thread.read.disconnect() 522 | self.read_thread.stop() 523 | 524 | @pyqtSlot(int) 525 | def update_config(self, i): 526 | """ 527 | Updates the configuration widgets. 528 | """ 529 | nb_config = self.config_list.count() 530 | if i == nb_config-1: 531 | self.reset_config() 532 | else: 533 | file = self.config_list.currentText() + ".json" 534 | file = os.path.join(settings.CONFIG_DIR, file) 535 | logger.info("Loading config {}".format(file)) 536 | config = {} 537 | with open(file) as f: 538 | config = json.load(f) 539 | self.drive_x.setCurrentIndex(config.get("x_drive", 0)) 540 | self.drive_y.setCurrentIndex(config.get("y_drive", 0)) 541 | self.drive_z.setCurrentIndex(config.get("z_drive", 0)) 542 | 543 | self.ratio_x.setValue(config.get("x_ratio", 1)) 544 | self.ratio_y.setValue(config.get("y_ratio", 1)) 545 | self.ratio_z.setValue(config.get("z_ratio", 1)) 546 | 547 | self.play_x.setValue(config.get("x_play", 0)) 548 | self.play_y.setValue(config.get("y_play", 0)) 549 | self.play_z.setValue(config.get("z_play", 0)) 550 | 551 | self.reverse_x.setChecked(config.get("x_reverse", False)) 552 | self.reverse_y.setChecked(config.get("y_reverse", False)) 553 | self.reverse_z.setChecked(config.get("z_reverse", False)) 554 | 555 | self.minTime_x.setValue(config.get("x_min_time", 5)) 556 | self.minTime_y.setValue(config.get("y_min_time", 5)) 557 | self.minTime_z.setValue(config.get("z_min_time", 5)) 558 | 559 | @pyqtSlot() 560 | def save_config(self, filename=None): 561 | """ 562 | Saves a configuration. 563 | 564 | :param filename: The name of the file. 565 | :type filename: str 566 | """ 567 | logger.info("Saving configuration.") 568 | logger.debug("Filename given : {}".format(filename)) 569 | current_config = self.config_list.currentIndex() 570 | nb_config = self.config_list.count() 571 | if current_config == nb_config-1 and not filename: 572 | self.save_config_as() 573 | else: 574 | if not filename: 575 | file = self.config_list.currentText() + ".json" 576 | file = os.path.join(settings.CONFIG_DIR, file) 577 | else: 578 | file = filename 579 | config = self.config_as_dict() 580 | with open(file, "w") as f: 581 | json.dump(config, f) 582 | 583 | @pyqtSlot() 584 | def save_config_as(self): 585 | """ 586 | Saves a configuration in a new file. 587 | """ 588 | f = QFileDialog.getSaveFileName( 589 | self, _translate("MainWindow", "Select file"), 590 | directory=settings.CONFIG_DIR, 591 | filter='JSON files (*.json)\nAll files (*)')[0] 592 | if f is not '': 593 | if not f.endswith(".json"): 594 | f = f + ".json" 595 | logger.info("Saving configuration as {}".format(f)) 596 | self.save_config(f) 597 | self.list_configs() 598 | self.update_config(self.config_list.currentIndex()) 599 | 600 | @pyqtSlot() 601 | def manage_connection(self): 602 | """ 603 | Manages the connection widgets. 604 | """ 605 | if self.serial_manager.is_open: 606 | if self.send_thread: 607 | self.emergency_stop() 608 | self.read_thread.read.disconnect() 609 | self.read_thread.stop() 610 | self.baudrate.setEnabled(True) 611 | self.timeout.setEnabled(True) 612 | self.serial_ports_list.setEnabled(True) 613 | self.btn_serial_ports_list.setEnabled(True) 614 | self.btn_connect.setText(_translate("MainWindow", "Connect")) 615 | self.serial_manager.close() 616 | else: 617 | port = self.serial_ports_list.currentText() 618 | baudrate = int(self.baudrate.currentText()) 619 | timeout = self.timeout.value() 620 | if self.serial_manager.open(baudrate, port, timeout): 621 | self.read_thread = ReadThread() 622 | self.baudrate.setEnabled(False) 623 | self.timeout.setEnabled(False) 624 | self.serial_ports_list.setEnabled(False) 625 | self.btn_serial_ports_list.setEnabled(False) 626 | self.btn_connect.setText( 627 | _translate("MainWindow", "Disconnect")) 628 | self.read_thread.read.connect(self.serial_manager.readMsg) 629 | self.read_thread.start() 630 | 631 | @pyqtSlot() 632 | def choose_file(self): 633 | """ 634 | Sets the gcode file. 635 | """ 636 | if not self.file_loaded: 637 | directory = settings.FILE_DIR 638 | else: 639 | directory = os.path.dirname(self.filename.text()) 640 | file = QFileDialog.getOpenFileName( 641 | self, _translate("MainWindow", "Select file"), 642 | directory=directory, 643 | filter='GCode files (*.gcode, *.ngc)\nAll files (*)')[0] 644 | 645 | if file is not '': 646 | self.filename.setText(file) 647 | self.load_file() 648 | 649 | @pyqtSlot() 650 | def load_file(self): 651 | """ 652 | Loads a gcode file. 653 | """ 654 | file = self.filename.text() 655 | try: 656 | logger.info("Loading {}".format(repr(file))) 657 | with open(file) as f: 658 | gcode = f.read() 659 | self.draw_file(gcode) 660 | self.code_edit.setText(gcode) 661 | self.file_loaded = True 662 | except FileNotFoundError: 663 | self.choose_file() 664 | 665 | @pyqtSlot() 666 | def save_file_as(self): 667 | """ 668 | Saves a gcode file in a nex file. 669 | """ 670 | if not self.file_loaded: 671 | directory = settings.FILE_DIR 672 | else: 673 | directory = os.path.dirname(self.filename.text()) 674 | file = QFileDialog.getSaveFileName( 675 | self, _translate("MainWindow", "Select file"), 676 | directory=directory, 677 | filter='GCode files (*.gcode, *.ngc)\nAll files (*)')[0] 678 | if file is not '': 679 | logger.info("Saving {}".format(repr(file))) 680 | self.filename.setText(file) 681 | with open(file, 'w') as f: 682 | f.write(self.code_edit.toPlainText()) 683 | self.file_loaded = True 684 | 685 | @pyqtSlot() 686 | def save_file(self): 687 | """ 688 | Saves a gcode file. 689 | """ 690 | if not self.file_loaded: 691 | self.save_file_as() 692 | else: 693 | file = self.filename.text() 694 | logger.info("Saving {}".format(repr(file))) 695 | with open(file, 'w') as f: 696 | f.write(self.code_edit.toPlainText()) 697 | 698 | @pyqtSlot() 699 | def close_file(self): 700 | """ 701 | Close the current file. 702 | """ 703 | self.filename.setText(_translate("MainWindow", "No file.")) 704 | self.code_edit.setText("") 705 | self.draw_file() 706 | 707 | @pyqtSlot() 708 | def update_drawing(self, highlight_line=None): 709 | """ 710 | Updates the drawing. 711 | 712 | :param highlight_line: A line which is to be highlighted. 713 | :type highlight_line: int 714 | """ 715 | self.view_3D.draw( 716 | reverse_x=self.reverse_display_x.isChecked(), 717 | reverse_y=self.reverse_display_y.isChecked(), 718 | reverse_z=self.reverse_display_z.isChecked(), 719 | highlight_line=highlight_line, 720 | ) 721 | 722 | @pyqtSlot(int) 723 | def parse_error(self, line): 724 | """ 725 | Handles parsing errors. 726 | 727 | :param line: The line where the error occurred. 728 | """ 729 | self.chk_display_current_line.setChecked(False) 730 | self.code_edit.setExtraSelections([]) 731 | QMessageBox.critical( 732 | self, 733 | _translate("MainWindow", "Error."), 734 | _translate("MainWindow", "An error occurred during parsing.") 735 | ) 736 | logger.error("While parsing line {}".format(line)) 737 | highlight = QTextEdit.ExtraSelection() 738 | highlight.cursor = QTextCursor( 739 | self.code_edit.document().findBlockByLineNumber(line)) 740 | highlight.format.setProperty(QTextFormat.FullWidthSelection, True) 741 | highlight.format.setBackground(Qt.red) 742 | self.code_edit.setTextCursor(highlight.cursor) 743 | self.code_edit.setExtraSelections([highlight]) 744 | 745 | @pyqtSlot() 746 | def draw_file(self, gcode=None): 747 | """ 748 | Draws a gcode file. 749 | 750 | :param gcode: gcode to use in place of the one form code_edit. 751 | """ 752 | if not gcode: 753 | gcode = self.code_edit.toPlainText() 754 | self.view_3D.compute_data(gcode) 755 | 756 | bounds = self.view_3D.get_bounds() 757 | self.space_x.display(bounds['max_x'] - bounds['min_x']) 758 | self.space_y.display(bounds['max_y'] - bounds['min_y']) 759 | self.space_z.display(bounds['max_z'] - bounds['min_z']) 760 | 761 | self.update_drawing() 762 | 763 | @pyqtSlot() 764 | def run_preprocessor(self): 765 | """ 766 | Runs the preprocessor dialog. 767 | """ 768 | self.preprocessor = PreprocessorDialog(self.code_edit.toPlainText()) 769 | self.preprocessor.accepted.connect(self.end_preprocessor) 770 | self.preprocessor.show() 771 | 772 | @pyqtSlot() 773 | def end_preprocessor(self): 774 | """ 775 | Manages the end of the preprocessing interface. 776 | """ 777 | self.code_edit.setText(self.preprocessor.gcode) 778 | self.preprocessor.accepted.disconnect() 779 | self.draw_file() 780 | 781 | @pyqtSlot() 782 | def about_license(self): 783 | """ 784 | Displays informations about the license. 785 | """ 786 | with open(os.path.join(settings.APP_DIR, 'license_dialog_text')) as f: 787 | QMessageBox.about(self, _translate( 788 | "MainWindow", "License"), f.read()) 789 | 790 | @pyqtSlot() 791 | def about_qt(self): 792 | """ 793 | Displays informations about Qt. 794 | """ 795 | QMessageBox.aboutQt(self) 796 | 797 | @pyqtSlot() 798 | def highlight_selected_path(self): 799 | """ 800 | Looks for selected line in the code_edit, then updates the drawing to 801 | highlight the corresponding path. 802 | """ 803 | if not self.chk_display_current_line.isChecked(): 804 | return 805 | 806 | i = self.code_edit.textCursor().blockNumber() 807 | if i == self.last_selected_path: 808 | return 809 | else: 810 | self.last_selected_path = i 811 | self.code_edit.setExtraSelections([]) 812 | highlight = QTextEdit.ExtraSelection() 813 | highlight.cursor = self.code_edit.textCursor() 814 | highlight.format.setProperty(QTextFormat.FullWidthSelection, True) 815 | highlight.format.setBackground(Qt.green) 816 | self.code_edit.setExtraSelections([highlight]) 817 | self.update_drawing(highlight_line=i) 818 | -------------------------------------------------------------------------------- /sivicncdriver/gcodes/gear1.ngc: -------------------------------------------------------------------------------- 1 | % 2 | (Header) 3 | (Generated by gcodetools from Inkscape.) 4 | (Using default header. To add your own header create file "header" in the output dir.) 5 | M3 6 | (Header end.) 7 | G21 (All units in mm) 8 | 9 | (Start cutting path id: circle4713) 10 | (Change tool to Default tool) 11 | 12 | G00 Z 5.0000 13 | G00 X 32.2391 Y 51.7274 14 | 15 | G01 Z -0.1250 F 100.0000(Penetrate) 16 | G02 X 32.0243 Y 50.6474 Z -0.1250 I -2.8222 J 0.0000 F 400.0000 17 | G02 X 31.4125 Y 49.7318 Z -0.1250 I -2.6074 J 1.0800 18 | G02 X 30.4969 Y 49.1200 Z -0.1250 I -1.9956 J 1.9956 19 | G02 X 29.4169 Y 48.9052 Z -0.1250 I -1.0800 J 2.6074 20 | G02 X 27.4213 Y 49.7318 Z -0.1250 I 0.0000 J 2.8222 21 | G02 X 26.5947 Y 51.7274 Z -0.1250 I 1.9956 J 1.9956 22 | G02 X 27.4213 Y 53.7230 Z -0.1250 I 2.8222 J 0.0000 23 | G02 X 29.4169 Y 54.5496 Z -0.1250 I 1.9956 J -1.9956 24 | G02 X 30.4969 Y 54.3348 Z -0.1250 I 0.0000 J -2.8222 25 | G02 X 31.4125 Y 53.7230 Z -0.1250 I -1.0800 J -2.6074 26 | G02 X 32.0243 Y 52.8074 Z -0.1250 I -1.9956 J -1.9956 27 | G02 X 32.2391 Y 51.7274 Z -0.1250 I -2.6074 J -1.0800 28 | G01 X 32.2391 Y 51.7274 Z -0.1250 29 | G00 Z 5.0000 30 | 31 | (End cutting path id: circle4713) 32 | 33 | 34 | (Start cutting path id: path4705) 35 | (Change tool to Default tool) 36 | 37 | G00 Z 5.0000 38 | G00 X 35.8834 Y 48.9052 39 | 40 | G01 Z -0.1250 F 100.0000(Penetrate) 41 | G02 X 32.9449 Y 45.6172 Z -0.1250 I -6.4665 J 2.8222 F 400.0000 42 | G02 X 28.6282 Y 44.7161 Z -0.1250 I -3.5280 J 6.1102 43 | G01 X 23.5739 Y 35.9605 Z -0.1250 44 | G03 X 30.8338 Y 34.9725 Z -0.1250 I 5.8430 J 15.7669 45 | G03 X 37.8247 Y 37.1657 Z -0.1250 I -1.4169 J 16.7549 46 | G03 X 43.2192 Y 42.1237 Z -0.1250 I -8.4078 J 14.5617 47 | G03 X 45.9931 Y 48.9052 Z -0.1250 I -13.8023 J 9.6037 48 | G01 X 35.8834 Y 48.9052 Z -0.1250 49 | G00 Z 5.0000 50 | 51 | (End cutting path id: path4705) 52 | 53 | 54 | (Start cutting path id: path4707) 55 | (Change tool to Default tool) 56 | 57 | G00 Z 5.0000 58 | G00 X 23.7398 Y 47.5380 59 | 60 | G01 Z -0.1250 F 100.0000(Penetrate) 61 | G02 X 22.3614 Y 51.7268 Z -0.1250 I 5.6771 J 4.1894 F 400.0000 62 | G02 X 23.7390 Y 55.9158 Z -0.1250 I 7.0556 J 0.0006 63 | G01 X 18.6831 Y 64.6704 Z -0.1250 64 | G03 X 14.1978 Y 58.8769 Z -0.1250 I 10.7338 J -12.9430 65 | G03 X 12.6022 Y 51.7259 Z -0.1250 I 15.2191 J -7.1494 66 | G03 X 14.1991 Y 44.5752 Z -0.1250 I 16.8147 J 0.0016 67 | G03 X 18.6855 Y 38.7824 Z -0.1250 I 15.2178 J 7.1523 68 | G01 X 23.7398 Y 47.5380 Z -0.1250 69 | G00 Z 5.0000 70 | 71 | (End cutting path id: path4707) 72 | 73 | 74 | (Start cutting path id: path4709) 75 | (Change tool to Default tool) 76 | 77 | G00 Z 5.0000 78 | G00 X 28.6269 Y 58.7386 79 | 80 | G01 Z -0.1250 F 100.0000(Penetrate) 81 | G02 X 32.9437 Y 57.8382 Z -0.1250 I 0.7900 J -7.0112 F 400.0000 82 | G02 X 35.8829 Y 54.5508 Z -0.1250 I -3.5268 J -6.1108 83 | G01 X 45.9926 Y 54.5527 Z -0.1250 84 | G03 X 43.2175 Y 61.3337 Z -0.1250 I -16.5757 J -2.8253 85 | G03 X 37.8220 Y 66.2907 Z -0.1250 I -13.8006 J -9.6063 86 | G03 X 30.8307 Y 68.4826 Z -0.1250 I -8.4051 J -14.5633 87 | G03 X 23.5710 Y 67.4932 Z -0.1250 I -1.4138 J -16.7552 88 | G01 X 28.6269 Y 58.7386 Z -0.1250 89 | G00 Z 5.0000 90 | 91 | (End cutting path id: path4709) 92 | 93 | 94 | (Start cutting path id: path4701) 95 | (Change tool to Default tool) 96 | 97 | G00 Z 5.0000 98 | G00 X 58.5067 Y 54.2259 99 | 100 | G01 Z -0.1250 F 100.0000(Penetrate) 101 | G02 X 58.6138 Y 51.7274 Z -0.1250 I -29.0899 J -2.4985 F 400.0000 102 | G02 X 58.5067 Y 49.2289 Z -0.1250 I -29.1970 J -0.0000 103 | G01 X 58.5067 Y 49.2289 Z -0.1250 104 | G01 X 58.2002 Y 49.0908 Z -0.1250 105 | G01 X 57.9092 Y 48.9678 Z -0.1250 106 | G01 X 57.6334 Y 48.8585 Z -0.1250 107 | G01 X 57.3720 Y 48.7618 Z -0.1250 108 | G01 X 57.1247 Y 48.6764 Z -0.1250 109 | G01 X 56.8910 Y 48.6014 Z -0.1250 110 | G01 X 56.6705 Y 48.5357 Z -0.1250 111 | G01 X 56.4627 Y 48.4786 Z -0.1250 112 | G01 X 56.2673 Y 48.4293 Z -0.1250 113 | G01 X 56.0838 Y 48.3869 Z -0.1250 114 | G01 X 55.9119 Y 48.3509 Z -0.1250 115 | G01 X 55.7512 Y 48.3205 Z -0.1250 116 | G01 X 55.6015 Y 48.2953 Z -0.1250 117 | G01 X 55.4624 Y 48.2747 Z -0.1250 118 | G01 X 55.3337 Y 48.2581 Z -0.1250 119 | G01 X 55.2150 Y 48.2451 Z -0.1250 120 | G01 X 55.1062 Y 48.2353 Z -0.1250 121 | G01 X 55.0069 Y 48.2282 Z -0.1250 122 | G01 X 54.9171 Y 48.2235 Z -0.1250 123 | G01 X 54.8364 Y 48.2207 Z -0.1250 124 | G01 X 54.7648 Y 48.2196 Z -0.1250 125 | G01 X 54.7020 Y 48.2198 Z -0.1250 126 | G01 X 54.6479 Y 48.2209 Z -0.1250 127 | G01 X 54.6025 Y 48.2227 Z -0.1250 128 | G01 X 54.5655 Y 48.2248 Z -0.1250 129 | G01 X 54.5368 Y 48.2270 Z -0.1250 130 | G01 X 54.5164 Y 48.2289 Z -0.1250 131 | G01 X 54.5042 Y 48.2303 Z -0.1250 132 | G01 X 51.6610 Y 48.6266 Z -0.1250 133 | G02 X 51.1109 Y 45.9147 Z -0.1250 I -22.2441 J 3.1009 134 | G02 X 50.2314 Y 43.2911 Z -0.1250 I -21.6939 J 5.8128 135 | G01 X 52.8919 Y 42.2128 Z -0.1250 136 | G01 X 52.9031 Y 42.2078 Z -0.1250 137 | G01 X 52.9217 Y 42.1993 Z -0.1250 138 | G01 X 52.9476 Y 42.1868 Z -0.1250 139 | G01 X 52.9807 Y 42.1702 Z -0.1250 140 | G01 X 53.0210 Y 42.1490 Z -0.1250 141 | G01 X 53.0684 Y 42.1229 Z -0.1250 142 | G01 X 53.1228 Y 42.0917 Z -0.1250 143 | G01 X 53.1843 Y 42.0549 Z -0.1250 144 | G01 X 53.2528 Y 42.0122 Z -0.1250 145 | G01 X 53.3282 Y 41.9632 Z -0.1250 146 | G01 X 53.4107 Y 41.9074 Z -0.1250 147 | G01 X 53.5000 Y 41.8445 Z -0.1250 148 | G01 X 53.5963 Y 41.7739 Z -0.1250 149 | G01 X 53.6995 Y 41.6952 Z -0.1250 150 | G01 X 53.8096 Y 41.6078 Z -0.1250 151 | G01 X 53.9267 Y 41.5111 Z -0.1250 152 | G01 X 54.0507 Y 41.4045 Z -0.1250 153 | G01 X 54.1815 Y 41.2873 Z -0.1250 154 | G01 X 54.3192 Y 41.1589 Z -0.1250 155 | G01 X 54.4638 Y 41.0184 Z -0.1250 156 | G01 X 54.6152 Y 40.8651 Z -0.1250 157 | G01 X 54.7734 Y 40.6980 Z -0.1250 158 | G01 X 54.9383 Y 40.5162 Z -0.1250 159 | G01 X 55.1097 Y 40.3186 Z -0.1250 160 | G01 X 55.2877 Y 40.1041 Z -0.1250 161 | G01 X 55.4720 Y 39.8715 Z -0.1250 162 | G01 X 55.6625 Y 39.6195 Z -0.1250 163 | G01 X 55.8589 Y 39.3466 Z -0.1250 164 | G02 X 54.7024 Y 37.1293 Z -0.1250 I -26.4420 J 12.3809 165 | G02 X 53.3605 Y 35.0191 Z -0.1250 I -25.2855 J 14.5982 166 | G01 X 53.3605 Y 35.0191 Z -0.1250 167 | G01 X 53.0260 Y 35.0527 Z -0.1250 168 | G01 X 52.7125 Y 35.0917 Z -0.1250 169 | G01 X 52.4189 Y 35.1350 Z -0.1250 170 | G01 X 52.1442 Y 35.1819 Z -0.1250 171 | G01 X 51.8873 Y 35.2316 Z -0.1250 172 | G01 X 51.6474 Y 35.2835 Z -0.1250 173 | G01 X 51.4236 Y 35.3369 Z -0.1250 174 | G01 X 51.2151 Y 35.3913 Z -0.1250 175 | G01 X 51.0212 Y 35.4463 Z -0.1250 176 | G01 X 50.8411 Y 35.5014 Z -0.1250 177 | G01 X 50.6742 Y 35.5561 Z -0.1250 178 | G01 X 50.5199 Y 35.6102 Z -0.1250 179 | G01 X 50.3776 Y 35.6632 Z -0.1250 180 | G01 X 50.2469 Y 35.7148 Z -0.1250 181 | G01 X 50.1271 Y 35.7648 Z -0.1250 182 | G01 X 50.0178 Y 35.8129 Z -0.1250 183 | G01 X 49.9186 Y 35.8588 Z -0.1250 184 | G01 X 49.8291 Y 35.9023 Z -0.1250 185 | G01 X 49.7490 Y 35.9431 Z -0.1250 186 | G01 X 49.6778 Y 35.9811 Z -0.1250 187 | G01 X 49.6152 Y 36.0160 Z -0.1250 188 | G01 X 49.5609 Y 36.0475 Z -0.1250 189 | G01 X 49.5146 Y 36.0755 Z -0.1250 190 | G01 X 49.4761 Y 36.0998 Z -0.1250 191 | G01 X 49.4451 Y 36.1202 Z -0.1250 192 | G01 X 49.4214 Y 36.1364 Z -0.1250 193 | G01 X 49.4047 Y 36.1482 Z -0.1250 194 | G01 X 49.3948 Y 36.1555 Z -0.1250 195 | G01 X 47.1306 Y 37.9202 Z -0.1250 196 | G02 X 45.2983 Y 35.8467 Z -0.1250 I -17.7138 J 13.8072 197 | G02 X 43.2249 Y 34.0143 Z -0.1250 I -15.8814 J 15.8806 198 | G01 X 44.9898 Y 31.7503 Z -0.1250 199 | G01 X 44.9971 Y 31.7403 Z -0.1250 200 | G01 X 45.0089 Y 31.7237 Z -0.1250 201 | G01 X 45.0251 Y 31.6999 Z -0.1250 202 | G01 X 45.0455 Y 31.6689 Z -0.1250 203 | G01 X 45.0697 Y 31.6304 Z -0.1250 204 | G01 X 45.0978 Y 31.5842 Z -0.1250 205 | G01 X 45.1293 Y 31.5299 Z -0.1250 206 | G01 X 45.1642 Y 31.4673 Z -0.1250 207 | G01 X 45.2021 Y 31.3961 Z -0.1250 208 | G01 X 45.2429 Y 31.3159 Z -0.1250 209 | G01 X 45.2864 Y 31.2264 Z -0.1250 210 | G01 X 45.3324 Y 31.1272 Z -0.1250 211 | G01 X 45.3804 Y 31.0180 Z -0.1250 212 | G01 X 45.4305 Y 30.8982 Z -0.1250 213 | G01 X 45.4821 Y 30.7674 Z -0.1250 214 | G01 X 45.5352 Y 30.6251 Z -0.1250 215 | G01 X 45.5892 Y 30.4709 Z -0.1250 216 | G01 X 45.6440 Y 30.3040 Z -0.1250 217 | G01 X 45.6990 Y 30.1239 Z -0.1250 218 | G01 X 45.7540 Y 29.9299 Z -0.1250 219 | G01 X 45.8085 Y 29.7214 Z -0.1250 220 | G01 X 45.8619 Y 29.4977 Z -0.1250 221 | G01 X 45.9138 Y 29.2578 Z -0.1250 222 | G01 X 45.9635 Y 29.0009 Z -0.1250 223 | G01 X 46.0104 Y 28.7262 Z -0.1250 224 | G01 X 46.0537 Y 28.4326 Z -0.1250 225 | G01 X 46.0927 Y 28.1191 Z -0.1250 226 | G01 X 46.1263 Y 27.7846 Z -0.1250 227 | G02 X 44.0162 Y 26.4426 Z -0.1250 I -16.7094 J 23.9429 228 | G02 X 41.7989 Y 25.2860 Z -0.1250 I -14.5992 J 25.2850 229 | G01 X 41.7989 Y 25.2860 Z -0.1250 230 | G01 X 41.5260 Y 25.4824 Z -0.1250 231 | G01 X 41.2740 Y 25.6728 Z -0.1250 232 | G01 X 41.0414 Y 25.8571 Z -0.1250 233 | G01 X 40.8269 Y 26.0351 Z -0.1250 234 | G01 X 40.6293 Y 26.2066 Z -0.1250 235 | G01 X 40.4475 Y 26.3714 Z -0.1250 236 | G01 X 40.2804 Y 26.5296 Z -0.1250 237 | G01 X 40.1271 Y 26.6810 Z -0.1250 238 | G01 X 39.9866 Y 26.8256 Z -0.1250 239 | G01 X 39.8582 Y 26.9633 Z -0.1250 240 | G01 X 39.7410 Y 27.0942 Z -0.1250 241 | G01 X 39.6344 Y 27.2181 Z -0.1250 242 | G01 X 39.5377 Y 27.3351 Z -0.1250 243 | G01 X 39.4503 Y 27.4453 Z -0.1250 244 | G01 X 39.3715 Y 27.5485 Z -0.1250 245 | G01 X 39.3009 Y 27.6448 Z -0.1250 246 | G01 X 39.2380 Y 27.7341 Z -0.1250 247 | G01 X 39.1823 Y 27.8165 Z -0.1250 248 | G01 X 39.1332 Y 27.8920 Z -0.1250 249 | G01 X 39.0905 Y 27.9604 Z -0.1250 250 | G01 X 39.0538 Y 28.0219 Z -0.1250 251 | G01 X 39.0225 Y 28.0764 Z -0.1250 252 | G01 X 38.9965 Y 28.1238 Z -0.1250 253 | G01 X 38.9753 Y 28.1640 Z -0.1250 254 | G01 X 38.9586 Y 28.1972 Z -0.1250 255 | G01 X 38.9461 Y 28.2231 Z -0.1250 256 | G01 X 38.9376 Y 28.2417 Z -0.1250 257 | G01 X 38.9327 Y 28.2529 Z -0.1250 258 | G01 X 37.8542 Y 30.9133 Z -0.1250 259 | G02 X 35.2306 Y 30.0337 Z -0.1250 I -8.4374 J 20.8141 260 | G02 X 32.5188 Y 29.4834 Z -0.1250 I -5.8138 J 21.6937 261 | G01 X 32.9152 Y 26.6403 Z -0.1250 262 | G01 X 32.9166 Y 26.6281 Z -0.1250 263 | G01 X 32.9185 Y 26.6077 Z -0.1250 264 | G01 X 32.9206 Y 26.5790 Z -0.1250 265 | G01 X 32.9228 Y 26.5420 Z -0.1250 266 | G01 X 32.9245 Y 26.4965 Z -0.1250 267 | G01 X 32.9257 Y 26.4425 Z -0.1250 268 | G01 X 32.9259 Y 26.3797 Z -0.1250 269 | G01 X 32.9248 Y 26.3081 Z -0.1250 270 | G01 X 32.9220 Y 26.2274 Z -0.1250 271 | G01 X 32.9173 Y 26.1376 Z -0.1250 272 | G01 X 32.9102 Y 26.0383 Z -0.1250 273 | G01 X 32.9004 Y 25.9295 Z -0.1250 274 | G01 X 32.8875 Y 25.8108 Z -0.1250 275 | G01 X 32.8709 Y 25.6821 Z -0.1250 276 | G01 X 32.8502 Y 25.5430 Z -0.1250 277 | G01 X 32.8250 Y 25.3933 Z -0.1250 278 | G01 X 32.7947 Y 25.2326 Z -0.1250 279 | G01 X 32.7586 Y 25.0607 Z -0.1250 280 | G01 X 32.7163 Y 24.8772 Z -0.1250 281 | G01 X 32.6669 Y 24.6817 Z -0.1250 282 | G01 X 32.6098 Y 24.4740 Z -0.1250 283 | G01 X 32.5442 Y 24.2534 Z -0.1250 284 | G01 X 32.4692 Y 24.0197 Z -0.1250 285 | G01 X 32.3839 Y 23.7725 Z -0.1250 286 | G01 X 32.2871 Y 23.5111 Z -0.1250 287 | G01 X 32.1779 Y 23.2351 Z -0.1250 288 | G01 X 32.0549 Y 22.9441 Z -0.1250 289 | G01 X 31.9167 Y 22.6376 Z -0.1250 290 | G02 X 29.4183 Y 22.5304 Z -0.1250 I -2.5001 J 29.0898 291 | G02 X 26.9198 Y 22.6374 Z -0.1250 I -0.0016 J 29.1970 292 | G01 X 26.9198 Y 22.6374 Z -0.1250 293 | G01 X 26.7816 Y 22.9438 Z -0.1250 294 | G01 X 26.6586 Y 23.2348 Z -0.1250 295 | G01 X 26.5493 Y 23.5108 Z -0.1250 296 | G01 X 26.4526 Y 23.7722 Z -0.1250 297 | G01 X 26.3672 Y 24.0195 Z -0.1250 298 | G01 X 26.2921 Y 24.2531 Z -0.1250 299 | G01 X 26.2265 Y 24.4737 Z -0.1250 300 | G01 X 26.1694 Y 24.6815 Z -0.1250 301 | G01 X 26.1201 Y 24.8769 Z -0.1250 302 | G01 X 26.0777 Y 25.0604 Z -0.1250 303 | G01 X 26.0416 Y 25.2323 Z -0.1250 304 | G01 X 26.0113 Y 25.3929 Z -0.1250 305 | G01 X 25.9860 Y 25.5427 Z -0.1250 306 | G01 X 25.9654 Y 25.6817 Z -0.1250 307 | G01 X 25.9488 Y 25.8105 Z -0.1250 308 | G01 X 25.9358 Y 25.9292 Z -0.1250 309 | G01 X 25.9260 Y 26.0380 Z -0.1250 310 | G01 X 25.9189 Y 26.1372 Z -0.1250 311 | G01 X 25.9142 Y 26.2271 Z -0.1250 312 | G01 X 25.9114 Y 26.3077 Z -0.1250 313 | G01 X 25.9103 Y 26.3794 Z -0.1250 314 | G01 X 25.9105 Y 26.4421 Z -0.1250 315 | G01 X 25.9116 Y 26.4962 Z -0.1250 316 | G01 X 25.9134 Y 26.5417 Z -0.1250 317 | G01 X 25.9155 Y 26.5787 Z -0.1250 318 | G01 X 25.9177 Y 26.6074 Z -0.1250 319 | G01 X 25.9196 Y 26.6278 Z -0.1250 320 | G01 X 25.9209 Y 26.6400 Z -0.1250 321 | G01 X 26.3171 Y 29.4832 Z -0.1250 322 | G02 X 23.6052 Y 30.0332 Z -0.1250 I 3.0999 J 22.2442 323 | G02 X 20.9816 Y 30.9125 Z -0.1250 I 5.8117 J 21.6942 324 | G01 X 19.9033 Y 28.2520 Z -0.1250 325 | G01 X 19.8984 Y 28.2408 Z -0.1250 326 | G01 X 19.8899 Y 28.2222 Z -0.1250 327 | G01 X 19.8774 Y 28.1963 Z -0.1250 328 | G01 X 19.8608 Y 28.1632 Z -0.1250 329 | G01 X 19.8395 Y 28.1229 Z -0.1250 330 | G01 X 19.8135 Y 28.0755 Z -0.1250 331 | G01 X 19.7823 Y 28.0210 Z -0.1250 332 | G01 X 19.7455 Y 27.9595 Z -0.1250 333 | G01 X 19.7028 Y 27.8911 Z -0.1250 334 | G01 X 19.6538 Y 27.8156 Z -0.1250 335 | G01 X 19.5981 Y 27.7332 Z -0.1250 336 | G01 X 19.5351 Y 27.6439 Z -0.1250 337 | G01 X 19.4645 Y 27.5476 Z -0.1250 338 | G01 X 19.3858 Y 27.4444 Z -0.1250 339 | G01 X 19.2984 Y 27.3342 Z -0.1250 340 | G01 X 19.2017 Y 27.2172 Z -0.1250 341 | G01 X 19.0951 Y 27.0932 Z -0.1250 342 | G01 X 18.9780 Y 26.9624 Z -0.1250 343 | G01 X 18.8495 Y 26.8246 Z -0.1250 344 | G01 X 18.7091 Y 26.6800 Z -0.1250 345 | G01 X 18.5557 Y 26.5286 Z -0.1250 346 | G01 X 18.3887 Y 26.3704 Z -0.1250 347 | G01 X 18.2068 Y 26.2055 Z -0.1250 348 | G01 X 18.0093 Y 26.0340 Z -0.1250 349 | G01 X 17.7948 Y 25.8561 Z -0.1250 350 | G01 X 17.5623 Y 25.6717 Z -0.1250 351 | G01 X 17.3102 Y 25.4813 Z -0.1250 352 | G01 X 17.0374 Y 25.2849 Z -0.1250 353 | G02 X 14.8200 Y 26.4412 Z -0.1250 I 12.3795 J 26.4426 354 | G02 X 12.7097 Y 27.7831 Z -0.1250 I 14.5969 J 25.2863 355 | G01 X 12.7097 Y 27.7831 Z -0.1250 356 | G01 X 12.7433 Y 28.1176 Z -0.1250 357 | G01 X 12.7823 Y 28.4311 Z -0.1250 358 | G01 X 12.8255 Y 28.7247 Z -0.1250 359 | G01 X 12.8724 Y 28.9994 Z -0.1250 360 | G01 X 12.9221 Y 29.2562 Z -0.1250 361 | G01 X 12.9740 Y 29.4961 Z -0.1250 362 | G01 X 13.0274 Y 29.7199 Z -0.1250 363 | G01 X 13.0818 Y 29.9284 Z -0.1250 364 | G01 X 13.1368 Y 30.1224 Z -0.1250 365 | G01 X 13.1919 Y 30.3025 Z -0.1250 366 | G01 X 13.2466 Y 30.4694 Z -0.1250 367 | G01 X 13.3006 Y 30.6237 Z -0.1250 368 | G01 X 13.3536 Y 30.7659 Z -0.1250 369 | G01 X 13.4053 Y 30.8967 Z -0.1250 370 | G01 X 13.4553 Y 31.0165 Z -0.1250 371 | G01 X 13.5034 Y 31.1258 Z -0.1250 372 | G01 X 13.5493 Y 31.2250 Z -0.1250 373 | G01 X 13.5928 Y 31.3144 Z -0.1250 374 | G01 X 13.6336 Y 31.3946 Z -0.1250 375 | G01 X 13.6715 Y 31.4658 Z -0.1250 376 | G01 X 13.7064 Y 31.5284 Z -0.1250 377 | G01 X 13.7379 Y 31.5827 Z -0.1250 378 | G01 X 13.7659 Y 31.6290 Z -0.1250 379 | G01 X 13.7902 Y 31.6674 Z -0.1250 380 | G01 X 13.8106 Y 31.6985 Z -0.1250 381 | G01 X 13.8268 Y 31.7222 Z -0.1250 382 | G01 X 13.8386 Y 31.7389 Z -0.1250 383 | G01 X 13.8459 Y 31.7488 Z -0.1250 384 | G01 X 15.6105 Y 34.0130 Z -0.1250 385 | G02 X 13.5370 Y 35.8453 Z -0.1250 I 13.8063 J 17.7144 386 | G02 X 11.7044 Y 37.9186 Z -0.1250 I 15.8799 J 15.8822 387 | G01 X 9.4405 Y 36.1536 Z -0.1250 388 | G01 X 9.4306 Y 36.1463 Z -0.1250 389 | G01 X 9.4139 Y 36.1345 Z -0.1250 390 | G01 X 9.3901 Y 36.1183 Z -0.1250 391 | G01 X 9.3591 Y 36.0979 Z -0.1250 392 | G01 X 9.3206 Y 36.0736 Z -0.1250 393 | G01 X 9.2744 Y 36.0456 Z -0.1250 394 | G01 X 9.2201 Y 36.0141 Z -0.1250 395 | G01 X 9.1575 Y 35.9792 Z -0.1250 396 | G01 X 9.0863 Y 35.9413 Z -0.1250 397 | G01 X 9.0062 Y 35.9004 Z -0.1250 398 | G01 X 8.9167 Y 35.8569 Z -0.1250 399 | G01 X 8.8175 Y 35.8110 Z -0.1250 400 | G01 X 8.7082 Y 35.7629 Z -0.1250 401 | G01 X 8.5885 Y 35.7129 Z -0.1250 402 | G01 X 8.4577 Y 35.6612 Z -0.1250 403 | G01 X 8.3154 Y 35.6082 Z -0.1250 404 | G01 X 8.1611 Y 35.5541 Z -0.1250 405 | G01 X 7.9942 Y 35.4994 Z -0.1250 406 | G01 X 7.8141 Y 35.4443 Z -0.1250 407 | G01 X 7.6202 Y 35.3893 Z -0.1250 408 | G01 X 7.4117 Y 35.3348 Z -0.1250 409 | G01 X 7.1879 Y 35.2814 Z -0.1250 410 | G01 X 6.9480 Y 35.2295 Z -0.1250 411 | G01 X 6.6912 Y 35.1798 Z -0.1250 412 | G01 X 6.4165 Y 35.1329 Z -0.1250 413 | G01 X 6.1229 Y 35.0895 Z -0.1250 414 | G01 X 5.8094 Y 35.0505 Z -0.1250 415 | G01 X 5.4749 Y 35.0169 Z -0.1250 416 | G02 X 4.1327 Y 37.1270 Z -0.1250 I 23.9420 J 16.7106 417 | G02 X 2.9761 Y 39.3442 Z -0.1250 I 25.2842 J 14.6005 418 | G01 X 2.9761 Y 39.3442 Z -0.1250 419 | G01 X 3.1724 Y 39.6171 Z -0.1250 420 | G01 X 3.3629 Y 39.8691 Z -0.1250 421 | G01 X 3.5472 Y 40.1017 Z -0.1250 422 | G01 X 3.7251 Y 40.3162 Z -0.1250 423 | G01 X 3.8966 Y 40.5138 Z -0.1250 424 | G01 X 4.0614 Y 40.6956 Z -0.1250 425 | G01 X 4.2196 Y 40.8627 Z -0.1250 426 | G01 X 4.3710 Y 41.0161 Z -0.1250 427 | G01 X 4.5156 Y 41.1566 Z -0.1250 428 | G01 X 4.6533 Y 41.2850 Z -0.1250 429 | G01 X 4.7841 Y 41.4022 Z -0.1250 430 | G01 X 4.9081 Y 41.5088 Z -0.1250 431 | G01 X 5.0251 Y 41.6055 Z -0.1250 432 | G01 X 5.1352 Y 41.6929 Z -0.1250 433 | G01 X 5.2385 Y 41.7717 Z -0.1250 434 | G01 X 5.3347 Y 41.8423 Z -0.1250 435 | G01 X 5.4241 Y 41.9052 Z -0.1250 436 | G01 X 5.5065 Y 41.9610 Z -0.1250 437 | G01 X 5.5819 Y 42.0100 Z -0.1250 438 | G01 X 5.6504 Y 42.0527 Z -0.1250 439 | G01 X 5.7119 Y 42.0895 Z -0.1250 440 | G01 X 5.7663 Y 42.1207 Z -0.1250 441 | G01 X 5.8137 Y 42.1468 Z -0.1250 442 | G01 X 5.8540 Y 42.1680 Z -0.1250 443 | G01 X 5.8871 Y 42.1846 Z -0.1250 444 | G01 X 5.9130 Y 42.1971 Z -0.1250 445 | G01 X 5.9316 Y 42.2057 Z -0.1250 446 | G01 X 5.9429 Y 42.2106 Z -0.1250 447 | G01 X 8.6032 Y 43.2892 Z -0.1250 448 | G02 X 7.7235 Y 45.9127 Z -0.1250 I 20.8137 J 8.4382 449 | G02 X 7.1731 Y 48.6245 Z -0.1250 I 21.6934 J 5.8147 450 | G01 X 4.3300 Y 48.2279 Z -0.1250 451 | G01 X 4.3178 Y 48.2266 Z -0.1250 452 | G01 X 4.2974 Y 48.2247 Z -0.1250 453 | G01 X 4.2687 Y 48.2225 Z -0.1250 454 | G01 X 4.2317 Y 48.2204 Z -0.1250 455 | G01 X 4.1862 Y 48.2186 Z -0.1250 456 | G01 X 4.1321 Y 48.2175 Z -0.1250 457 | G01 X 4.0694 Y 48.2173 Z -0.1250 458 | G01 X 3.9977 Y 48.2184 Z -0.1250 459 | G01 X 3.9171 Y 48.2211 Z -0.1250 460 | G01 X 3.8272 Y 48.2259 Z -0.1250 461 | G01 X 3.7280 Y 48.2329 Z -0.1250 462 | G01 X 3.6191 Y 48.2427 Z -0.1250 463 | G01 X 3.5005 Y 48.2557 Z -0.1250 464 | G01 X 3.3717 Y 48.2722 Z -0.1250 465 | G01 X 3.2326 Y 48.2929 Z -0.1250 466 | G01 X 3.0829 Y 48.3181 Z -0.1250 467 | G01 X 2.9223 Y 48.3484 Z -0.1250 468 | G01 X 2.7504 Y 48.3845 Z -0.1250 469 | G01 X 2.5669 Y 48.4268 Z -0.1250 470 | G01 X 2.3714 Y 48.4762 Z -0.1250 471 | G01 X 2.1636 Y 48.5332 Z -0.1250 472 | G01 X 1.9431 Y 48.5988 Z -0.1250 473 | G01 X 1.7094 Y 48.6738 Z -0.1250 474 | G01 X 1.4621 Y 48.7592 Z -0.1250 475 | G01 X 1.2007 Y 48.8559 Z -0.1250 476 | G01 X 0.9249 Y 48.9651 Z -0.1250 477 | G01 X 0.6339 Y 49.0881 Z -0.1250 478 | G01 X 0.3274 Y 49.2262 Z -0.1250 479 | G02 X 0.2200 Y 51.7247 Z -0.1250 I 29.0896 J 2.5018 480 | G02 X 0.3269 Y 54.2232 Z -0.1250 I 29.1970 J 0.0033 481 | G01 X 0.3269 Y 54.2232 Z -0.1250 482 | G01 X 0.6334 Y 54.3614 Z -0.1250 483 | G01 X 0.9243 Y 54.4844 Z -0.1250 484 | G01 X 1.2002 Y 54.5937 Z -0.1250 485 | G01 X 1.4615 Y 54.6905 Z -0.1250 486 | G01 X 1.7088 Y 54.7759 Z -0.1250 487 | G01 X 1.9425 Y 54.8509 Z -0.1250 488 | G01 X 2.1630 Y 54.9166 Z -0.1250 489 | G01 X 2.3708 Y 54.9737 Z -0.1250 490 | G01 X 2.5663 Y 55.0230 Z -0.1250 491 | G01 X 2.7497 Y 55.0654 Z -0.1250 492 | G01 X 2.9216 Y 55.1015 Z -0.1250 493 | G01 X 3.0823 Y 55.1318 Z -0.1250 494 | G01 X 3.2320 Y 55.1571 Z -0.1250 495 | G01 X 3.3711 Y 55.1777 Z -0.1250 496 | G01 X 3.4998 Y 55.1943 Z -0.1250 497 | G01 X 3.6185 Y 55.2073 Z -0.1250 498 | G01 X 3.7274 Y 55.2172 Z -0.1250 499 | G01 X 3.8266 Y 55.2243 Z -0.1250 500 | G01 X 3.9164 Y 55.2290 Z -0.1250 501 | G01 X 3.9971 Y 55.2317 Z -0.1250 502 | G01 X 4.0687 Y 55.2328 Z -0.1250 503 | G01 X 4.1315 Y 55.2327 Z -0.1250 504 | G01 X 4.1855 Y 55.2315 Z -0.1250 505 | G01 X 4.2310 Y 55.2298 Z -0.1250 506 | G01 X 4.2681 Y 55.2276 Z -0.1250 507 | G01 X 4.2967 Y 55.2255 Z -0.1250 508 | G01 X 4.3171 Y 55.2236 Z -0.1250 509 | G01 X 4.3293 Y 55.2223 Z -0.1250 510 | G01 X 7.1725 Y 54.8262 Z -0.1250 511 | G02 X 7.7224 Y 57.5381 Z -0.1250 I 22.2444 J -3.0988 512 | G02 X 8.6016 Y 60.1618 Z -0.1250 I 21.6945 J -5.8107 513 | G01 X 5.9411 Y 61.2399 Z -0.1250 514 | G01 X 5.9298 Y 61.2448 Z -0.1250 515 | G01 X 5.9112 Y 61.2534 Z -0.1250 516 | G01 X 5.8853 Y 61.2658 Z -0.1250 517 | G01 X 5.8522 Y 61.2825 Z -0.1250 518 | G01 X 5.8119 Y 61.3037 Z -0.1250 519 | G01 X 5.7645 Y 61.3297 Z -0.1250 520 | G01 X 5.7101 Y 61.3610 Z -0.1250 521 | G01 X 5.6486 Y 61.3977 Z -0.1250 522 | G01 X 5.5801 Y 61.4404 Z -0.1250 523 | G01 X 5.5047 Y 61.4894 Z -0.1250 524 | G01 X 5.4222 Y 61.5452 Z -0.1250 525 | G01 X 5.3329 Y 61.6081 Z -0.1250 526 | G01 X 5.2366 Y 61.6787 Z -0.1250 527 | G01 X 5.1334 Y 61.7574 Z -0.1250 528 | G01 X 5.0233 Y 61.8448 Z -0.1250 529 | G01 X 4.9062 Y 61.9415 Z -0.1250 530 | G01 X 4.7822 Y 62.0481 Z -0.1250 531 | G01 X 4.6514 Y 62.1652 Z -0.1250 532 | G01 X 4.5136 Y 62.2936 Z -0.1250 533 | G01 X 4.3690 Y 62.4341 Z -0.1250 534 | G01 X 4.2176 Y 62.5874 Z -0.1250 535 | G01 X 4.0594 Y 62.7545 Z -0.1250 536 | G01 X 3.8945 Y 62.9363 Z -0.1250 537 | G01 X 3.7230 Y 63.1339 Z -0.1250 538 | G01 X 3.5450 Y 63.3483 Z -0.1250 539 | G01 X 3.3607 Y 63.5809 Z -0.1250 540 | G01 X 3.1702 Y 63.8329 Z -0.1250 541 | G01 X 2.9738 Y 64.1057 Z -0.1250 542 | G02 X 4.1300 Y 66.3232 Z -0.1250 I 26.4432 J -12.3783 543 | G02 X 5.4718 Y 68.4335 Z -0.1250 I 25.2870 J -14.5957 544 | G01 X 5.4718 Y 68.4335 Z -0.1250 545 | G01 X 5.8063 Y 68.3999 Z -0.1250 546 | G01 X 6.1198 Y 68.3610 Z -0.1250 547 | G01 X 6.4134 Y 68.3177 Z -0.1250 548 | G01 X 6.6881 Y 68.2708 Z -0.1250 549 | G01 X 6.9450 Y 68.2212 Z -0.1250 550 | G01 X 7.1849 Y 68.1693 Z -0.1250 551 | G01 X 7.4087 Y 68.1159 Z -0.1250 552 | G01 X 7.6172 Y 68.0615 Z -0.1250 553 | G01 X 7.8111 Y 68.0065 Z -0.1250 554 | G01 X 7.9912 Y 67.9515 Z -0.1250 555 | G01 X 8.1581 Y 67.8967 Z -0.1250 556 | G01 X 8.3124 Y 67.8427 Z -0.1250 557 | G01 X 8.4547 Y 67.7897 Z -0.1250 558 | G01 X 8.5855 Y 67.7381 Z -0.1250 559 | G01 X 8.7053 Y 67.6881 Z -0.1250 560 | G01 X 8.8146 Y 67.6400 Z -0.1250 561 | G01 X 8.9137 Y 67.5941 Z -0.1250 562 | G01 X 9.0032 Y 67.5506 Z -0.1250 563 | G01 X 9.0834 Y 67.5098 Z -0.1250 564 | G01 X 9.1546 Y 67.4718 Z -0.1250 565 | G01 X 9.2172 Y 67.4370 Z -0.1250 566 | G01 X 9.2715 Y 67.4055 Z -0.1250 567 | G01 X 9.3178 Y 67.3775 Z -0.1250 568 | G01 X 9.3562 Y 67.3532 Z -0.1250 569 | G01 X 9.3872 Y 67.3328 Z -0.1250 570 | G01 X 9.4110 Y 67.3166 Z -0.1250 571 | G01 X 9.4277 Y 67.3048 Z -0.1250 572 | G01 X 9.4376 Y 67.2975 Z -0.1250 573 | G01 X 11.7019 Y 65.5330 Z -0.1250 574 | G02 X 13.5340 Y 67.6066 Z -0.1250 I 17.7150 J -13.8056 575 | G02 X 15.6072 Y 69.4392 Z -0.1250 I 15.8829 J -15.8792 576 | G01 X 13.8422 Y 71.7031 Z -0.1250 577 | G01 X 13.8349 Y 71.7130 Z -0.1250 578 | G01 X 13.8231 Y 71.7297 Z -0.1250 579 | G01 X 13.8069 Y 71.7535 Z -0.1250 580 | G01 X 13.7865 Y 71.7845 Z -0.1250 581 | G01 X 13.7622 Y 71.8230 Z -0.1250 582 | G01 X 13.7342 Y 71.8692 Z -0.1250 583 | G01 X 13.7027 Y 71.9235 Z -0.1250 584 | G01 X 13.6678 Y 71.9861 Z -0.1250 585 | G01 X 13.6298 Y 72.0573 Z -0.1250 586 | G01 X 13.5890 Y 72.1374 Z -0.1250 587 | G01 X 13.5455 Y 72.2269 Z -0.1250 588 | G01 X 13.4996 Y 72.3261 Z -0.1250 589 | G01 X 13.4515 Y 72.4354 Z -0.1250 590 | G01 X 13.4014 Y 72.5551 Z -0.1250 591 | G01 X 13.3497 Y 72.6859 Z -0.1250 592 | G01 X 13.2967 Y 72.8282 Z -0.1250 593 | G01 X 13.2427 Y 72.9825 Z -0.1250 594 | G01 X 13.1879 Y 73.1493 Z -0.1250 595 | G01 X 13.1328 Y 73.3294 Z -0.1250 596 | G01 X 13.0778 Y 73.5234 Z -0.1250 597 | G01 X 13.0234 Y 73.7319 Z -0.1250 598 | G01 X 12.9699 Y 73.9557 Z -0.1250 599 | G01 X 12.9180 Y 74.1955 Z -0.1250 600 | G01 X 12.8682 Y 74.4524 Z -0.1250 601 | G01 X 12.8213 Y 74.7271 Z -0.1250 602 | G01 X 12.7779 Y 75.0207 Z -0.1250 603 | G01 X 12.7390 Y 75.3342 Z -0.1250 604 | G01 X 12.7053 Y 75.6687 Z -0.1250 605 | G02 X 14.8153 Y 77.0109 Z -0.1250 I 16.7117 J -23.9413 606 | G02 X 17.0325 Y 78.1677 Z -0.1250 I 14.6017 J -25.2835 607 | G01 X 17.0325 Y 78.1677 Z -0.1250 608 | G01 X 17.3053 Y 77.9713 Z -0.1250 609 | G01 X 17.5574 Y 77.7809 Z -0.1250 610 | G01 X 17.7900 Y 77.5966 Z -0.1250 611 | G01 X 18.0045 Y 77.4187 Z -0.1250 612 | G01 X 18.2021 Y 77.2472 Z -0.1250 613 | G01 X 18.3839 Y 77.0823 Z -0.1250 614 | G01 X 18.5511 Y 76.9242 Z -0.1250 615 | G01 X 18.7044 Y 76.7728 Z -0.1250 616 | G01 X 18.8449 Y 76.6282 Z -0.1250 617 | G01 X 18.9734 Y 76.4905 Z -0.1250 618 | G01 X 19.0905 Y 76.3597 Z -0.1250 619 | G01 X 19.1971 Y 76.2358 Z -0.1250 620 | G01 X 19.2939 Y 76.1187 Z -0.1250 621 | G01 X 19.3813 Y 76.0086 Z -0.1250 622 | G01 X 19.4601 Y 75.9054 Z -0.1250 623 | G01 X 19.5306 Y 75.8091 Z -0.1250 624 | G01 X 19.5936 Y 75.7198 Z -0.1250 625 | G01 X 19.6493 Y 75.6374 Z -0.1250 626 | G01 X 19.6984 Y 75.5620 Z -0.1250 627 | G01 X 19.7411 Y 75.4935 Z -0.1250 628 | G01 X 19.7779 Y 75.4320 Z -0.1250 629 | G01 X 19.8091 Y 75.3775 Z -0.1250 630 | G01 X 19.8352 Y 75.3302 Z -0.1250 631 | G01 X 19.8564 Y 75.2899 Z -0.1250 632 | G01 X 19.8730 Y 75.2568 Z -0.1250 633 | G01 X 19.8855 Y 75.2309 Z -0.1250 634 | G01 X 19.8941 Y 75.2123 Z -0.1250 635 | G01 X 19.8990 Y 75.2010 Z -0.1250 636 | G01 X 20.9777 Y 72.5408 Z -0.1250 637 | G02 X 23.6012 Y 73.4206 Z -0.1250 I 8.4392 J -20.8133 638 | G02 X 26.3130 Y 73.9711 Z -0.1250 I 5.8157 J -21.6931 639 | G01 X 25.9163 Y 76.8142 Z -0.1250 640 | G01 X 25.9149 Y 76.8264 Z -0.1250 641 | G01 X 25.9130 Y 76.8468 Z -0.1250 642 | G01 X 25.9108 Y 76.8754 Z -0.1250 643 | G01 X 25.9087 Y 76.9125 Z -0.1250 644 | G01 X 25.9069 Y 76.9580 Z -0.1250 645 | G01 X 25.9058 Y 77.0120 Z -0.1250 646 | G01 X 25.9056 Y 77.0748 Z -0.1250 647 | G01 X 25.9067 Y 77.1464 Z -0.1250 648 | G01 X 25.9094 Y 77.2271 Z -0.1250 649 | G01 X 25.9141 Y 77.3169 Z -0.1250 650 | G01 X 25.9212 Y 77.4162 Z -0.1250 651 | G01 X 25.9310 Y 77.5250 Z -0.1250 652 | G01 X 25.9440 Y 77.6437 Z -0.1250 653 | G01 X 25.9605 Y 77.7724 Z -0.1250 654 | G01 X 25.9812 Y 77.9115 Z -0.1250 655 | G01 X 26.0064 Y 78.0613 Z -0.1250 656 | G01 X 26.0367 Y 78.2219 Z -0.1250 657 | G01 X 26.0727 Y 78.3938 Z -0.1250 658 | G01 X 26.1151 Y 78.5773 Z -0.1250 659 | G01 X 26.1644 Y 78.7728 Z -0.1250 660 | G01 X 26.2215 Y 78.9806 Z -0.1250 661 | G01 X 26.2870 Y 79.2011 Z -0.1250 662 | G01 X 26.3620 Y 79.4348 Z -0.1250 663 | G01 X 26.4474 Y 79.6821 Z -0.1250 664 | G01 X 26.5441 Y 79.9435 Z -0.1250 665 | G01 X 26.6533 Y 80.2194 Z -0.1250 666 | G01 X 26.7763 Y 80.5104 Z -0.1250 667 | G01 X 26.9144 Y 80.8169 Z -0.1250 668 | G02 X 29.4128 Y 80.9244 Z -0.1250 I 2.5034 J -29.0895 669 | G02 X 31.9113 Y 80.8177 Z -0.1250 I 0.0049 J -29.1970 670 | G01 X 31.9113 Y 80.8177 Z -0.1250 671 | G01 X 32.0495 Y 80.5113 Z -0.1250 672 | G01 X 32.1726 Y 80.2203 Z -0.1250 673 | G01 X 32.2819 Y 79.9443 Z -0.1250 674 | G01 X 32.3787 Y 79.6829 Z -0.1250 675 | G01 X 32.4641 Y 79.4356 Z -0.1250 676 | G01 X 32.5391 Y 79.2020 Z -0.1250 677 | G01 X 32.6048 Y 78.9814 Z -0.1250 678 | G01 X 32.6619 Y 78.7737 Z -0.1250 679 | G01 X 32.7113 Y 78.5782 Z -0.1250 680 | G01 X 32.7537 Y 78.3947 Z -0.1250 681 | G01 X 32.7898 Y 78.2229 Z -0.1250 682 | G01 X 32.8201 Y 78.0622 Z -0.1250 683 | G01 X 32.8454 Y 77.9125 Z -0.1250 684 | G01 X 32.8660 Y 77.7734 Z -0.1250 685 | G01 X 32.8826 Y 77.6446 Z -0.1250 686 | G01 X 32.8956 Y 77.5260 Z -0.1250 687 | G01 X 32.9055 Y 77.4171 Z -0.1250 688 | G01 X 32.9125 Y 77.3179 Z -0.1250 689 | G01 X 32.9173 Y 77.2281 Z -0.1250 690 | G01 X 32.9200 Y 77.1474 Z -0.1250 691 | G01 X 32.9212 Y 77.0758 Z -0.1250 692 | G01 X 32.9210 Y 77.0130 Z -0.1250 693 | G01 X 32.9199 Y 76.9589 Z -0.1250 694 | G01 X 32.9181 Y 76.9135 Z -0.1250 695 | G01 X 32.9160 Y 76.8764 Z -0.1250 696 | G01 X 32.9138 Y 76.8478 Z -0.1250 697 | G01 X 32.9119 Y 76.8274 Z -0.1250 698 | G01 X 32.9106 Y 76.8152 Z -0.1250 699 | G01 X 32.5147 Y 73.9719 Z -0.1250 700 | G02 X 35.2266 Y 73.4222 Z -0.1250 I -3.0977 J -22.2445 701 | G02 X 37.8503 Y 72.5431 Z -0.1250 I -5.8096 J -21.6948 702 | G01 X 38.9283 Y 75.2037 Z -0.1250 703 | G01 X 38.9332 Y 75.2149 Z -0.1250 704 | G01 X 38.9418 Y 75.2335 Z -0.1250 705 | G01 X 38.9542 Y 75.2594 Z -0.1250 706 | G01 X 38.9709 Y 75.2926 Z -0.1250 707 | G01 X 38.9921 Y 75.3328 Z -0.1250 708 | G01 X 39.0181 Y 75.3802 Z -0.1250 709 | G01 X 39.0494 Y 75.4347 Z -0.1250 710 | G01 X 39.0861 Y 75.4962 Z -0.1250 711 | G01 X 39.1288 Y 75.5647 Z -0.1250 712 | G01 X 39.1778 Y 75.6401 Z -0.1250 713 | G01 X 39.2336 Y 75.7225 Z -0.1250 714 | G01 X 39.2965 Y 75.8119 Z -0.1250 715 | G01 X 39.3670 Y 75.9082 Z -0.1250 716 | G01 X 39.4457 Y 76.0114 Z -0.1250 717 | G01 X 39.5332 Y 76.1215 Z -0.1250 718 | G01 X 39.6299 Y 76.2386 Z -0.1250 719 | G01 X 39.7364 Y 76.3626 Z -0.1250 720 | G01 X 39.8536 Y 76.4935 Z -0.1250 721 | G01 X 39.9820 Y 76.6312 Z -0.1250 722 | G01 X 40.1225 Y 76.7758 Z -0.1250 723 | G01 X 40.2758 Y 76.9272 Z -0.1250 724 | G01 X 40.4428 Y 77.0854 Z -0.1250 725 | G01 X 40.6246 Y 77.2503 Z -0.1250 726 | G01 X 40.8222 Y 77.4218 Z -0.1250 727 | G01 X 41.0366 Y 77.5998 Z -0.1250 728 | G01 X 41.2692 Y 77.7842 Z -0.1250 729 | G01 X 41.5212 Y 77.9747 Z -0.1250 730 | G01 X 41.7940 Y 78.1711 Z -0.1250 731 | G02 X 44.0115 Y 77.0150 Z -0.1250 I -12.3771 J -26.4438 732 | G02 X 46.1219 Y 75.6733 Z -0.1250 I -14.5946 J -25.2876 733 | G01 X 46.1219 Y 75.6733 Z -0.1250 734 | G01 X 46.0883 Y 75.3388 Z -0.1250 735 | G01 X 46.0494 Y 75.0253 Z -0.1250 736 | G01 X 46.0061 Y 74.7317 Z -0.1250 737 | G01 X 45.9593 Y 74.4570 Z -0.1250 738 | G01 X 45.9096 Y 74.2001 Z -0.1250 739 | G01 X 45.8578 Y 73.9602 Z -0.1250 740 | G01 X 45.8044 Y 73.7364 Z -0.1250 741 | G01 X 45.7500 Y 73.5279 Z -0.1250 742 | G01 X 45.6950 Y 73.3339 Z -0.1250 743 | G01 X 45.6400 Y 73.1539 Z -0.1250 744 | G01 X 45.5853 Y 72.9870 Z -0.1250 745 | G01 X 45.5312 Y 72.8327 Z -0.1250 746 | G01 X 45.4783 Y 72.6904 Z -0.1250 747 | G01 X 45.4266 Y 72.5596 Z -0.1250 748 | G01 X 45.3766 Y 72.4398 Z -0.1250 749 | G01 X 45.3285 Y 72.3305 Z -0.1250 750 | G01 X 45.2826 Y 72.2313 Z -0.1250 751 | G01 X 45.2392 Y 72.1418 Z -0.1250 752 | G01 X 45.1983 Y 72.0617 Z -0.1250 753 | G01 X 45.1604 Y 71.9905 Z -0.1250 754 | G01 X 45.1255 Y 71.9279 Z -0.1250 755 | G01 X 45.0940 Y 71.8736 Z -0.1250 756 | G01 X 45.0660 Y 71.8273 Z -0.1250 757 | G01 X 45.0417 Y 71.7888 Z -0.1250 758 | G01 X 45.0214 Y 71.7578 Z -0.1250 759 | G01 X 45.0052 Y 71.7341 Z -0.1250 760 | G01 X 44.9934 Y 71.7174 Z -0.1250 761 | G01 X 44.9861 Y 71.7075 Z -0.1250 762 | G01 X 43.2216 Y 69.4431 Z -0.1250 763 | G02 X 45.2954 Y 67.6110 Z -0.1250 I -13.8047 J -17.7157 764 | G02 X 47.1281 Y 65.5379 Z -0.1250 I -15.8785 J -15.8836 765 | G01 X 49.3919 Y 67.3031 Z -0.1250 766 | G01 X 49.4018 Y 67.3103 Z -0.1250 767 | G01 X 49.4185 Y 67.3222 Z -0.1250 768 | G01 X 49.4422 Y 67.3384 Z -0.1250 769 | G01 X 49.4732 Y 67.3587 Z -0.1250 770 | G01 X 49.5117 Y 67.3830 Z -0.1250 771 | G01 X 49.5580 Y 67.4111 Z -0.1250 772 | G01 X 49.6123 Y 67.4426 Z -0.1250 773 | G01 X 49.6749 Y 67.4775 Z -0.1250 774 | G01 X 49.7461 Y 67.5154 Z -0.1250 775 | G01 X 49.8262 Y 67.5563 Z -0.1250 776 | G01 X 49.9157 Y 67.5998 Z -0.1250 777 | G01 X 50.0148 Y 67.6457 Z -0.1250 778 | G01 X 50.1241 Y 67.6938 Z -0.1250 779 | G01 X 50.2439 Y 67.7439 Z -0.1250 780 | G01 X 50.3747 Y 67.7956 Z -0.1250 781 | G01 X 50.5169 Y 67.8486 Z -0.1250 782 | G01 X 50.6712 Y 67.9027 Z -0.1250 783 | G01 X 50.8381 Y 67.9574 Z -0.1250 784 | G01 X 51.0182 Y 68.0125 Z -0.1250 785 | G01 X 51.2121 Y 68.0675 Z -0.1250 786 | G01 X 51.4206 Y 68.1220 Z -0.1250 787 | G01 X 51.6444 Y 68.1755 Z -0.1250 788 | G01 X 51.8843 Y 68.2274 Z -0.1250 789 | G01 X 52.1411 Y 68.2771 Z -0.1250 790 | G01 X 52.4158 Y 68.3241 Z -0.1250 791 | G01 X 52.7094 Y 68.3675 Z -0.1250 792 | G01 X 53.0229 Y 68.4065 Z -0.1250 793 | G01 X 53.3574 Y 68.4402 Z -0.1250 794 | G02 X 54.6997 Y 66.3302 Z -0.1250 I -23.9405 J -16.7128 795 | G02 X 55.8566 Y 64.1131 Z -0.1250 I -25.2829 J -14.6028 796 | G01 X 55.8566 Y 64.1131 Z -0.1250 797 | G01 X 55.6603 Y 63.8402 Z -0.1250 798 | G01 X 55.4698 Y 63.5881 Z -0.1250 799 | G01 X 55.2856 Y 63.3555 Z -0.1250 800 | G01 X 55.1076 Y 63.1410 Z -0.1250 801 | G01 X 54.9362 Y 62.9434 Z -0.1250 802 | G01 X 54.7713 Y 62.7615 Z -0.1250 803 | G01 X 54.6132 Y 62.5944 Z -0.1250 804 | G01 X 54.4618 Y 62.4411 Z -0.1250 805 | G01 X 54.3173 Y 62.3006 Z -0.1250 806 | G01 X 54.1796 Y 62.1721 Z -0.1250 807 | G01 X 54.0487 Y 62.0549 Z -0.1250 808 | G01 X 53.9248 Y 61.9483 Z -0.1250 809 | G01 X 53.8078 Y 61.8516 Z -0.1250 810 | G01 X 53.6976 Y 61.7641 Z -0.1250 811 | G01 X 53.5944 Y 61.6854 Z -0.1250 812 | G01 X 53.4982 Y 61.6148 Z -0.1250 813 | G01 X 53.4089 Y 61.5518 Z -0.1250 814 | G01 X 53.3264 Y 61.4961 Z -0.1250 815 | G01 X 53.2510 Y 61.4471 Z -0.1250 816 | G01 X 53.1825 Y 61.4043 Z -0.1250 817 | G01 X 53.1211 Y 61.3676 Z -0.1250 818 | G01 X 53.0666 Y 61.3363 Z -0.1250 819 | G01 X 53.0192 Y 61.3103 Z -0.1250 820 | G01 X 52.9790 Y 61.2890 Z -0.1250 821 | G01 X 52.9458 Y 61.2724 Z -0.1250 822 | G01 X 52.9200 Y 61.2599 Z -0.1250 823 | G01 X 52.9013 Y 61.2513 Z -0.1250 824 | G01 X 52.8901 Y 61.2464 Z -0.1250 825 | G01 X 50.2299 Y 60.1676 Z -0.1250 826 | G02 X 51.1098 Y 57.5441 Z -0.1250 I -20.8130 J -8.4401 827 | G02 X 51.6604 Y 54.8324 Z -0.1250 I -21.6929 J -5.8167 828 | G01 X 54.5036 Y 55.2292 Z -0.1250 829 | G01 X 54.5158 Y 55.2306 Z -0.1250 830 | G01 X 54.5361 Y 55.2325 Z -0.1250 831 | G01 X 54.5648 Y 55.2346 Z -0.1250 832 | G01 X 54.6018 Y 55.2368 Z -0.1250 833 | G01 X 54.6473 Y 55.2385 Z -0.1250 834 | G01 X 54.7014 Y 55.2397 Z -0.1250 835 | G01 X 54.7641 Y 55.2399 Z -0.1250 836 | G01 X 54.8358 Y 55.2388 Z -0.1250 837 | G01 X 54.9164 Y 55.2361 Z -0.1250 838 | G01 X 55.0063 Y 55.2313 Z -0.1250 839 | G01 X 55.1055 Y 55.2243 Z -0.1250 840 | G01 X 55.2143 Y 55.2145 Z -0.1250 841 | G01 X 55.3330 Y 55.2015 Z -0.1250 842 | G01 X 55.4618 Y 55.1850 Z -0.1250 843 | G01 X 55.6009 Y 55.1644 Z -0.1250 844 | G01 X 55.7506 Y 55.1391 Z -0.1250 845 | G01 X 55.9113 Y 55.1088 Z -0.1250 846 | G01 X 56.0832 Y 55.0728 Z -0.1250 847 | G01 X 56.2667 Y 55.0305 Z -0.1250 848 | G01 X 56.4621 Y 54.9812 Z -0.1250 849 | G01 X 56.6699 Y 54.9241 Z -0.1250 850 | G01 X 56.8905 Y 54.8586 Z -0.1250 851 | G01 X 57.1242 Y 54.7836 Z -0.1250 852 | G01 X 57.3715 Y 54.6982 Z -0.1250 853 | G01 X 57.6329 Y 54.6015 Z -0.1250 854 | G01 X 57.9087 Y 54.4923 Z -0.1250 855 | G01 X 58.1996 Y 54.3693 Z -0.1250 856 | G01 X 58.5064 Y 54.2313 Z -0.1250 857 | G01 X 58.5067 Y 54.2259 Z -0.1250 858 | G00 Z 5.0000 859 | 860 | (End cutting path id: path4701) 861 | 862 | 863 | 864 | (Footer) 865 | M5 866 | G00 X0.0000 Y0.0000 867 | M2 868 | (Using default footer. To add your own footer create file "footer" in the output dir.) 869 | (end) 870 | % --------------------------------------------------------------------------------