├── dev ├── td-modules │ ├── base_sop_to_svg │ │ ├── requirements.txt │ │ ├── base_sop_to_svg.tox │ │ └── svgEXT.py │ └── tools │ │ ├── releaseFiles.json │ │ └── collect_files.py ├── dev-env.toe └── scripts │ └── extHelperMOD.py ├── assets ├── circle.PNG ├── copy1.PNG ├── copy2.PNG ├── null_icon.png ├── circle-openarc.PNG ├── copy1-svg-settings.PNG └── copy2-svg-settings.PNG ├── release └── sop-to-svg.tox ├── .gitignore ├── LICENSE └── README.md /dev/td-modules/base_sop_to_svg/requirements.txt: -------------------------------------------------------------------------------- 1 | svgwrite -------------------------------------------------------------------------------- /dev/dev-env.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/dev/dev-env.toe -------------------------------------------------------------------------------- /assets/circle.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/circle.PNG -------------------------------------------------------------------------------- /assets/copy1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/copy1.PNG -------------------------------------------------------------------------------- /assets/copy2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/copy2.PNG -------------------------------------------------------------------------------- /assets/null_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/null_icon.png -------------------------------------------------------------------------------- /release/sop-to-svg.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/release/sop-to-svg.tox -------------------------------------------------------------------------------- /assets/circle-openarc.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/circle-openarc.PNG -------------------------------------------------------------------------------- /assets/copy1-svg-settings.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/copy1-svg-settings.PNG -------------------------------------------------------------------------------- /assets/copy2-svg-settings.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/assets/copy2-svg-settings.PNG -------------------------------------------------------------------------------- /dev/td-modules/base_sop_to_svg/base_sop_to_svg.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/touchdesigner-sop-to-svg/HEAD/dev/td-modules/base_sop_to_svg/base_sop_to_svg.tox -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | dev/dep/* 3 | release/dep/* 4 | 5 | # the git-pip python bits 6 | dev/dep/python/get-pip.py 7 | release/dep/python/get-pip.py 8 | get-pip.py 9 | 10 | # the external TD tools 11 | dev/td-modules/tools/releaseVersions/* 12 | 13 | # mac files 14 | *.DS_STORE -------------------------------------------------------------------------------- /dev/td-modules/tools/releaseFiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets" : [ 3 | { 4 | "name" : "base_save", 5 | "url" : "https://github.com/raganmd/touchdesigner-save-external/blob/master/release/base_save.tox?raw=true" 6 | }, 7 | { 8 | "name" : "base_save_for_release", 9 | "url" : "https://github.com/raganmd/touchdesigner-tox-prep-for-release/blob/master/release/base_save_for_release.tox?raw=true" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Matthew Ragan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dev/td-modules/tools/collect_files.py: -------------------------------------------------------------------------------- 1 | import wget 2 | import json 3 | import os 4 | 5 | # vars for execution 6 | release_dir = "releaseVersions" 7 | json_file_ext = "releaseFiles.json" 8 | 9 | # a function to check for and create our release_dir if necessary 10 | def check_for_dir(): 11 | if os.path.isdir(release_dir): 12 | pass 13 | else: 14 | print("Creating releaseVersions directory") 15 | os.mkdir(release_dir) 16 | pass 17 | 18 | # a function to delete old versions 19 | def delete_old_files(del_dir): 20 | # loop through all files in directory and delete them 21 | for each_file in os.listdir(del_dir): 22 | target_file = '{}\{}'.format(del_dir, each_file) 23 | os.remove(target_file) 24 | print('deleting {}'.format(target_file)) 25 | pass 26 | 27 | # a function to download new files as described in external file 28 | def download_files(json_file_with_externals): 29 | # open extenral file and create python dictionary out of json 30 | allRemotes = open(json_file_with_externals, "r") 31 | workingDict = json.load(allRemotes) 32 | allRemotes.close() 33 | 34 | # loop through all entries and download them to the directory specified 35 | for each_remote in workingDict['targets']: 36 | save_name = each_remote['name'] 37 | target_url = each_remote['url'] 38 | 39 | target_path = "{dir}\\{file}.tox".format(dir=release_dir, file=save_name) 40 | wget.download(target_url, target_path) 41 | 42 | print("\ndownloading {save_url} \nsaving to {target_path} \n".format(target_path=target_path, save_url=target_url)) 43 | pass 44 | 45 | # check for directory 46 | print("Checking for releaseVersions directory") 47 | check_for_dir() 48 | 49 | # delete old files 50 | print("Deleting Old Files") 51 | delete_old_files(release_dir) 52 | 53 | print('- ' * 5) 54 | print('\n') 55 | 56 | # download latest release versions - these are all from master branches 57 | print("Downloading New Files") 58 | download_files(json_file_ext) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # touchdesigner-sop-to-svg 2 | ### Matthew Ragan ### 3 | _[matthewragan.com](http://matthewragan.com)_ 4 | A pipeline for handling the SOP to SVG pipeline. This is especially handy for using procedurally generated geometry for paths to be cut or plotted. 5 | 6 | # Dependencies 7 | **TouchDesigner 099** - https://www.derivative.ca/099/Downloads/ 8 | This tox is written and maintained in 099. 9 | 10 | **SVG Write** - https://pypi.python.org/pypi/svgwrite/ 11 | This tox requires the Python library SVG Write to function correctly. If you're familiar with adding external Python libraries in Touch please carry on. If that's a new process to you, I'd recommend that you read through the python documentation on the derivative wiki: 12 | https://www.derivative.ca/wiki099/index.php?title=Introduction_to_Python_Tutorial 13 | 14 | ## Adding Dependencies to TouchDesigner 15 | If you'd like to add the modules directly to your TouchDesigner folder you can do that with the following commands 16 | 17 | ### **Windows** 18 | First install [Python 3.5.1](https://www.python.org/downloads/release/python-351/) 19 | From the command line `pip install --target=/path/to/your/packages/directory/for/TD package_to_install` 20 | 21 | There's a good chance that looks like: 22 | From the command line `pip install --target="C:\Program Files\Derivative\TouchDesigner099\bin\Lib\site-packages" svgwrite` 23 | 24 | ### **MacOS** 25 | First install [Python 3.5.1](https://www.python.org/downloads/release/python-351/) 26 | `curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py` 27 | `python3 get-pip.py` 28 | `python3 -m pip install --target=/Applications/TouchDesigner099.app/Contents/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages svgwrite` 29 | 30 | # Overview 31 | Scalable Vector Graphics (SVG) are a handy means of creating images that aren't bound by the rules of raster graphics. Illustrators and designers often use SVGs for a variety of purposes, but they're especially interesting when it comes to interacting with plotting and cutting machinery. Laser and vinyl cutters, plotters and all manner of other devices can use SVGs rather than raster based graphics. 32 | 33 | Derivative's TouchDesigner is well known for working with raster based graphics, but there's little support for capturing and outputting SVGs as a built in approach. It does, however, support a robust python layer which is capable of handling this task. The primary design in this TouchDesigner module is to target the process of converting Surface Operators (SOPs) to SVGs so they can be plotted or laser cut. 34 | 35 | ![plot1](assets/null_icon.png) 36 | 37 | To that end in this TOX you'll find several means of capturing and plotting your geometry. 38 | 39 | # Feature and Parameters 40 | Not all geometry is created equal, and while it's lovely to imagine that there might be a single perfect way to handle drawing all shapes, but alas it's a little more dicey than that when plotting lines. A large concern here is the need to be able to draw closed shapes, open shapes, and images that have both closed and open shapes. 41 | 42 | This is addressed through a set of custom parameters and a python extension. The python extension can be found inside of the base_sop_to_svg tox as svgEXT. If Python is in your wheelhouse, take a look inside the tox as the methods and members are documented inside of this extension. 43 | 44 | Parameter | Description and Function 45 | ---|--- 46 | Polyline SOP | This parameter accepts a SOP and should be a group of any primitives you'd like to have drawn as open polylines. Important to note, the extension will draw each primitive in your SOP as its own open shaped polyline. 47 | PolygonSOP | This parameter accepts a SOP and should be a group of any primitives you'd like to have drawn as closed polygons. Important to note, the extension will draw each primitive in your SOP as its own closed shape polygon. 48 | Canvas Size | The axidraw (my primary plotting device) supports two different canvas sizes: Letter and A4. Using this parameter will help ensure that your svg is correctly set up to export at dimensions that are friendly to inkscape for plotting right away without needing additional document setup. 49 | Destination Directory | This should be a path to your desired directory location for your file. It's important to note that TouchDesigner has an undesirable legacy feature that uses a built in variable $TOUCH that represents the location of your project file. Using this variable will result in an error at save. To avoid this, make sure that your save destination is not the same as where your project is saved. If you're using an un-saved project, this typically defaults to the desktop - in this case, save to a destination other than your desktop. 50 | File Name | The string name for your file. The file extension .svg will be added to your saved file - you do not need to include this. 51 | SVG Type | Here you can choose to save an SVG that's comprised of polygons and polylines, only polylines, or only polygons. Use the appropriate menu selection to match your intended output. 52 | Save SVG | Click to save to file. 53 | Use Camera | Toggle on to use perspective camera, Toggle off for orthographic rendering. 54 | Aspect | The aspect ratio for the final output. Not really used. 55 | 56 | # Using the TOX 57 | Let's look at a simple use case where we want to draw both closed and open shapes. 58 | 59 | ### Drawing Closed Shapes 60 | Let's use a circle SOP set to 3 divisions. 61 | 62 | ![circle-div3](assets/circle.PNG) 63 | 64 | Next we'll use a copy SOP to make 7 copies. Let's set the rz parameter to 8, and the uniform scale parameter to 0.8. 65 | 66 | ![copy circle](assets/copy1.PNG) 67 | 68 | Let's connect this to a null SOP, and then drag and drop the null SOP onto the Polygon SOP parameter. Select a canvas size, destination directory, and name your file. Make sure to select the Polygon only SVG Type, and click save. 69 | 70 | ![svg settings](assets/copy1-svg-settings.PNG) 71 | 72 | Congratulations, you've made your first SVG out of SOPs. 73 | 74 | ### Drawing Open Shapes 75 | Let's start with a circle SOP with 40 divisions. Let's make sure our arc type is set to open, and our arc angles are set to 90. 76 | 77 | ![ope arc](assets/circle-openarc.PNG) 78 | 79 | Next let's connect a copy SOP to make 7 copies. This time let's change our rz par to 45, and our uniform scale to 0.8. 80 | 81 | ![copy sop](assets/copy2.PNG) 82 | 83 | Connect your copy SOP to a null SOP, now drag and drop your null onto the Polyline SOP parameter. Select a canvas size, destination directory, and name your file. Make sure to select the Polygon only SVG Type, and click save. 84 | 85 | ![poly line settings](assets/copy2-svg-settings.PNG) 86 | 87 | Congratulations, you've made your second SVG out of SOPs. 88 | 89 | [plot1]:https://instagram.fsnc1-1.fna.fbcdn.net/t51.2885-15/e35/21480091_1766025503697997_8211273022301011968_n.jpg 90 | [circle3div]:https://github.com/raganmd/touchdesigner-sop-to-svg/blob/master/assets/circle.PNG 91 | [copyCircle]:https://github.com/raganmd/touchdesigner-sop-to-svg/blob/master/assets/copy1.PNG 92 | [polygonSettings]:https://github.com/raganmd/touchdesigner-sop-to-svg/blob/master/assets/copy1-svg-settings.PNG?raw=true 93 | [circle-openarc]:https://github.com/raganmd/touchdesigner-sop-to-svg/blob/master/assets/circle-openarc.PNG 94 | [copyCircle2]:https://github.com/raganmd/touchdesigner-sop-to-svg/blob/master/assets/copy2.PNG 95 | [polylineSettings]:https://github.com/raganmd/touchdesigner-sop-to-svg/blob/master/assets/copy2-svg-settings.PNG -------------------------------------------------------------------------------- /dev/scripts/extHelperMOD.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import platform 4 | 5 | def Check_dep(debug=False): 6 | ''' 7 | Check for dependencies and project path. 8 | 9 | This method checks to see if the path for the project is included in sys.path. 10 | This will ensure that the python modules used in the project will 11 | be respected by the Touch. 12 | 13 | Notes 14 | --------------- 15 | 'self' does not need to be included in the Args section. 16 | 17 | Args 18 | --------------- 19 | debug (bool): 20 | > a bool to allow for us to print out the content of sys.path 21 | 22 | Returns 23 | --------------- 24 | None 25 | ''' 26 | 27 | # our path for all non-standard python modules 28 | dep_path = '{}/dep/python/'.format(project.folder) 29 | 30 | # if our path is already present we can skip this step 31 | if dep_path in sys.path: 32 | pass 33 | 34 | # insert the python path into our sys.path 35 | else: 36 | sys.path.insert(0, dep_path) 37 | 38 | # print each path in sys.path if debug is true: 39 | if debug: 40 | 41 | for each in sys.path: 42 | print(each) 43 | else: 44 | pass 45 | 46 | pass 47 | 48 | def Install_python_external(): 49 | ''' 50 | Check and install any external modules. 51 | 52 | This method will go through all the necessary steps to enesure 53 | that our external modules are loaded into our project specific 54 | location. This approach assumes that external libraries should be 55 | housed with the project, rather than with the standalone python 56 | installation, or with the Touch Installation. This ensrues a more consistent, 57 | reliable, and portable approach when working with non-standard python 58 | modules. 59 | 60 | Notes 61 | --------------- 62 | 'self' does not need to be included in the Args section. 63 | 64 | Args 65 | --------------- 66 | None 67 | 68 | Returns 69 | --------------- 70 | None 71 | ''' 72 | 73 | dep_path = '{}/dep'.format(project.folder) 74 | python_path = '{}/dep/python'.format(project.folder) 75 | scripts_reqs_path = '{proj}/dep/{name}'.format(proj=project.folder, name=parent().par.Name) 76 | requirements = '{}/requirements.txt'.format(scripts_reqs_path) 77 | reqs_dat = op('reqs') 78 | win_py_dep = '{}/update-dep-python-windows.cmd'.format(scripts_reqs_path) 79 | mac_py_dep = '{}/update-dep-python-mac.sh'.format(scripts_reqs_path) 80 | 81 | # check to see if /dep is in the project folder 82 | if os.path.isdir(dep_path): 83 | pass 84 | # create the direcotry if it's not there 85 | else: 86 | os.mkdir(dep_path) 87 | 88 | # check to see if /python is in the project folder 89 | if os.path.isdir(python_path): 90 | pass 91 | # create the direcotry if it's not there 92 | else: 93 | os.mkdir(python_path) 94 | 95 | # check to see if there's a scripts and requirements folder 96 | if os.path.isdir(scripts_reqs_path): 97 | pass 98 | # create the direcotry if it's not there 99 | else: 100 | os.mkdir(scripts_reqs_path) 101 | 102 | # check to see if the requirements txt is in place 103 | if os.path.isfile(requirements): 104 | pass 105 | else: 106 | reqs_file = open(requirements, 'w') 107 | reqs_file.write(reqs_dat.text) 108 | reqs_file.close() 109 | 110 | # check to see if our auto-generaetd scripts are in place 111 | has_win_py = os.path.isfile(win_py_dep) 112 | has_mac_py = os.path.isfile(mac_py_dep) 113 | 114 | win_py_txt = me.mod.extHelperMOD.win_dep(scripts_reqs_path, python_path) 115 | mac_py_txt = me.mod.extHelperMOD.mac_dep(scripts_reqs_path, python_path) 116 | 117 | # identify platform 118 | osPlatform = platform.system() 119 | 120 | # on windows 121 | if osPlatform == "Windows": 122 | # create the script to handle grabbing our dependencies 123 | req_file = open(win_py_dep, 'w') 124 | req_file.write(win_py_txt) 125 | req_file.close() 126 | 127 | # check to see if there is anything in the python dep dir 128 | # for now we'll assume that if there are files here we 129 | # successfully installed our python dependencies 130 | if len(os.listdir(python_path)) == 0: 131 | subprocess.Popen([win_py_dep]) 132 | 133 | else: 134 | pass 135 | # on mac 136 | elif osPlatform == "Darwin": 137 | # create the script to handle grabbing our dependencies 138 | mac_file = open(mac_py_dep, 'w') 139 | mac_file.write(mac_py_txt) 140 | mac_file.close() 141 | 142 | # change file permissions for the file 143 | subprocess.call(['chmod', '755', mac_py_dep]) 144 | 145 | # change file to be executable 146 | subprocess.call(['chmod', '+x', mac_py_dep]) 147 | 148 | # check to see if there is anything in the python dep dir 149 | # for now we'll assume that if there are files here we 150 | # successfully installed our python dependencies 151 | if len(os.listdir(python_path)) == 0: 152 | print("Running Install Script") 153 | subprocess.Popen(["open", "-a", "Terminal.app", mac_py_dep]) 154 | else: 155 | pass 156 | 157 | else: 158 | pass 159 | 160 | return 161 | 162 | def win_dep(requirementsPath, targetPath): 163 | ''' 164 | Format text for command line execution. 165 | 166 | This method returns a formatted script to be executed by windows to 167 | both upgrade pip, and install any modules listed in the requirements 168 | DAT. 169 | 170 | Notes 171 | --------------- 172 | 'self' does not need to be included in the Args section. 173 | 174 | Args 175 | --------------- 176 | requirementsPath (str): 177 | > a string path to the requirements txt doc 178 | 179 | targetPath (str): 180 | > a string path to the target installation directory 181 | 182 | Returns 183 | --------------- 184 | formatted_win_txt (str): 185 | > the formatted text for a .cmd file for automated installation 186 | ''' 187 | win_txt = ''':: Update dependencies 188 | 189 | :: make sure pip is up to date 190 | python -m pip install --user --upgrade pip 191 | 192 | :: install requirements 193 | pip install -r {reqs}/requirements.txt --target="{target}"''' 194 | 195 | formatted_win_txt = win_txt.format(reqs=requirementsPath, target=targetPath) 196 | 197 | return formatted_win_txt 198 | 199 | 200 | def mac_dep(requirementsPath, targetPath): 201 | ''' 202 | Format text for command line execution. 203 | 204 | This method returns a formatted script to be executed by macOS to 205 | both upgrade pip, and install any modules listed in the requirements 206 | DAT. 207 | 208 | Notes 209 | --------------- 210 | 'self' does not need to be included in the Args section. 211 | 212 | Args 213 | --------------- 214 | requirementsPath (str): 215 | > a string path to the requirements txt doc 216 | 217 | targetPath (str): 218 | > a string path to the target installation directory 219 | 220 | Returns 221 | --------------- 222 | formatted_mac_txt (str): 223 | > the formatted text for a .sh file for automated installation 224 | ''' 225 | mac_txt = ''' 226 | #!/bin/bash 227 | 228 | dep=$(dirname "$0") 229 | pythonDir=/python 230 | 231 | # change current direcotry to where the script is run from 232 | dirname "$(readlink -f "$0")" 233 | 234 | # fix up pip with python3 235 | curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 236 | python3 get-pip.py 237 | 238 | # Update dependencies 239 | 240 | # make sure pip is up to date 241 | python3 -m pip install --user --upgrade pip 242 | 243 | # install requirements 244 | python3 -m pip install -r {reqs}/requirements.txt --target={target}''' 245 | formatted_mac_txt = mac_txt.format(reqs=requirementsPath, target=targetPath) 246 | return formatted_mac_txt -------------------------------------------------------------------------------- /dev/td-modules/base_sop_to_svg/svgEXT.py: -------------------------------------------------------------------------------- 1 | '''SVG Write in TouchDesigner Class 2 | Authors | matthew ragan 3 | matthewragan.com 4 | ''' 5 | 6 | import svgwrite 7 | import numpy as np 8 | import math 9 | import webbrowser 10 | 11 | class Soptosvg: 12 | ''' 13 | This class is inteded to handle writing SVGs from SOPs in TouchDesigner. 14 | 15 | This is a largely experimental approach so there are bound to be things 16 | that are wrong or don't work. 17 | 18 | --------------- 19 | 20 | ''' 21 | 22 | def __init__( self ): 23 | ''' This is the init method for the Soptosvg process 24 | ''' 25 | self.Polylinesop = parent.svg.par.Polylinesop 26 | self.Polygonsop = parent.svg.par.Polygonsop 27 | self.Svgtype = parent.svg.par.Svgtype 28 | self.Filepath = "{dir}/{file}.svg" 29 | self.UseCamera = parent.svg.par.Usecamera 30 | self.Camera = parent.svg.par.Camera 31 | self.Aspect = (parent.svg.par.Aspect1, parent.svg.par.Aspect2) 32 | 33 | self.Axidocumentation = "http://wiki.evilmadscientist.com/AxiDraw" 34 | self.Axipdf = "http://cdn.evilmadscientist.com/wiki/axidraw/software/AxiDraw_V33.pdf" 35 | self.Svgwritedocumentation = "http://svgwrite.readthedocs.io/en/latest/svgwrite.html" 36 | 37 | print( "Sop to SVG Initialized" ) 38 | return 39 | 40 | 41 | def WorldToCam(self, oldP): 42 | '''Method to convert worldspace coords to cameraspace coords. 43 | 44 | Args 45 | ------------- 46 | oldP (tdu.Position) : the tdu.Position to convert to camera space. 47 | 48 | Returns 49 | ------------- 50 | newP (tuple) : tuple of x,y coordinates after camera projection. 51 | 52 | ''' 53 | camera = op(self.Camera.eval()) 54 | view = camera.transform() 55 | view.invert() 56 | pers = camera.projection( self.Aspect[0].eval(), self.Aspect[1].eval() ) 57 | viewP = view * oldP 58 | adjusted = pers * viewP 59 | newX = adjusted.x/adjusted.z 60 | newY = adjusted.y/adjusted.z 61 | newP = (newX, newY) 62 | 63 | return newP 64 | 65 | def Canvas_size(self): 66 | ''' This is a helper method to return the dimensions of the canvas. 67 | 68 | Having an output size for the SVG isn't necessary, but for working with the axi-draw 69 | it's often helpful to have your file set-up and ready to plot from if possible. 70 | This method grabs the par of the svgWrite tox and returns those dimensions to other methods. 71 | 72 | Notes 73 | --------------- 74 | 75 | Args 76 | --------------- 77 | none 78 | 79 | Returns 80 | --------------- 81 | canvassize (tupple) : a tupple of width and height dimensions measured in millimeters 82 | ''' 83 | canvassize = None 84 | 85 | if parent.svg.par.Canvassize == 'letter': 86 | canvassize = ('279.4mm','279.4mm') 87 | elif parent.svg.par.Canvassize == 'A4': 88 | canvassize = ('210mm','297mm') 89 | 90 | return canvassize 91 | 92 | def SavePolyline(self, path, pline): 93 | ''' This is a sample method. 94 | 95 | This sample method is intended to help illustrate what method docstrings should look like. 96 | 97 | Notes 98 | --------------- 99 | 'self' does not need to be included in the Args section. 100 | 101 | Args 102 | --------------- 103 | name (str): A string name, with spaces as underscores 104 | age (int): Age as full year measurements 105 | height (float): Height in meters to 2 significant digits, ex: 1.45 106 | 107 | Examples 108 | --------------- 109 | 110 | Returns 111 | --------------- 112 | formatted_profile (str) : A formatted string populated with the with the supplied information 113 | ''' 114 | Canvassize = self.Canvas_size() 115 | 116 | prims = pline.prims 117 | dwg = svgwrite.Drawing(path, profile='tiny', size=Canvassize) 118 | 119 | for item in prims: 120 | 121 | if self.UseCamera: 122 | newPoints = [self.WorldToCam(vert.point.P) for vert in item ] 123 | else: 124 | newPoints = [(vert.point.x,vert.point.y) for vert in item ] 125 | 126 | newPoly = dwg.polyline(points=newPoints, stroke='black', stroke_width=1, fill='none') 127 | dwg.add(newPoly) 128 | 129 | dwg.save() 130 | 131 | return 132 | 133 | def SavePolygon(self, path, pgon): 134 | ''' This is a sample method. 135 | 136 | This sample method is intended to help illustrate what method docstrings should look like. 137 | 138 | Notes 139 | --------------- 140 | 'self' does not need to be included in the Args section. 141 | 142 | Args 143 | --------------- 144 | name (str): A string name, with spaces as underscores 145 | age (int): Age as full year measurements 146 | height (float): Height in meters to 2 significant digits, ex: 1.45 147 | 148 | Examples 149 | --------------- 150 | 151 | Returns 152 | --------------- 153 | formatted_profile (str) : A formatted string populated with the with the supplied information 154 | ''' 155 | Canvassize = self.Canvas_size() 156 | 157 | prims = pgon.prims 158 | dwg = svgwrite.Drawing(path, profile='tiny', size=Canvassize) 159 | 160 | for item in prims: 161 | 162 | if self.UseCamera: 163 | newPoints = [self.WorldToCam(vert.point.P) for vert in item ] 164 | else: 165 | newPoints = [(vert.point.x,vert.point.y) for vert in item ] 166 | 167 | newPoly = dwg.polygon(points=newPoints, stroke='black', stroke_width=1, fill='none') 168 | dwg.add(newPoly) 169 | 170 | dwg.save() 171 | 172 | def SavePolygonAndPolygon(self, path, pline, pgon): 173 | ''' This is a sample method. 174 | 175 | This sample method is intended to help illustrate what method docstrings should look like. 176 | 177 | Notes 178 | --------------- 179 | 'self' does not need to be included in the Args section. 180 | 181 | Args 182 | --------------- 183 | name (str): A string name, with spaces as underscores 184 | age (int): Age as full year measurements 185 | height (float): Height in meters to 2 significant digits, ex: 1.45 186 | 187 | Examples 188 | --------------- 189 | 190 | Returns 191 | --------------- 192 | formatted_profile (str) : A formatted string populated with the with the supplied information 193 | ''' 194 | Canvassize = self.Canvas_size() 195 | 196 | pgonPrims = pgon.prims 197 | plinePrims = pline.prims 198 | dwg = svgwrite.Drawing(path, profile='tiny', size=Canvassize) 199 | 200 | for item in pgonPrims: 201 | 202 | if self.UseCamera: 203 | newPoints = [self.WorldToCam(vert.point.P) for vert in item ] 204 | else: 205 | newPoints = [(vert.point.x,vert.point.y) for vert in item ] 206 | 207 | newPoly = dwg.polygon(points=newPoints, stroke='black', stroke_width=1, fill='none') 208 | dwg.add(newPoly) 209 | 210 | for item in plinePrims: 211 | 212 | if self.UseCamera: 213 | newPoints = [self.WorldToCam(vert.point.P) for vert in item ] 214 | else: 215 | newPoints = [(vert.point.x,vert.point.y) for vert in item ] 216 | 217 | newPoly = dwg.polyline(points=newPoints, stroke='black', stroke_width=1, fill='none') 218 | dwg.add(newPoly) 219 | 220 | dwg.save() 221 | 222 | return 223 | 224 | def Par_check(self, svg_type): 225 | ''' Par_check() is an error handling method. 226 | 227 | Par_check aims to ensrue that all parameters are correctly set up so we can advance to the 228 | steps of creating our SVGs. This means checking to ensure that all needed fields are 229 | completed in the TOX. If we pass all of the par check tests then we can move on to 230 | writing our SVG file to disk. 231 | 232 | Notes 233 | --------------- 234 | 'self' does not need to be included in the Args section. 235 | 236 | Args 237 | --------------- 238 | svg_type (str): the string name for the inteded type of output - polygon, polyline, both 239 | 240 | Returns 241 | --------------- 242 | ready (bool) : the results of a set of logical checks to that ensures all requisite 243 | pars have been supplied for a sucessful write to disk for the file. 244 | ''' 245 | 246 | ready = False 247 | 248 | title = "We're off the RAILS!" 249 | message = '''Hey there, things don't look totally right. 250 | Check on these parameters to make sure everything is in order:\n{}''' 251 | buttons = ['okay'] 252 | checklist = [] 253 | 254 | # error handling for geometry permutations 255 | # handling polyline saving 256 | if self.Svgtype == 'pline': 257 | if self.Polylinesop != None and op(self.Polylinesop).isSOP: 258 | pass 259 | else: 260 | checklist.append( 'Missing Polygon SOP' ) 261 | 262 | # handling polygon saving 263 | elif self.Svgtype == 'pgon': 264 | if self.Polygonsop != None and op(self.Polygonsop).isSOP: 265 | pass 266 | else: 267 | checklist.append( 'Missing Polyline SOP' ) 268 | 269 | # handling combined objects - polyline and polygon saving 270 | elif self.Svgtype == 'both': 271 | polyline = self.Polylinesop != None and op(self.Polylinesop).isSOP 272 | polygon = self.Polygonsop != None and op(self.Polygonsop).isSOP 273 | 274 | # both sops are present 275 | if polyline and polygon: 276 | pass 277 | # missing polyline sop 278 | elif polygon and not polyline: 279 | checklist.append( 'Missing Polyline SOP' ) 280 | # missing polygon sop 281 | elif polyline and not polygon: 282 | checklist.append( 'Missing Polygon SOP' ) 283 | # missing both polyline and polygon sops 284 | elif not polyline and not polygon: 285 | checklist.append( 'Missing Polygon SOP') 286 | checklist.append( 'Missing Polyline SOP') 287 | 288 | # handling to check for a directory path 289 | if parent.svg.par.Dir == None or parent.svg.par.Dir.val == '': 290 | checklist.append( 'Missing Directory Path' ) 291 | 292 | else: 293 | pass 294 | 295 | # handling to check for a file path 296 | if parent.svg.par.Filename == None or parent.svg.par.Filename.val == '': 297 | checklist.append( 'Missing File name' ) 298 | 299 | # Check for camera 300 | if parent.svg.par.Usecamera: 301 | if parent.svg.par.Camera == None or op(parent.svg.par.Camera).type != "cam": 302 | checklist.append( 'Missing Camera' ) 303 | 304 | else: 305 | pass 306 | 307 | # we're in the clear, everything is ready to go 308 | if len(checklist) == 0: 309 | ready = True 310 | # correctly format message for ui.messageBox and warn user about missing elements 311 | else: 312 | ready = False 313 | messageChecklist = '\n' 314 | for item in checklist: 315 | messageChecklist += ' * {}\n'.format(item) 316 | 317 | message = message.format(messageChecklist) 318 | ui.messageBox(title, message, buttons=buttons) 319 | return ready 320 | 321 | def Save(self): 322 | ''' This is the Save method, used to start the process of writing the svg to disk. 323 | 324 | Based on settings in the tox's parameters the Save() method will utilize other 325 | helper methods to correctly save out the file. Pragmatically, this means first 326 | ensuring that all pars are correctly set up (error prevention), then the 327 | appropriate calling of other methods to ensure that geometry is correclty 328 | written to file. 329 | 330 | Notes 331 | --------------- 332 | none 333 | 334 | Args 335 | --------------- 336 | none 337 | 338 | Returns 339 | --------------- 340 | none 341 | ''' 342 | # get the svg type 343 | svgtype = self.Svgtype 344 | 345 | # start with Par_check to see if we're ready to proced. 346 | readyToContinue = self.Par_check( svgtype ) 347 | 348 | if readyToContinue: 349 | filepath = self.Filepath.format( dir=parent.svg.par.Dir, 350 | file=parent.svg.par.Filename) 351 | 352 | if svgtype == 'pline': 353 | self.SavePolyline( path=filepath, 354 | pline=op(self.Polylinesop)) 355 | 356 | elif svgtype == 'pgon': 357 | self.SavePolygon( path=filepath, 358 | pgon=op(self.Polygonsop)) 359 | 360 | elif svgtype == 'both': 361 | self.SavePolygonAndPolygon( path=filepath, 362 | pline=op(self.Polylinesop), 363 | pgon=op(self.Polygonsop)) 364 | else: 365 | print("Woah... something is very wrong") 366 | pass 367 | 368 | print(filepath) 369 | print(self.Polylinesop) 370 | print(self.Svgtype) 371 | 372 | else: 373 | pass 374 | 375 | return 376 | --------------------------------------------------------------------------------