├── .gitignore ├── LICENSE ├── README.rst ├── openscad_runner └── __init__.py ├── pyproject.toml ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | foo.scad 132 | 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Revar Desmera 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 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | OpenSCAD Runner 2 | =============== 3 | 4 | A Python library to interface with and run the OpenSCAD interpreter. 5 | 6 | ColorScheme Enum Class 7 | ---------------------- 8 | ``ColorScheme`` defines the following enums: 9 | - cornfield 10 | - metallic 11 | - sunset 12 | - starnight 13 | - beforedawn 14 | - nature 15 | - deepocean 16 | - solarized 17 | - tomorrow 18 | - tomorrow_night 19 | - monotone 20 | 21 | RenderMode Enum Class 22 | ---------------------- 23 | ``RenderMode`` defines the following enums:: 24 | - test_only 25 | - render 26 | - preview 27 | - thrown_together 28 | - wireframe 29 | 30 | OpenScadRunner Class 31 | --------------------- 32 | The ``OpenScadRunner`` class provides the following methods: 33 | 34 | - ``__init__()`` The initializer method, which has the following arguments: 35 | - scriptfile = The name of the script file to process. 36 | - outfile = The name of the file to output to. 37 | - imgsize = The size of the imagefile to output to, if outputting to a PNG or GIF. Default: (640,480) 38 | - antialias = The antialiasing scaling factor. If greater than 1.0, images are generated at a larger size, then scaled down to the target size with anti-aliasing. Default: 1.0 (no anti-aliasing) 39 | - animate = If given an integer number of frames, creates that many frames of animation, and collates them into an animated GIF or APNG. Default: None 40 | - animate_duration = Number of milliseconds per frame for an animated GIF or APNG. Default: 250 41 | - render_mode = The rendering mode to use when generating an image. See RenderMode Enum. Default: RenderMode.preview 42 | - show_axes = If True, show axes in the rendering. Default: True 43 | - show_scales = If True, show the scales along the axes. Default: True 44 | - show_edges = If True, shows the edges of all the faces. Default: False 45 | - show_crosshairs = If True, shows the crosshairs for the center of the camera translation. Default: False 46 | - camera = Gives the camera position as either [translate_x,y,z,rot_x,y,z,dist] or [eye_x,y,z,center_x,y,z] 47 | - orthographic = If True, render orthographic. If False, render with perspective. Default: False 48 | - auto_center = If True, and script does not set $vpt, $vpr, or $vpd, then centers the shape in the rendered image. Default: False 49 | - view_all = If True, and script does not set $vpd, then the field of view is scaled to show the complete rendered shape. Default: False 50 | - color_scheme = The color scheme to render an image with. See ColorScheme Enum. Default: ColorScheme.cornfield, 51 | - csg_limit = If given, the maximum number of CSG elements to render. 52 | - deps_file = If given, the file to write Makefile dependancies out to. 53 | - make_file = If given, the Makefile script to run when missing a dependency. 54 | - set_vars = An optional dictionary of script variables and values to set. 55 | - customizer_file = If given, specifies the file containing Customizer Parameters. 56 | - customizer_params = An optional dictionary of customizer parameter names and values to set. 57 | - hard_warnings = Stop at first WARNING, as if it were an ERROR. Default: False 58 | - quiet = Suppresses non-error, non-warning messages. Default: False 59 | - ``good()`` Returns True if the ``run()`` method was called, and processing completed successfully. 60 | - ``__bool__()`` Returns True if the ``run()`` method was called, and processing completed, whether or not it was successful. 61 | - ``run()`` Run the OpenSCAD app with the current settings. This sets some instance variables: 62 | - .complete = A boolean value indicating if the processing has completed yet. 63 | - .success = A boolean value indicating if the processing completed sucessfully. 64 | - .script = The script that was evaluated, as a list of line strings. 65 | - .cmdline = The commandline arguments used to launch the OpenSCAD app. 66 | - .return_code = The return code from OpenSCAD. Generally 0 if successful. 67 | - .echos = A list of ECHO: output line strings. 68 | - .warnings = A list of WARNING: output line strings. 69 | - .errors = A list of ERROR: or TRACE: output line strings. 70 | 71 | 72 | Creating an STL file:: 73 | 74 | from openscad_runner import OpenScadRunner 75 | osr = OpenScadRunner("example.scad", "example.stl") 76 | osr.run() 77 | for line in osr.echos: 78 | print(line) 79 | for line in osr.warnings: 80 | print(line) 81 | for line in osr.errors: 82 | print(line) 83 | if osr.good(): 84 | print("Successfully created example.stl") 85 | 86 | Creating a Preview PNG:: 87 | 88 | from openscad_runner import RenderMode, OpenScadRunner 89 | osr = OpenScadRunner("example.scad", "example.png", render_mode=RenderMode.preview, imgsize=(800,600), antialias=2.0) 90 | osr.run() 91 | for line in osr.echos: 92 | print(line) 93 | for line in osr.warnings: 94 | print(line) 95 | for line in osr.errors: 96 | print(line) 97 | if osr.good(): 98 | print("Successfully created example.png") 99 | 100 | Creating a Fully Rendered PNG:: 101 | 102 | from openscad_runner import RenderMode, OpenScadRunner 103 | osr = OpenScadRunner("example.scad", "example.png", render_mode=RenderMode.render, imgsize=(800,600), antialias=2.0) 104 | osr.run() 105 | for line in osr.echos: 106 | print(line) 107 | for line in osr.warnings: 108 | print(line) 109 | for line in osr.errors: 110 | print(line) 111 | if osr.good(): 112 | print("Successfully created example.png") 113 | 114 | Rendering an animated GIF:: 115 | 116 | from openscad_runner import OpenScadRunner 117 | osr = OpenScadRunner("example.scad", "example.gif", imgsize=(320,200), animate=36, animate_duration=200) 118 | osr.run() 119 | for line in osr.echos: 120 | print(line) 121 | for line in osr.warnings: 122 | print(line) 123 | for line in osr.errors: 124 | print(line) 125 | if osr.good(): 126 | print("Successfully created example.gif") 127 | 128 | Rendering an animated PNG:: 129 | 130 | from openscad_runner import OpenScadRunner 131 | osr = OpenScadRunner("example.scad", "example.png", imgsize=(320,200), animate=36, animate_duration=200) 132 | osr.run() 133 | for line in osr.echos: 134 | print(line) 135 | for line in osr.warnings: 136 | print(line) 137 | for line in osr.errors: 138 | print(line) 139 | if osr.good(): 140 | print("Successfully created example.png") 141 | 142 | 143 | -------------------------------------------------------------------------------- /openscad_runner/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import math 4 | import shutil 5 | import filecmp 6 | import os.path 7 | import platform 8 | import subprocess 9 | 10 | from enum import Enum 11 | from PIL import Image, ImageChops 12 | import pygifsicle 13 | from apng import APNG 14 | 15 | 16 | class RenderMode(Enum): 17 | """ 18 | RenderMode Enum class. 19 | - test_only 20 | - render 21 | - preview 22 | - thrown_together 23 | - wireframe 24 | """ 25 | test_only = "Test" 26 | render = "Render" 27 | preview = "Preview" 28 | thrown_together = "Thrown Together" 29 | wireframe = "Wireframe" 30 | 31 | 32 | class ColorScheme(Enum): 33 | """ 34 | ColorScheme Enum class. 35 | - cornfield 36 | - metallic 37 | - sunset 38 | - starnight 39 | - beforedawn 40 | - nature 41 | - deepocean 42 | - solarized 43 | - tomorrow 44 | - tomorrow_night 45 | - monotone 46 | """ 47 | cornfield = "Cornfield" 48 | metallic = "Metallic" 49 | sunset = "Sunset" 50 | starnight = "Starnight" 51 | beforedawn = "BeforeDawn" 52 | nature = "Nature" 53 | deepocean = "DeepOcean" 54 | solarized = "Solarized" 55 | tomorrow = "Tomorrow" 56 | tomorrow_night = "Tomorrow Night" 57 | monotone = "Monotone" 58 | 59 | 60 | class OpenScadRunner(object): 61 | def __init__( 62 | self, scriptfile, outfile, 63 | imgsize=(640,480), 64 | antialias=1.0, 65 | animate=None, 66 | animate_duration=250, 67 | render_mode=RenderMode.preview, 68 | show_axes=True, 69 | show_scales=True, 70 | show_edges=False, 71 | show_crosshairs=False, 72 | camera=None, 73 | orthographic=False, 74 | auto_center=False, 75 | view_all=False, 76 | color_scheme=ColorScheme.cornfield, 77 | csg_limit=None, 78 | deps_file=None, 79 | make_file=None, 80 | set_vars={}, 81 | customizer_file=None, 82 | customizer_params={}, 83 | hard_warnings=False, 84 | quiet=False, 85 | verbose=False 86 | ): 87 | """ 88 | Initializer method. Arguments are: 89 | - scriptfile = The name of the script file to process. 90 | - outfile = The name of the file to output to. 91 | - imgsize = The size of the imagefile to output to, if outputting to a PNG or GIF. Default: (640,480) 92 | - antialias = The antialiasing scaling factor. If greater than 1.0, images are generated at a larger size, then scaled down to the target size with anti-aliasing. Default: 1.0 (no anti-aliasing) 93 | - animate = If given an integer number of frames, creates that many frames of animation, and collates them into an animated GIF. Default: None 94 | - animate_duration = Number of milliseconds per frame for an animated GIF. Default: 250 95 | - render_mode = The rendering mode to use when generating an image. See RenderMode Enum. Default: RenderMode.preview 96 | - show_axes = If True, show axes in the rendering. Default: True 97 | - show_scales = If True, show the scales along the axes. Default: True 98 | - show_edges = If True, shows the edges of all the faces. Default: False 99 | - show_crosshairs = If True, shows the crosshairs for the center of the camera translation. Default: False 100 | - camera = Gives the camera position as either [translate_x,y,z,rot_x,y,z,dist] or [eye_x,y,z,center_x,y,z] 101 | - orthographic = If True, render orthographic. If False, render with perspective. Default: False 102 | - auto_center = If True, and script does not set $vpt, $vpr, or $vpd, then centers the shape in the rendered image. Default: False 103 | - view_all = If True, and script does not set $vpd, then the field of view is scaled to show the complete rendered shape. Default: False 104 | - color_scheme = The color scheme to render an image with. See ColorScheme Enum. Default: ColorScheme.cornfield, 105 | - csg_limit = If given, the maximum number of CSG elements to render. 106 | - deps_file = If given, the file to write Makefile dependancies out to. 107 | - make_file = If given, the Makefile script to run when missing a dependency. 108 | - set_vars = An optional dictionary of script variables and values to set. 109 | - customizer_file = If given, specifies the file containing Customizer Parameters. 110 | - customizer_params = An optional dictionary of customizer parameter names and values to set. 111 | - hard_warnings = Stop at first WARNING, as if it were an ERROR. Default: False 112 | - quiet = Suppresses non-error, non-warning messages. Default: False 113 | - verbose = Print the command-line to stdout on each execution. Default: False 114 | """ 115 | exepath = shutil.which("openscad") 116 | if exepath is not None: 117 | self.OPENSCAD = exepath 118 | elif platform.system() == "Darwin": 119 | exepath = shutil.which("/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD") 120 | if exepath is not None: 121 | self.OPENSCAD = exepath 122 | elif platform.system() == "Windows": 123 | test_paths = [ 124 | "C:\\Program Files\\openSCAD\\openscad.com", 125 | "C:\\Program Files\\openSCAD\\openscad.exe", 126 | "C:\\Program Files (x86)\\openSCAD\\openscad.com", 127 | "C:\\Program Files (x86)\\openSCAD\\openscad.exe", 128 | ] 129 | for p in test_paths: 130 | exepath = shutil.which(p) 131 | if exepath is not None: 132 | self.OPENSCAD = exepath 133 | break 134 | if not hasattr(self, "OPENSCAD"): 135 | raise Exception("Can't find OpenSCAD executable. Is OpenSCAD on your system PATH?") 136 | self.scriptfile = scriptfile 137 | self.outfile = outfile 138 | self.imgsize = imgsize 139 | self.antialias = antialias 140 | self.show_axes = show_axes 141 | self.show_edges = show_edges 142 | self.show_scales = show_scales 143 | self.show_crosshairs = show_crosshairs 144 | self.camera = camera 145 | self.color_scheme = color_scheme 146 | self.auto_center = auto_center 147 | self.view_all = view_all 148 | self.orthographic = orthographic 149 | self.animate = animate 150 | self.animate_duration = animate_duration 151 | self.render_mode = render_mode 152 | self.csg_limit = csg_limit 153 | self.deps_file = deps_file 154 | self.make_file = make_file 155 | self.set_vars = set_vars 156 | self.customizer_file = customizer_file 157 | self.customizer_params = customizer_params 158 | self.hard_warnings = hard_warnings 159 | self.quiet = quiet 160 | self.verbose = verbose 161 | 162 | self.cmdline = [] 163 | self.script = [] 164 | self.return_code = None 165 | self.stderr = [] 166 | self.stdout = [] 167 | self.echos = [] 168 | self.warnings = [] 169 | self.errors = [] 170 | self.success = False 171 | self.complete = False 172 | 173 | def __bool__(self): 174 | """ 175 | Returns True if the run() method has been called, and the processing is complete, whether or not it was successful. 176 | """ 177 | return self.complete 178 | 179 | def good(self): 180 | """ 181 | Returns True if the run() method has been called, and the result was successful. 182 | """ 183 | return self.success 184 | 185 | def run(self): 186 | """ 187 | Runs the OpenSCAD application with the current paramaters. 188 | """ 189 | outfile = self.outfile 190 | basename, fileext = os.path.splitext(outfile) 191 | fileext = fileext.lower() 192 | if self.animate is not None: 193 | assert (fileext in (".gif", ".png")), "Can only animate to a gif or png file." 194 | basename = basename.replace(".", "_") 195 | outfile = basename + ".png" 196 | if self.render_mode == RenderMode.test_only: 197 | scadcmd = [self.OPENSCAD, "-o", "foo.term"] 198 | else: 199 | scadcmd = [self.OPENSCAD, "-o", outfile] 200 | if fileext in [".png", ".gif"]: 201 | scadcmd.append("--imgsize={},{}".format(int(self.imgsize[0]*self.antialias), int(self.imgsize[1]*self.antialias))) 202 | if self.show_axes or self.show_scales or self.show_edges or self.show_crosshairs or self.render_mode==RenderMode.wireframe: 203 | showparts = [] 204 | if self.show_axes: 205 | showparts.append("axes") 206 | if self.show_scales: 207 | showparts.append("scales") 208 | if self.show_edges: 209 | showparts.append("edges") 210 | if self.show_crosshairs: 211 | showparts.append("crosshairs") 212 | if self.render_mode == RenderMode.wireframe: 213 | showparts.append("wireframe") 214 | scadcmd.append("--view=" + ",".join(showparts)) 215 | if self.camera is not None: 216 | while len(self.camera) < 6: 217 | self.camera.append(0) 218 | scadcmd.extend(["--camera", ",".join(str(x) for x in self.camera)]) 219 | if self.color_scheme != ColorScheme.cornfield: 220 | scadcmd.extend(["--colorscheme", self.color_scheme]) 221 | scadcmd.append("--projection=o" if self.orthographic else "--projection=p") 222 | if self.auto_center: 223 | scadcmd.append("--autocenter") 224 | if self.view_all: 225 | scadcmd.append("--viewall") 226 | if self.animate is not None: 227 | scadcmd.extend(["--animate", "{}".format(self.animate)]) 228 | if self.render_mode == RenderMode.render: 229 | scadcmd.extend(["--render", ""]) 230 | elif self.render_mode == RenderMode.preview: 231 | scadcmd.extend(["--preview", ""]) 232 | elif self.render_mode == RenderMode.thrown_together: 233 | scadcmd.extend(["--preview", "throwntogether"]) 234 | elif self.render_mode == RenderMode.wireframe: 235 | scadcmd.extend(["--render", ""]) 236 | if self.csg_limit is not None: 237 | scadcmd.extend(["--csglimit", self.csg_limit]) 238 | if self.deps_file != None: 239 | scadcmd.extend(["-d", self.deps_file]) 240 | if self.make_file != None: 241 | scadcmd.extend(["-m", self.make_file]) 242 | for var, val in self.set_vars.items(): 243 | scadcmd.extend(["-D", "{}={}".format(var,val)]) 244 | if self.customizer_file is not None: 245 | scadcmd.extend(["-p", self.customizer_file]) 246 | for var, val in self.customizer_params.items(): 247 | scadcmd.extend(["-P", "{}={}".format(var,val)]) 248 | if self.hard_warnings: 249 | scadcmd.append("--hardwarnings") 250 | if self.quiet: 251 | scadcmd.append("--quiet") 252 | scadcmd.append(self.scriptfile) 253 | if self.verbose: 254 | line = " ".join([ 255 | "'{}'".format(arg) if ' ' in arg or arg=='' else arg 256 | for arg in scadcmd 257 | ]) 258 | print(line) 259 | if platform.system() == "Windows": 260 | # Due to argument escaping, empty arguments will cause openscad to fail on Windows 261 | scadcmd = [c for c in scadcmd if c] 262 | p = subprocess.Popen(scadcmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 263 | (stdoutdata, stderrdata) = p.communicate(None) 264 | stdoutdata = stdoutdata.decode('utf-8') 265 | stderrdata = stderrdata.decode('utf-8') 266 | self.return_code = p.returncode 267 | self.cmdline = scadcmd 268 | self.stderr = stderrdata.split("\n") 269 | self.stdout = stdoutdata.split("\n") 270 | self.echos = [x for x in self.stderr if x.startswith("ECHO:")] 271 | self.warnings = [x for x in self.stderr if x.startswith("WARNING:")] 272 | self.errors = [x for x in self.stderr if x.startswith("ERROR:") or x.startswith("TRACE:")] 273 | if self.return_code==0 and self.errors==[] and (not self.hard_warnings or self.warnings==[]): 274 | self.success = True 275 | if self.render_mode==RenderMode.test_only and os.path.isfile("foo.term"): 276 | os.unlink("foo.term") 277 | with open(self.scriptfile, "r") as f: 278 | self.script = f.readlines(); 279 | if self.success and self.render_mode != RenderMode.test_only: 280 | if self.animate: 281 | imgfiles = ["{}{:05d}.png".format(basename,i) for i in range(self.animate)] 282 | if fileext == ".gif": 283 | imgs = [] 284 | for imgfile in imgfiles: 285 | img = Image.open(imgfile) 286 | if self.antialias != 1.0: 287 | img.thumbnail(self.imgsize, Image.Resampling.LANCZOS) 288 | imgs.append(img) 289 | imgs[0].save( 290 | self.outfile, 291 | save_all=True, 292 | append_images=imgs[1:], 293 | duration=self.animate_duration, 294 | loop=0 295 | ) 296 | pygifsicle.optimize(self.outfile, colors=64) 297 | elif fileext == ".png": 298 | if self.antialias != 1.0: 299 | for imgfile in imgfiles: 300 | img = Image.open(imgfile) 301 | img.thumbnail(self.imgsize, Image.Resampling.LANCZOS) 302 | os.unlink(imgfile) 303 | img.save(imgfile) 304 | APNG.from_files(imgfiles, delay=self.animate_duration).save(self.outfile) 305 | for imgfile in imgfiles: 306 | os.unlink(imgfile) 307 | elif float(self.antialias) != 1.0: 308 | im = Image.open(self.outfile) 309 | im.thumbnail(self.imgsize, Image.Resampling.LANCZOS) 310 | os.unlink(self.outfile) 311 | im.save(self.outfile) 312 | self.complete = True 313 | return self.success 314 | 315 | 316 | # vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 317 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=61.0", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [project] 9 | name = "openscad_runner" 10 | version = "1.1.2" 11 | authors = [ 12 | { name="Revar Desmera", email="revarbat@gmail.com" }, 13 | ] 14 | maintainers = [ 15 | { name="Revar Desmera", email="revarbat@gmail.com" }, 16 | ] 17 | description = "A Python library to interface with the OpenSCAD app." 18 | readme = "README.rst" 19 | requires-python = ">=3.7" 20 | classifiers = [ 21 | "Development Status :: 5 - Production/Stable", 22 | "Environment :: Console", 23 | "Intended Audience :: Developers", 24 | "Intended Audience :: Manufacturing", 25 | "License :: OSI Approved :: MIT License", 26 | "Operating System :: MacOS :: MacOS X", 27 | "Operating System :: Microsoft :: Windows", 28 | "Operating System :: POSIX", 29 | "Programming Language :: Python :: 3", 30 | "Topic :: Artistic Software", 31 | "Topic :: Multimedia :: Graphics :: 3D Modeling", 32 | "Topic :: Multimedia :: Graphics :: 3D Rendering", 33 | "Topic :: Software Development :: Libraries", 34 | "Topic :: Software Development :: Libraries :: Python Modules", 35 | ] 36 | keywords = ["openscad", "interface"] 37 | dependencies = [ 38 | "Pillow>=7.2.0", 39 | "pygifsicle>=1.0.2", 40 | "apng>=0.3.4" 41 | ] 42 | 43 | [project.urls] 44 | "Homepage" = "https://github.com/belfryscad/openscad_runner" 45 | "Repository" = "https://github.com/belfryscad/openscad_runner" 46 | "Bug Tracker" = "https://github.com/belfryscad/openscad_runner/issues" 47 | "Releases" = "https://github.com/belfryscad/openscad_runner/releases" 48 | "Usage" = "https://github.com/belfryscad/openscad_runner/README.rst" 49 | "Documentation" = "https://github.com/belfryscad/openscad_runner/README.rst" 50 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import setuptools 4 | 5 | if __name__ == "__main__": 6 | setuptools.setup() 7 | 8 | 9 | --------------------------------------------------------------------------------