├── pysdfscad ├── __init__.py ├── openscad.lark ├── main.py ├── openscad_builtins.py └── compiler.py ├── pysdfscad_qtgui ├── __init__.py ├── examples │ └── openscad │ │ ├── Advanced │ │ ├── tests │ │ │ └── foo.scad │ │ ├── surface_image.png │ │ ├── surface_image.scad │ │ ├── offset.scad │ │ ├── assert.scad │ │ ├── children.scad │ │ ├── GEB.scad │ │ ├── children_indexed.scad │ │ ├── module_recursion.scad │ │ └── animation.scad │ │ ├── Old │ │ ├── example016.stl │ │ ├── example004.scad │ │ ├── example014.scad │ │ ├── example012.scad │ │ ├── example011.scad │ │ ├── example019.scad │ │ ├── example010.scad │ │ ├── example013.scad │ │ ├── example003.scad │ │ ├── example001.scad │ │ ├── example002.scad │ │ ├── example018.scad │ │ ├── example005.scad │ │ ├── example015.scad │ │ ├── example016.scad │ │ ├── example024.scad │ │ ├── example023.scad │ │ ├── example021.scad │ │ ├── example008.scad │ │ ├── example009.scad │ │ ├── example022.scad │ │ ├── example007.scad │ │ ├── example006.scad │ │ ├── example020.scad │ │ ├── example017.scad │ │ └── example012.stl │ │ ├── Parametric │ │ ├── sign.json │ │ ├── sign.scad │ │ ├── candleStand.json │ │ └── candleStand.scad │ │ ├── Basics │ │ ├── CSG.scad │ │ ├── hull.scad │ │ ├── LetterBlock.scad │ │ ├── logo.scad │ │ ├── linear_extrude.scad │ │ ├── roof.scad │ │ ├── logo_and_text.scad │ │ ├── text_on_cube.scad │ │ ├── rotate_extrude.scad │ │ ├── projection.scad │ │ └── CSG-modules.scad │ │ ├── Functions │ │ ├── recursion.scad │ │ ├── functions.scad │ │ ├── echo.scad │ │ ├── list_comprehensions.scad │ │ └── polygon_areas.scad │ │ ├── examples.json │ │ └── COPYING-CC0.txt ├── logo.png ├── logWidget.py ├── main.ui └── main.py ├── docs ├── api.md ├── Screenshot_0.png ├── index.md ├── 3D_objects.md └── transformations.md ├── .gitattributes ├── tests ├── data │ └── test_geom │ │ └── sphere.stl ├── test_geometry.py └── test_interpretor.py ├── .gitmodules ├── compile.sh ├── mkdocs.yml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── pyproject.toml ├── doc_extra.py ├── flake.nix ├── flake.lock ├── .github └── workflows │ └── qtDeploy.yaml ├── .gitignore └── README.md /pysdfscad/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | ::: pysdfscad.openscad_builtins 2 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/tests/foo.scad: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/data/test_geom filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /docs/Screenshot_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traverseda/PySdfScad/HEAD/docs/Screenshot_0.png -------------------------------------------------------------------------------- /pysdfscad_qtgui/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traverseda/PySdfScad/HEAD/pysdfscad_qtgui/logo.png -------------------------------------------------------------------------------- /tests/data/test_geom/sphere.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traverseda/PySdfScad/HEAD/tests/data/test_geom/sphere.stl -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/data/openscad"] 2 | path = tests/openscad 3 | url = https://github.com/openscad/openscad.git 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Hephorge 2 | 3 | Programmers CAD with advanced features like bevels. 4 | 5 | ![Main Screenshot](Screenshot_0.png) 6 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example016.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traverseda/PySdfScad/HEAD/pysdfscad_qtgui/examples/openscad/Old/example016.stl -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/surface_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/traverseda/PySdfScad/HEAD/pysdfscad_qtgui/examples/openscad/Advanced/surface_image.png -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | QT_QPA_PLATFORM=minimal poetry run pyinstaller pysdfscad_qtgui/main.py --onefile --name=pySdfScad --collect-data pysdfscad --collect-data pysdfscad_qtgui --windowed 2 | -------------------------------------------------------------------------------- /docs/3D_objects.md: -------------------------------------------------------------------------------- 1 | 2 | ## Primitive Solids 3 | 4 | ### Cube 5 | 6 | {{scad_image(""" 7 | cube([10,10,10]); 8 | """)}} 9 | 10 | ### Cylinder 11 | 12 | {{scad_image( 13 | """ 14 | cylinder(r=5,h=10); 15 | """ 16 | )}} 17 | 18 | ### Sphere 19 | 20 | {{scad_image(""" 21 | sphere(r=5); 22 | """)}} 23 | 24 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Hephorge 2 | theme: 3 | name: readthedocs 4 | highlightjs: true 5 | hljs_languages: 6 | - python 7 | - openscad 8 | 9 | plugins: 10 | - mkdocstrings 11 | - search 12 | - macros: 13 | module_name: doc_extra 14 | 15 | nav: 16 | - 3D_objects.md 17 | - transformations.md 18 | - api.md 19 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-bookworm 2 | 3 | # Install system dependencies 4 | RUN apt-get update && apt-get install -y \ 5 | build-essential \ 6 | git \ 7 | libgl1-mesa-dev \ 8 | libglu1-mesa-dev \ 9 | freeglut3-dev \ 10 | mesa-common-dev \ 11 | xvfb \ 12 | && rm -rf /var/lib/apt/lists/* 13 | 14 | # Install Poetry 15 | RUN pip install poetry 16 | 17 | # Set up a non-root user 18 | ARG USERNAME=vscode 19 | ARG USER_UID=1000 20 | ARG USER_GID=$USER_UID 21 | RUN groupadd --gid $USER_GID $USERNAME \ 22 | && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME 23 | 24 | # Switch to the non-root user 25 | USER $USERNAME 26 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example004.scad: -------------------------------------------------------------------------------- 1 | 2 | module example004() 3 | { 4 | difference() { 5 | cube(30, center = true); 6 | sphere(20); 7 | } 8 | } 9 | 10 | echo(version=version()); 11 | 12 | example004(); 13 | 14 | // Written by Clifford Wolf and Marius 15 | // Kintel 16 | // 17 | // To the extent possible under law, the author(s) have dedicated all 18 | // copyright and related and neighboring rights to this software to the 19 | // public domain worldwide. This software is distributed without any 20 | // warranty. 21 | // 22 | // You should have received a copy of the CC0 Public Domain 23 | // Dedication along with this software. 24 | // If not, see . 25 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example014.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | intersection_for(i = [ 4 | [0, 0, 0], 5 | [10, 20, 300], 6 | [200, 40, 57], 7 | [20, 88, 57] 8 | ]) 9 | rotate(i) cube([100, 20, 20], center = true); 10 | 11 | // Written by Clifford Wolf and Marius 12 | // Kintel 13 | // 14 | // To the extent possible under law, the author(s) have dedicated all 15 | // copyright and related and neighboring rights to this software to the 16 | // public domain worldwide. This software is distributed without any 17 | // warranty. 18 | // 19 | // You should have received a copy of the CC0 Public Domain 20 | // Dedication along with this software. 21 | // If not, see . 22 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Parametric/sign.json: -------------------------------------------------------------------------------- 1 | { 2 | "parameterSets": { 3 | "Welcome sign": { 4 | "Message": "Welcome to...", 5 | "To": "Parametric Designs", 6 | "height": "2", 7 | "radius": "80", 8 | "resolution": "30" 9 | }, 10 | "Congo Sign": { 11 | "Message": "Congratulations", 12 | "To": "openSCAD", 13 | "height": "2", 14 | "radius": "67", 15 | "resolution": "20" 16 | }, 17 | "Happy birthday sign": { 18 | "Message": "Happy Birthday!", 19 | "To": "To Me", 20 | "height": "2", 21 | "radius": "67", 22 | "resolution": "10" 23 | } 24 | }, 25 | "fileFormatVersion": "1" 26 | } 27 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example012.scad: -------------------------------------------------------------------------------- 1 | // example012.stl is generated from Basics/LetterBlock.scad 2 | 3 | echo(version=version()); 4 | 5 | difference() { 6 | sphere(20); 7 | 8 | translate([ -2.92, 0.5, +20 ]) 9 | rotate([180, 0, 180]) 10 | import("example012.stl", convexity = 5); 11 | } 12 | 13 | // Written by Clifford Wolf and Marius 14 | // Kintel 15 | // 16 | // To the extent possible under law, the author(s) have dedicated all 17 | // copyright and related and neighboring rights to this software to the 18 | // public domain worldwide. This software is distributed without any 19 | // warranty. 20 | // 21 | // You should have received a copy of the CC0 Public Domain 22 | // Dedication along with this software. 23 | // If not, see . 24 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example011.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | polyhedron( 4 | points = [ 5 | [10, 0, 0], 6 | [0, 10, 0], 7 | [-10, 0, 0], 8 | [0, -10, 0], 9 | [0, 0, 10] 10 | ], 11 | triangles = [ 12 | [0, 1, 2, 3], 13 | [4, 1, 0], 14 | [4, 2, 1], 15 | [4, 3, 2], 16 | [4, 0, 3] 17 | ] 18 | ); 19 | 20 | // Written by Clifford Wolf and Marius 21 | // Kintel 22 | // 23 | // To the extent possible under law, the author(s) have dedicated all 24 | // copyright and related and neighboring rights to this software to the 25 | // public domain worldwide. This software is distributed without any 26 | // warranty. 27 | // 28 | // You should have received a copy of the CC0 Public Domain 29 | // Dedication along with this software. 30 | // If not, see . 31 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example019.scad: -------------------------------------------------------------------------------- 1 | 2 | function get_cylinder_h(p) = lookup(p, [ 3 | [ -200, 5 ], 4 | [ -50, 20 ], 5 | [ -20, 18 ], 6 | [ +80, 25 ], 7 | [ +150, 2 ] 8 | ]); 9 | 10 | echo(version=version()); 11 | for (i = [-100:5:+100]) { 12 | translate([ i, 0, -30 ]) cylinder(r1 = 6, r2 = 2, h = get_cylinder_h(i)*3); 13 | } 14 | 15 | // Written by Clifford Wolf and Marius 16 | // Kintel 17 | // 18 | // To the extent possible under law, the author(s) have dedicated all 19 | // copyright and related and neighboring rights to this software to the 20 | // public domain worldwide. This software is distributed without any 21 | // warranty. 22 | // 23 | // You should have received a copy of the CC0 Public Domain 24 | // Dedication along with this software. 25 | // If not, see . 26 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/CSG.scad: -------------------------------------------------------------------------------- 1 | // CSG.scad - Basic example of CSG usage 2 | 3 | translate([-24,0,0]) { 4 | union() { 5 | cube(15, center=true); 6 | sphere(10); 7 | } 8 | } 9 | 10 | intersection() { 11 | cube(15, center=true); 12 | sphere(10); 13 | } 14 | 15 | translate([24,0,0]) { 16 | difference() { 17 | cube(15, center=true); 18 | sphere(10); 19 | } 20 | } 21 | 22 | echo(version=version()); 23 | // Written by Marius Kintel 24 | // 25 | // To the extent possible under law, the author(s) have dedicated all 26 | // copyright and related and neighboring rights to this software to the 27 | // public domain worldwide. This software is distributed without any 28 | // warranty. 29 | // 30 | // You should have received a copy of the CC0 Public Domain 31 | // Dedication along with this software. 32 | // If not, see . 33 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example010.scad: -------------------------------------------------------------------------------- 1 | 2 | // example010.dat generated using octave: 3 | // d = (sin(1:0.2:10)' * cos(1:0.2:10)) * 10; 4 | // save("example010.dat", "d"); 5 | 6 | echo(version=version()); 7 | 8 | intersection() { 9 | surface(file = "example010.dat", center = true, convexity = 5); 10 | 11 | rotate(45, [0, 0, 1]) 12 | surface(file = "example010.dat", center = true, convexity = 5); 13 | } 14 | 15 | // Written by Clifford Wolf and Marius 16 | // Kintel 17 | // 18 | // To the extent possible under law, the author(s) have dedicated all 19 | // copyright and related and neighboring rights to this software to the 20 | // public domain worldwide. This software is distributed without any 21 | // warranty. 22 | // 23 | // You should have received a copy of the CC0 Public Domain 24 | // Dedication along with this software. 25 | // If not, see . 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pysdfscad" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [{ name = "Alex Davies", email = "traverse.da@gmail.com" }] 6 | requires-python = ">=3.10,<3.12" 7 | readme = "README.md" 8 | dependencies = [ 9 | "sdf", 10 | "lark>=1.1.5,<2", 11 | "loguru>=0.6.0,<0.7", 12 | "click>=8.1.3,<9", 13 | "appdirs>=1.4.4,<2", 14 | "astor>=0.8.1,<0.9", 15 | ] 16 | 17 | [project.scripts] 18 | pysdfscad = "pysdfscad.main:main" 19 | pysdfscad_qtgui = "pysdfscad_qtgui.main:main" 20 | 21 | [project.optional-dependencies] 22 | gui = [ 23 | "pyside6>=6.9.3", 24 | "qscintilla>=2.13.3,<3", 25 | "pyqtgraph>=0.13.1,<0.14", 26 | "pyopengl>=3.1.6,<4", 27 | ] 28 | dev = [ 29 | "pytest>=7.2.0,<8", 30 | "black>=22.12.0,<23", 31 | ] 32 | 33 | [tool.uv] 34 | package = true 35 | 36 | [tool.uv.sources] 37 | sdf = { git = "https://github.com/fogleman/sdf.git" } 38 | 39 | [tool.setuptools] 40 | packages = ["pysdfscad", "pysdfscad_qtgui"] 41 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example013.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | intersection() { 4 | linear_extrude(height = 100, center = true, convexity= 3) 5 | import(file = "example013.dxf"); 6 | rotate([0, 90, 0]) 7 | linear_extrude(height = 100, center = true, convexity= 3) 8 | import(file = "example013.dxf"); 9 | rotate([90, 0, 0]) 10 | linear_extrude(height = 100, center = true, convexity= 3) 11 | import(file = "example013.dxf"); 12 | } 13 | 14 | // Written by Clifford Wolf and Marius 15 | // Kintel 16 | // 17 | // To the extent possible under law, the author(s) have dedicated all 18 | // copyright and related and neighboring rights to this software to the 19 | // public domain worldwide. This software is distributed without any 20 | // warranty. 21 | // 22 | // You should have received a copy of the CC0 Public Domain 23 | // Dedication along with this software. 24 | // If not, see . 25 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/hull.scad: -------------------------------------------------------------------------------- 1 | // Hull Example 2 | // The hull of a toy sailboat 3 | $fs = .1; 4 | hull(){ 5 | #translate([0,30,2.5]) sphere(1); //bow 6 | #translate([0,10,-1]) cube([20,1,8],true); //1st bulkhead 7 | #translate([0,10,-5]) cube([1,1,8],true); //1st keel 8 | #translate([0,-8,-1]) cube([20,1,8],true); //2nd bulkhead 9 | #translate([0,-8,-5]) cube([1,1,8],true); //2nd keel 10 | #translate([0,-30,1]) cube([16,1,4],true); //stern 11 | } 12 | cylinder(40,1,1); //Mast 13 | 14 | 15 | // written by Paul Young, 2022 16 | // paulwhy.2@gmail.com 17 | 18 | // To the extent possible under law, the author(s) have dedicated all 19 | // copyright and related and neighboring rights to this software to the 20 | // public domain worldwide. This software is distributed without any 21 | // warranty. 22 | // 23 | // You should have received a copy of the CC0 Public Domain 24 | // Dedication along with this software. 25 | // If not, see . -------------------------------------------------------------------------------- /doc_extra.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | from pathlib import Path 3 | import hashlib 4 | 5 | #Need to run this in a subprocess because of https://github.com/pyqtgraph/pyqtgraph/issues/2618 6 | def scad_image_subprocess(path, code_block): 7 | from pysdfscad.main import OpenscadFile 8 | 9 | file = OpenscadFile() 10 | file.text=code_block 11 | image = file.as_image() 12 | image.save(str(path)) 13 | 14 | 15 | def define_env(env): 16 | 17 | @env.macro 18 | def scad_image(code_block): 19 | manager = multiprocessing.Manager() 20 | digest = hashlib.md5(code_block.encode()).hexdigest() 21 | 22 | imageRoot = Path('docs/images/auto') 23 | imageRoot.mkdir(parents=True, exist_ok=True) 24 | imagePath = imageRoot/(digest+".png") 25 | 26 | if not imagePath.exists(): 27 | p = multiprocessing.Process(target=scad_image_subprocess, args=(imagePath, code_block)) 28 | p.start() 29 | p.join() 30 | return '```openscad'+code_block+'```\n\n'+f"![Preview of code block](/images/auto/{digest}.png)\n\n" 31 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example003.scad: -------------------------------------------------------------------------------- 1 | 2 | module example003() 3 | { 4 | difference() { 5 | union() { 6 | cube([30, 30, 30], center = true); 7 | cube([40, 15, 15], center = true); 8 | cube([15, 40, 15], center = true); 9 | cube([15, 15, 40], center = true); 10 | } 11 | union() { 12 | cube([50, 10, 10], center = true); 13 | cube([10, 50, 10], center = true); 14 | cube([10, 10, 50], center = true); 15 | } 16 | } 17 | } 18 | 19 | echo(version=version()); 20 | 21 | example003(); 22 | 23 | // Written by Clifford Wolf and Marius 24 | // Kintel 25 | // 26 | // To the extent possible under law, the author(s) have dedicated all 27 | // copyright and related and neighboring rights to this software to the 28 | // public domain worldwide. This software is distributed without any 29 | // warranty. 30 | // 31 | // You should have received a copy of the CC0 Public Domain 32 | // Dedication along with this software. 33 | // If not, see . 34 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/surface_image.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | // surface() can import images, the pixel values are converted 4 | // to grayscale and converted to values between 0 and 100. 5 | // The example takes 3 cuts from the height map and displays 6 | // those as 3 stacked layers. 7 | 8 | for (a = [1, 2, 3]) 9 | color([a/6 + 0.5, 0, 0]) 10 | linear_extrude(height = 2 * a, convexity = 10) 11 | projection(cut = true) 12 | translate([0, 0, -30 * a]) 13 | surface("surface_image.png", center = true); 14 | 15 | 16 | 17 | // Written in 2015 by Torsten Paul 18 | // 19 | // To the extent possible under law, the author(s) have dedicated all 20 | // copyright and related and neighboring rights to this software to the 21 | // public domain worldwide. This software is distributed without any 22 | // warranty. 23 | // 24 | // You should have received a copy of the CC0 Public Domain 25 | // Dedication along with this software. 26 | // If not, see . 27 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pysdfscad", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "context": ".." 6 | }, 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "ms-python.python", 11 | "ms-python.vscode-pylance", 12 | "njpwerner.autodocstring", 13 | "ms-python.black-formatter" 14 | ], 15 | "settings": { 16 | "python.defaultInterpreterPath": "/home/vscode/.local/bin/poetry", 17 | "python.poetryPath": "/home/vscode/.local/bin/poetry", 18 | "python.testing.pytestEnabled": true, 19 | "editor.formatOnSave": true, 20 | "python.formatting.provider": "black" 21 | } 22 | } 23 | }, 24 | "remoteUser": "vscode", 25 | "postCreateCommand": "poetry install --with dev,qtgui --no-root", 26 | "mounts": [ 27 | "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached" 28 | ], 29 | "workspaceFolder": "/workspace" 30 | } 31 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example001.scad: -------------------------------------------------------------------------------- 1 | module example001() 2 | { 3 | function r_from_dia(d) = d / 2; 4 | 5 | module rotcy(rot, r, h) { 6 | rotate(90, rot) 7 | cylinder(r = r, h = h, center = true); 8 | } 9 | 10 | difference() { 11 | sphere(r = r_from_dia(size)); 12 | rotcy([0, 0, 0], cy_r, cy_h); 13 | rotcy([1, 0, 0], cy_r, cy_h); 14 | rotcy([0, 1, 0], cy_r, cy_h); 15 | } 16 | 17 | size = 50; 18 | hole = 25; 19 | 20 | cy_r = r_from_dia(hole); 21 | cy_h = r_from_dia(size * 2.5); 22 | } 23 | 24 | echo(version=version()); 25 | 26 | example001(); 27 | 28 | // Written by Clifford Wolf and Marius 29 | // Kintel 30 | // 31 | // To the extent possible under law, the author(s) have dedicated all 32 | // copyright and related and neighboring rights to this software to the 33 | // public domain worldwide. This software is distributed without any 34 | // warranty. 35 | // 36 | // You should have received a copy of the CC0 Public Domain 37 | // Dedication along with this software. 38 | // If not, see . 39 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example002.scad: -------------------------------------------------------------------------------- 1 | 2 | module example002() 3 | { 4 | intersection() { 5 | difference() { 6 | union() { 7 | cube([30, 30, 30], center = true); 8 | translate([0, 0, -25]) 9 | cube([15, 15, 50], center = true); 10 | } 11 | union() { 12 | cube([50, 10, 10], center = true); 13 | cube([10, 50, 10], center = true); 14 | cube([10, 10, 50], center = true); 15 | } 16 | } 17 | translate([0, 0, 5]) 18 | cylinder(h = 50, r1 = 20, r2 = 5, center = true); 19 | } 20 | } 21 | 22 | echo(version=version()); 23 | 24 | example002(); 25 | 26 | // Written by Clifford Wolf and Marius 27 | // Kintel 28 | // 29 | // To the extent possible under law, the author(s) have dedicated all 30 | // copyright and related and neighboring rights to this software to the 31 | // public domain worldwide. This software is distributed without any 32 | // warranty. 33 | // 34 | // You should have received a copy of the CC0 Public Domain 35 | // Dedication along with this software. 36 | // If not, see . 37 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example018.scad: -------------------------------------------------------------------------------- 1 | 2 | module step(len, mod) 3 | { 4 | for (i = [0:$children-1]) { 5 | translate([ len*(i - ($children-1)/2), 0, 0 ]) children((i+mod) % $children); 6 | } 7 | } 8 | 9 | echo(version=version()); 10 | 11 | for (i = [1:4]) { 12 | translate([0, -250+i*100, 0]) step(100, i) { 13 | sphere(30); 14 | cube(60, true); 15 | cylinder(r = 30, h = 50, center = true); 16 | 17 | union() { 18 | cube(45, true); 19 | rotate([45, 0, 0]) cube(50, true); 20 | rotate([0, 45, 0]) cube(50, true); 21 | rotate([0, 0, 45]) cube(50, true); 22 | } 23 | } 24 | } 25 | 26 | // Written by Clifford Wolf and Marius 27 | // Kintel 28 | // 29 | // To the extent possible under law, the author(s) have dedicated all 30 | // copyright and related and neighboring rights to this software to the 31 | // public domain worldwide. This software is distributed without any 32 | // warranty. 33 | // 34 | // You should have received a copy of the CC0 Public Domain 35 | // Dedication along with this software. 36 | // If not, see . 37 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example005.scad: -------------------------------------------------------------------------------- 1 | 2 | module example005() 3 | { 4 | translate([0, 0, -120]) { 5 | difference() { 6 | cylinder(h = 50, r = 100); 7 | translate([0, 0, 10]) cylinder(h = 50, r = 80); 8 | translate([100, 0, 35]) cube(50, center = true); 9 | } 10 | for (i = [0:5]) { 11 | echo(360*i/6, sin(360*i/6)*80, cos(360*i/6)*80); 12 | translate([sin(360*i/6)*80, cos(360*i/6)*80, 0 ]) 13 | cylinder(h = 200, r=10); 14 | } 15 | translate([0, 0, 200]) 16 | cylinder(h = 80, r1 = 120, r2 = 0); 17 | } 18 | } 19 | 20 | echo(version=version()); 21 | 22 | example005(); 23 | 24 | // Written by Clifford Wolf and Marius 25 | // Kintel 26 | // 27 | // To the extent possible under law, the author(s) have dedicated all 28 | // copyright and related and neighboring rights to this software to the 29 | // public domain worldwide. This software is distributed without any 30 | // warranty. 31 | // 32 | // You should have received a copy of the CC0 Public Domain 33 | // Dedication along with this software. 34 | // If not, see . 35 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example015.scad: -------------------------------------------------------------------------------- 1 | 2 | module shape() 3 | { 4 | difference() { 5 | translate([ -35, -35 ]) intersection() { 6 | union() { 7 | difference() { 8 | square(100, true); 9 | square(50, true); 10 | } 11 | translate([ 50, 50 ]) square(15, true); 12 | } 13 | rotate(45) translate([ 0, -15 ]) square([ 100, 30 ]); 14 | } 15 | 16 | rotate(-45) scale([ 0.7, 1.3 ]) circle(5); 17 | } 18 | 19 | import(file = "example009.dxf", layer = "body", convexity = 6, scale=2); 20 | } 21 | 22 | echo(version=version()); 23 | 24 | // linear_extrude(convexity = 10, center = true) 25 | shape(); 26 | 27 | // Written by Clifford Wolf and Marius 28 | // Kintel 29 | // 30 | // To the extent possible under law, the author(s) have dedicated all 31 | // copyright and related and neighboring rights to this software to the 32 | // public domain worldwide. This software is distributed without any 33 | // warranty. 34 | // 35 | // You should have received a copy of the CC0 Public Domain 36 | // Dedication along with this software. 37 | // If not, see . 38 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/offset.scad: -------------------------------------------------------------------------------- 1 | // offset.scad - Example for offset() usage in OpenSCAD 2 | 3 | $fn = 40; 4 | 5 | foot_height = 20; 6 | 7 | echo(version=version()); 8 | 9 | module outline(wall = 1) { 10 | difference() { 11 | offset(wall / 2) children(); 12 | offset(-wall / 2) children(); 13 | } 14 | } 15 | 16 | // offsetting with a positive value allows to create rounded corners easily 17 | linear_extrude(height = foot_height, scale = 0.5) { 18 | offset(10) { 19 | square(50, center = true); 20 | } 21 | } 22 | 23 | translate([0, 0, foot_height]) { 24 | linear_extrude(height = 20) { 25 | outline(wall = 2) circle(15); 26 | } 27 | } 28 | 29 | %cylinder(r = 14, h = 100); 30 | %translate([0, 0, 100]) sphere(r = 30); 31 | 32 | 33 | 34 | // Written in 2014 by Torsten Paul 35 | // 36 | // To the extent possible under law, the author(s) have dedicated all 37 | // copyright and related and neighboring rights to this software to the 38 | // public domain worldwide. This software is distributed without any 39 | // warranty. 40 | // 41 | // You should have received a copy of the CC0 Public Domain 42 | // Dedication along with this software. 43 | // If not, see . 44 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Functions/recursion.scad: -------------------------------------------------------------------------------- 1 | // recursionscad: Basic recursion example 2 | 3 | // Recursive functions are very powerful for calculating values. 4 | // A good number of algorithms make use of recursive definitions, 5 | // e.g the calculation of the factorial of a number. 6 | // The ternary operator " ? : " is the easiest way to define the 7 | // termination condition. 8 | // Note how the following simple implementation will never terminate 9 | // when called with a negative value. This will produce an error after 10 | // some time when OpenSCAD detects the endless recursive call. 11 | function factorial(n) = n == 0 ? 1 : factorial(n - 1) * n; 12 | 13 | color("cyan") text(str("6! = ", factorial(6)), halign = "center"); 14 | 15 | echo(version=version()); 16 | // Written in 2015 by Torsten Paul 17 | // 18 | // To the extent possible under law, the author(s) have dedicated all 19 | // copyright and related and neighboring rights to this software to the 20 | // public domain worldwide. This software is distributed without any 21 | // warranty. 22 | // 23 | // You should have received a copy of the CC0 Public Domain 24 | // Dedication along with this software. 25 | // If not, see . 26 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/assert.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | function size(x) = assert(x % 2 == 0, "Size must be an even number") x; 4 | 5 | module ring(r = 10, cnt = 3, s = 6) { 6 | assert(r >= 10, "Parameter r must be >= 10"); 7 | assert(cnt >= 3 && cnt <= 20, "Parameter cnt must be between 3 and 20 (inclusive"); 8 | for (a = [0 : cnt - 1]) { 9 | rotate(a * 360 / cnt) translate([r, 0, 0]) cube(size(s), center = true); 10 | } 11 | } 12 | 13 | // ring(5, 5, 4); // trigger assertion for parameter r 14 | 15 | // ring(10, 2, 4); // trigger assertion for parameter cnt 16 | 17 | // ring(10, 3, 5); // trigger assertion in function size() 18 | 19 | color("red") ring(10, 3, 4); 20 | color("green") ring(25, 9, 6); 21 | color("blue") ring(40, 20, 8); 22 | 23 | // Written in 2018 by Torsten Paul 24 | // 25 | // To the extent possible under law, the author(s) have dedicated all 26 | // copyright and related and neighboring rights to this software to the 27 | // public domain worldwide. This software is distributed without any 28 | // warranty. 29 | // 30 | // You should have received a copy of the CC0 Public Domain 31 | // Dedication along with this software. 32 | // If not, see . 33 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example016.scad: -------------------------------------------------------------------------------- 1 | // chopped_blocks.stl is derived from Basics/LetterBlock.scad 2 | // The exported STL was converted to binary using MeshLab 3 | 4 | module blk1() { 5 | cube([ 65, 28, 28 ], center = true); 6 | } 7 | 8 | module blk2() { 9 | difference() { 10 | translate([ 0, 0, 7.5 ]) cube([ 60, 28, 14 ], center = true); 11 | cube([ 8, 32, 32 ], center = true); 12 | } 13 | } 14 | 15 | module chop() { 16 | translate([ -18, 0, 0 ]) 17 | import(file = "example016.stl", convexity = 12); 18 | } 19 | 20 | echo(version=version()); 21 | 22 | difference() { 23 | blk1(); 24 | for (alpha = [0, 90, 180, 270]) { 25 | rotate(alpha, [ 1, 0, 0]) 26 | render(convexity = 12) 27 | difference() { 28 | blk2(); 29 | chop(); 30 | } 31 | } 32 | } 33 | 34 | // Written by Clifford Wolf and Marius 35 | // Kintel 36 | // 37 | // To the extent possible under law, the author(s) have dedicated all 38 | // copyright and related and neighboring rights to this software to the 39 | // public domain worldwide. This software is distributed without any 40 | // warranty. 41 | // 42 | // You should have received a copy of the CC0 Public Domain 43 | // Dedication along with this software. 44 | // If not, see . 45 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example024.scad: -------------------------------------------------------------------------------- 1 | // Menger Sponge 2 | 3 | // Size of edge of sponge 4 | D=100; 5 | // Fractal depth (number of iterations) 6 | n=3; 7 | 8 | echo(version=version()); 9 | 10 | module menger() { 11 | difference() { 12 | cube(D, center=true); 13 | for (v=[[0,0,0], [0,0,90], [0,90,0]]) 14 | rotate(v) menger_negative(side=D, maxside=D, level=n); 15 | } 16 | } 17 | 18 | module menger_negative(side=1, maxside=1, level=1) { 19 | l=side/3; 20 | cube([maxside*1.1, l, l], center=true); 21 | if (level > 1) { 22 | for (i=[-1:1], j=[-1:1]) 23 | if (i || j) 24 | translate([0, i*l, j*l]) 25 | menger_negative(side=l, maxside=maxside, level=level-1); 26 | } 27 | } 28 | 29 | difference() { 30 | rotate([45, atan(1/sqrt(2)), 0]) menger(); 31 | translate([0,0,-D]) cube(2*D, center=true); 32 | } 33 | 34 | // Written by Nathan Hellweg, Emmett Lalish and Marius Kintel May 13, 2013 35 | // 36 | // To the extent possible under law, the author(s) have dedicated all 37 | // copyright and related and neighboring rights to this software to the 38 | // public domain worldwide. This software is distributed without any 39 | // warranty. 40 | // 41 | // You should have received a copy of the CC0 Public Domain 42 | // Dedication along with this software. 43 | // If not, see . 44 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/LetterBlock.scad: -------------------------------------------------------------------------------- 1 | // LetterBlock.scad - Basic usage of text() and linear_extrude() 2 | 3 | // Module instantiation 4 | LetterBlock("M"); 5 | 6 | // Module definition. 7 | // size=30 defines an optional parameter with a default value. 8 | module LetterBlock(letter, size=30) { 9 | difference() { 10 | translate([0,0,size/4]) cube([size,size,size/2], center=true); 11 | translate([0,0,size/6]) { 12 | // convexity is needed for correct preview 13 | // since characters can be highly concave 14 | linear_extrude(height=size, convexity=4) 15 | text(letter, 16 | size=size*22/30, 17 | font="Bitstream Vera Sans", 18 | halign="center", 19 | valign="center"); 20 | } 21 | } 22 | } 23 | 24 | echo(version=version()); 25 | // Written by Marius Kintel 26 | // 27 | // To the extent possible under law, the author(s) have dedicated all 28 | // copyright and related and neighboring rights to this software to the 29 | // public domain worldwide. This software is distributed without any 30 | // warranty. 31 | // 32 | // You should have received a copy of the CC0 Public Domain 33 | // Dedication along with this software. 34 | // If not, see . 35 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Functions/functions.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | // Functions can be defined to simplify code using lots of 4 | // calculations. 5 | 6 | // Simple example with a single function argument (which should 7 | // be a number) and returning a number calculated based on that. 8 | function f(x) = 0.5 * x + 1; 9 | 10 | color("red") 11 | for (a = [ -100 : 5 : 100 ]) 12 | translate([a, f(a), 0]) cube(2, center = true); 13 | 14 | // Functions can call other functions and return complex values 15 | // too. In this case a 3 element vector is returned which can 16 | // be used as point in 3D space or as vector (in the mathematical 17 | // meaning) for translations and other transformations. 18 | function g(x) = [ 5 * x + 20, f(x) * f(x) - 50, 0 ]; 19 | 20 | color("green") 21 | for (a = [ -200 : 10 : 200 ]) 22 | translate(g(a / 8)) sphere(1); 23 | 24 | 25 | 26 | // Written in 2015 by Torsten Paul 27 | // 28 | // To the extent possible under law, the author(s) have dedicated all 29 | // copyright and related and neighboring rights to this software to the 30 | // public domain worldwide. This software is distributed without any 31 | // warranty. 32 | // 33 | // You should have received a copy of the CC0 Public Domain 34 | // Dedication along with this software. 35 | // If not, see . 36 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example023.scad: -------------------------------------------------------------------------------- 1 | // Example combining MCAD/fonts.scad with search() function. 2 | 3 | use 4 | 5 | echo(version=version()); 6 | 7 | thisFont=8bit_polyfont(); 8 | x_shift=thisFont[0][0]; 9 | y_shift=thisFont[0][1]; 10 | 11 | hours=["one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve"]; 12 | 13 | module clock_hour_words(word_offset=20.0,word_height=2.0) { 14 | for(i=[0:(len(hours)-1)]) { 15 | hourHandAngle=(i+1)*360/len(hours); 16 | theseIndicies=search(hours[i],thisFont[2],1,1); 17 | rotate(90-hourHandAngle) translate([word_offset,0]) 18 | for( j=[0:(len(theseIndicies)-1)] ) translate([j*x_shift,-y_shift/2]) { 19 | linear_extrude(height=word_height) polygon(points=thisFont[2][theseIndicies[j]][6][0],paths=thisFont[2][theseIndicies[j]][6][1]); 20 | } 21 | } 22 | } 23 | 24 | clock_hour_words(word_offset=16.0,word_height=5.0); 25 | 26 | // Written by Andrew Plumb 27 | // 28 | // To the extent possible under law, the author(s) have dedicated all 29 | // copyright and related and neighboring rights to this software to the 30 | // public domain worldwide. This software is distributed without any 31 | // warranty. 32 | // 33 | // You should have received a copy of the CC0 Public Domain 34 | // Dedication along with this software. 35 | // If not, see . 36 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/logo.scad: -------------------------------------------------------------------------------- 1 | // logo.scad - Basic example of module, top-level variable and $fn usage 2 | 3 | Logo(50); 4 | 5 | // The $fn parameter will influence all objects inside this module 6 | // It can, optionally, be overridden when instantiating the module 7 | module Logo(size=50, $fn=100) { 8 | // Temporary variables 9 | hole = size/2; 10 | cylinderHeight = size * 1.25; 11 | 12 | // One positive object (sphere) and three negative objects (cylinders) 13 | difference() { 14 | sphere(d=size); 15 | 16 | cylinder(d=hole, h=cylinderHeight, center=true); 17 | // The '#' operator highlights the object 18 | #rotate([90, 0, 0]) cylinder(d=hole, h=cylinderHeight, center=true); 19 | rotate([0, 90, 0]) cylinder(d=hole, h=cylinderHeight, center=true); 20 | } 21 | } 22 | 23 | echo(version=version()); 24 | // Written by Clifford Wolf and Marius 25 | // Kintel 26 | // 27 | // To the extent possible under law, the author(s) have dedicated all 28 | // copyright and related and neighboring rights to this software to the 29 | // public domain worldwide. This software is distributed without any 30 | // warranty. 31 | // 32 | // You should have received a copy of the CC0 Public Domain 33 | // Dedication along with this software. 34 | // If not, see . 35 | 36 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/linear_extrude.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | // simple 2D -> 3D extrusion of a rectangle 4 | color("red") 5 | translate([0, -30, 0]) 6 | linear_extrude(height = 20) 7 | square([20, 10], center = true); 8 | 9 | // using the scale parameter a frustum can be constructed 10 | color("green") 11 | translate([-30, 0, 0]) 12 | linear_extrude(height = 20, scale = 0.2) 13 | square([20, 10], center = true); 14 | 15 | // with twist the extruded shape will rotate around the Z axis 16 | color("cyan") 17 | translate([30, 0, 0]) 18 | linear_extrude(height = 20, twist = 90) 19 | square([20, 10], center = true); 20 | 21 | // combining both relatively complex shapes can be created 22 | color("gray") 23 | translate([0, 30, 0]) 24 | linear_extrude(height = 40, twist = -360, scale = 0, center = true, $fs=1, $fa=1) 25 | square([20, 10], center = true); 26 | 27 | // Written in 2015 by Torsten Paul 28 | // 29 | // To the extent possible under law, the author(s) have dedicated all 30 | // copyright and related and neighboring rights to this software to the 31 | // public domain worldwide. This software is distributed without any 32 | // warranty. 33 | // 34 | // You should have received a copy of the CC0 Public Domain 35 | // Dedication along with this software. 36 | // If not, see . 37 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/roof.scad: -------------------------------------------------------------------------------- 1 | // This file is a part of openscad. Everything implied is implied. 2 | // Author: Alexey Korepanov 3 | 4 | echo(version=version()); 5 | 6 | // These are examples for the `roof` function, which builds 7 | // "roofs" on top of 2d sketches. A roof can be constructed using 8 | // either straight skeleton or Voronoi diagram (see below). 9 | // 10 | // Under the hood, to construct straight skeletons we use cgal, 11 | // while for Voronoi diagrams we use boost::polygon. 12 | // 13 | // With the current implementation, computation of Voronoi diagrams 14 | // is much faster (10x - 100x) than that of straight skeletons. 15 | 16 | // some 2d sketch 17 | module sketch() { 18 | polygon(points=[[-5,-1],[-0.15,-1],[0,0],[0.15,-1],[5,-1], 19 | [5,-0.1],[4,0],[5,0.1],[5,1],[-5,1]]); 20 | } 21 | 22 | // straight skeleton roof 23 | roof(method = "straight") sketch(); 24 | 25 | // Voronoi diagram roof (the default) 26 | translate([0,-5,0]) 27 | roof(method = "voronoi") sketch(); 28 | 29 | // Voronoi diagram respects discretization parameters 30 | // $fa, $fs and $fn: 31 | translate([0,-8,0]) 32 | roof(method = "voronoi", $fn=4) sketch(); 33 | 34 | // A nice application is beveling of fonts: 35 | translate([6,0,0]) 36 | roof(method = "straight") 37 | text("straight skeleton", size = 2, halign = "left", valign = "center"); 38 | 39 | translate([6,-7,0]) 40 | roof(method = "voronoi") 41 | text("Voronoi diagram", size = 2, halign = "left", valign = "center"); 42 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example021.scad: -------------------------------------------------------------------------------- 1 | 2 | module thing() 3 | { 4 | $fa = 30; 5 | difference() { 6 | sphere(r = 25); 7 | cylinder(h = 62.5, r1 = 12.5, r2 = 6.25, center = true); 8 | rotate(90, [ 1, 0, 0 ]) cylinder(h = 62.5, 9 | r1 = 12.5, r2 = 6.25, center = true); 10 | rotate(90, [ 0, 1, 0 ]) cylinder(h = 62.5, 11 | r1 = 12.5, r2 = 6.25, center = true); 12 | } 13 | } 14 | 15 | module demo_proj() 16 | { 17 | linear_extrude(center = true, height = 0.5) projection(cut = false) thing(); 18 | % thing(); 19 | } 20 | 21 | module demo_cut() 22 | { 23 | for (i=[-20:5:+20]) { 24 | rotate(-30, [ 1, 1, 0 ]) translate([ 0, 0, -i ]) 25 | linear_extrude(center = true, height = 0.5) projection(cut = true) 26 | translate([ 0, 0, i ]) rotate(+30, [ 1, 1, 0 ]) thing(); 27 | } 28 | % thing(); 29 | } 30 | 31 | echo(version=version()); 32 | translate([ -30, 0, 0 ]) demo_proj(); 33 | translate([ +30, 0, 0 ]) demo_cut(); 34 | 35 | // Written by Clifford Wolf and Marius 36 | // Kintel 37 | // 38 | // To the extent possible under law, the author(s) have dedicated all 39 | // copyright and related and neighboring rights to this software to the 40 | // public domain worldwide. This software is distributed without any 41 | // warranty. 42 | // 43 | // You should have received a copy of the CC0 Public Domain 44 | // Dedication along with this software. 45 | // If not, see . 46 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "Basics": [ 3 | "CSG.scad", 4 | "CSG-modules.scad", 5 | "logo.scad", 6 | "LetterBlock.scad", 7 | "logo_and_text.scad", 8 | "linear_extrude.scad", 9 | "roof.scad", 10 | "rotate_extrude.scad", 11 | "text_on_cube.scad", 12 | "projection.scad", 13 | "hull.scad" 14 | ], 15 | "Functions": [ 16 | "echo.scad", 17 | "functions.scad", 18 | "list_comprehensions.scad", 19 | "recursion.scad", 20 | "polygon_areas.scad" 21 | ], 22 | "Advanced": [ 23 | "offset.scad", 24 | "surface_image.scad", 25 | "children.scad", 26 | "children_indexed.scad", 27 | "GEB.scad", 28 | "animation.scad", 29 | "module_recursion.scad", 30 | "assert.scad" 31 | ], 32 | "Parametric": [ 33 | "sign.scad", 34 | "candleStand.scad" 35 | ], 36 | "Old": [ 37 | "example001.scad", 38 | "example002.scad", 39 | "example003.scad", 40 | "example004.scad", 41 | "example005.scad", 42 | "example006.scad", 43 | "example007.scad", 44 | "example008.scad", 45 | "example009.scad", 46 | "example010.scad", 47 | "example011.scad", 48 | "example012.scad", 49 | "example013.scad", 50 | "example014.scad", 51 | "example015.scad", 52 | "example016.scad", 53 | "example017.scad", 54 | "example018.scad", 55 | "example019.scad", 56 | "example020.scad", 57 | "example021.scad", 58 | "example022.scad", 59 | "example023.scad", 60 | "example024.scad" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example008.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | difference() { 4 | intersection() { 5 | translate([ -25, -25, -25]) 6 | linear_extrude(height = 50, convexity = 3) 7 | import(file = "example008.dxf", layer = "G"); 8 | 9 | rotate(90, [1, 0, 0]) 10 | translate([ -25, -125, -25]) 11 | linear_extrude(height = 50, convexity = 3) 12 | import(file = "example008.dxf", layer = "E"); 13 | 14 | rotate(90, [0, 1, 0]) 15 | translate([ -125, -125, -25]) 16 | linear_extrude(height = 50, convexity = 3) 17 | import(file = "example008.dxf", layer = "B"); 18 | } 19 | 20 | intersection() { 21 | translate([ -125, -25, -26]) 22 | linear_extrude(height = 52, convexity = 1) 23 | import(file = "example008.dxf", layer = "X"); 24 | 25 | rotate(90, [0, 1, 0]) 26 | translate([ -125, -25, -26]) 27 | linear_extrude(height = 52, convexity = 1) 28 | import(file = "example008.dxf", layer = "X"); 29 | } 30 | } 31 | 32 | // Written by Clifford Wolf and Marius 33 | // Kintel 34 | // 35 | // To the extent possible under law, the author(s) have dedicated all 36 | // copyright and related and neighboring rights to this software to the 37 | // public domain worldwide. This software is distributed without any 38 | // warranty. 39 | // 40 | // You should have received a copy of the CC0 Public Domain 41 | // Dedication along with this software. 42 | // If not, see . 43 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Python Qt development environment with UV"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | flake-utils.lib.eachDefaultSystem (system: 11 | let 12 | pkgs = nixpkgs.legacyPackages.${system}; 13 | in 14 | { 15 | devShells.default = pkgs.mkShell { 16 | buildInputs = with pkgs; [ 17 | # Python and UV 18 | python311 19 | uv 20 | 21 | # Qt libraries 22 | #qt6.full 23 | qt6.qtbase 24 | #qt6.qttools 25 | 26 | # Python Qt bindings (available system-wide for reference) 27 | python311Packages.pyqt6 28 | python311Packages.pyside6 29 | 30 | # Development tools 31 | python311Packages.pip 32 | ]; 33 | 34 | shellHook = '' 35 | echo "Qt Python development environment with UV" 36 | echo "Python: $(python --version)" 37 | echo "UV: $(uv --version)" 38 | echo "" 39 | echo "Use 'uv init' to create a new project" 40 | echo "Use 'uv add pyqt6' or 'uv add pyside6' to add Qt bindings" 41 | echo "Use 'uv run python your_app.py' to run your application" 42 | ''; 43 | 44 | # Required for Qt applications 45 | QT_QPA_PLATFORM_PLUGIN_PATH = "${pkgs.qt6.qtbase}/lib/qt-6/plugins"; 46 | LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath [ pkgs.qt6.qtbase ]}"; 47 | }; 48 | } 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example009.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | $fs = 1; 4 | $fa = 3; 5 | 6 | bodywidth = dxf_dim(file = "example009.dxf", name = "bodywidth"); 7 | fanwidth = dxf_dim(file = "example009.dxf", name = "fanwidth"); 8 | platewidth = dxf_dim(file = "example009.dxf", name = "platewidth"); 9 | fan_side_center = dxf_cross(file = "example009.dxf", layer = "fan_side_center"); 10 | fanrot = dxf_dim(file = "example009.dxf", name = "fanrot"); 11 | 12 | % linear_extrude(height = bodywidth, center = true, convexity = 10) 13 | import(file = "example009.dxf", layer = "body"); 14 | 15 | % for (z = [+(bodywidth/2 + platewidth/2), 16 | -(bodywidth/2 + platewidth/2)]) { 17 | translate([0, 0, z]) 18 | linear_extrude(height = platewidth, center = true, convexity = 10) 19 | import(file = "example009.dxf", layer = "plate"); 20 | } 21 | 22 | intersection() { 23 | linear_extrude(height = fanwidth, center = true, convexity = 10, twist = -fanrot) 24 | import(file = "example009.dxf", layer = "fan_top"); 25 | 26 | rotate_extrude(convexity = 10) 27 | import(file = "example009.dxf", layer = "fan_side", origin = [0, -40]); 28 | } 29 | 30 | // Written by Clifford Wolf and Marius 31 | // Kintel 32 | // 33 | // To the extent possible under law, the author(s) have dedicated all 34 | // copyright and related and neighboring rights to this software to the 35 | // public domain worldwide. This software is distributed without any 36 | // warranty. 37 | // 38 | // You should have received a copy of the CC0 Public Domain 39 | // Dedication along with this software. 40 | // If not, see . 41 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1759735786, 24 | "narHash": "sha256-a0+h02lyP2KwSNrZz4wLJTu9ikujNsTWIC874Bv7IJ0=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "20c4598c84a671783f741e02bf05cbfaf4907cff", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-25.05", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /docs/transformations.md: -------------------------------------------------------------------------------- 1 | # Transformations 2 | 3 | ## From Openscad 4 | 5 | ### Movement operations 6 | 7 | #### Translate 8 | 9 | {{scad_image( 10 | """ 11 | translate([-10,-10,5])cube([10,10,10]); 12 | """ 13 | )}} 14 | 15 | #### rotate 16 | 17 | {{scad_image( 18 | """ 19 | rotate([0,0,45])cube([10,10,10]); 20 | """ 21 | )}} 22 | 23 | ### CSG operations 24 | 25 | Under Hephorge most CSG operations support the "smooth" argument. 26 | 27 | #### Difference 28 | 29 | {{scad_image( 30 | """ 31 | difference(){ 32 | cube([10,10,10]); 33 | translate([12,12,-1])cylinder(r=10,h=12); 34 | } 35 | """ 36 | )}} 37 | 38 | {{scad_image( 39 | """ 40 | difference(smooth = 1){ 41 | cube([10,10,10]); 42 | translate([12,12,-1])cylinder(r=10,h=12); 43 | } 44 | """ 45 | )}} 46 | 47 | #### Intersection 48 | 49 | {{scad_image( 50 | """ 51 | intersection(){ 52 | cube([10,10,10]); 53 | cylinder(r=10,h=12); 54 | } 55 | """ 56 | )}} 57 | 58 | {{scad_image( 59 | """ 60 | intersection(smooth=1){ 61 | cube([10,10,10]); 62 | cylinder(r=10,h=12); 63 | } 64 | """ 65 | )}} 66 | 67 | 68 | ## Hephorge extentions 69 | 70 | ### Blend 71 | 72 | {{scad_image( 73 | """ 74 | blend(ratio=0.5){ 75 | cube([10,10,10]); 76 | translate([5,5,5])sphere(r=10); 77 | } 78 | """ 79 | )}} 80 | 81 | ### Shell 82 | 83 | Very usefull for making things like pipes or moulds. 84 | 85 | {{scad_image( 86 | """ 87 | difference(){ 88 | shell(thickness=1)cube([10,10,10]); 89 | translate([12,12,-1])cylinder(r=10,h=12); 90 | } 91 | """ 92 | )}} 93 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/logo_and_text.scad: -------------------------------------------------------------------------------- 1 | // logo_and_text.scad - Example for use<> and text() 2 | 3 | use // Imports the Logo() module from logo.scad into this namespace 4 | 5 | // Set the initial viewport parameters 6 | $vpr = [90, 0, 0]; 7 | $vpt = [300, 0, 80]; 8 | $vpd = 1600; 9 | 10 | logosize = 120; 11 | 12 | translate([110, 0, 80]) { 13 | translate([0, 0, 30]) rotate([25, 25, -40]) Logo(logosize); 14 | translate([100, 0, 40]) green() t("Open", s = 42, spacing = 1.05); 15 | translate([247, 0, 40]) corn() t("SCAD" , s = 42, spacing = 0.9); 16 | translate([100, 0, 0]) black() t("The Programmers"); 17 | translate([160, 0, -30]) black() t("Solid 3D CAD Modeller"); 18 | } 19 | 20 | // Helper to create 3D text with correct font and orientation 21 | module t(t, s = 18, style = ":style=Bold", spacing = 1) { 22 | rotate([90, 0, 0]) 23 | linear_extrude(height = 1) 24 | text(t, size = s, 25 | spacing=spacing, 26 | font = str("Liberation Sans", style), 27 | $fn = 16); 28 | } 29 | 30 | // Color helpers 31 | module green() color([157/255,203/255,81/255]) children(); 32 | module corn() color([249/255,210/255,44/255]) children(); 33 | module black() color([0, 0, 0]) children(); 34 | 35 | echo(version=version()); 36 | // Written in 2014 by Torsten Paul 37 | // 38 | // To the extent possible under law, the author(s) have dedicated all 39 | // copyright and related and neighboring rights to this software to the 40 | // public domain worldwide. This software is distributed without any 41 | // warranty. 42 | // 43 | // You should have received a copy of the CC0 Public Domain 44 | // Dedication along with this software. 45 | // If not, see . 46 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/children.scad: -------------------------------------------------------------------------------- 1 | // children.scad - Usage of children() 2 | 3 | // The use of children() allows to write generic modules that 4 | // modify child modules regardless of how the child geometry 5 | // is created. 6 | 7 | color("red") 8 | make_ring_of(radius = 15, count = 6) 9 | cube(8, center = true); 10 | 11 | color("green") 12 | make_ring_of(radius = 30, count = 12) 13 | difference() { 14 | sphere(5); 15 | cylinder(r = 2, h = 12, center = true); 16 | } 17 | 18 | color("cyan") 19 | make_ring_of(radius = 50, count = 4) 20 | something(); 21 | 22 | module make_ring_of(radius, count) 23 | { 24 | for (a = [0 : count - 1]) { 25 | angle = a * 360 / count; 26 | translate(radius * [sin(angle), -cos(angle), 0]) 27 | rotate([0, 0, angle]) 28 | children(); 29 | } 30 | } 31 | 32 | module something() 33 | { 34 | cube(10, center = true); 35 | cylinder(r = 2, h = 12, $fn = 40); 36 | translate([0, 0, 12]) 37 | rotate([90, 0, 0]) 38 | linear_extrude(height = 2, center = true) 39 | text("SCAD", 8, halign = "center"); 40 | translate([0, 0, 12]) 41 | cube([22, 1.6, 0.4], center = true); 42 | } 43 | 44 | echo(version=version()); 45 | // Written in 2015 by Torsten Paul 46 | // 47 | // To the extent possible under law, the author(s) have dedicated all 48 | // copyright and related and neighboring rights to this software to the 49 | // public domain worldwide. This software is distributed without any 50 | // warranty. 51 | // 52 | // You should have received a copy of the CC0 Public Domain 53 | // Dedication along with this software. 54 | // If not, see . 55 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Functions/echo.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | // Using echo() in expression context can help with debugging 4 | // recursive functions. See console window for output of the 5 | // examples below. 6 | 7 | // Simple example just outputting the function input parameters. 8 | function f1(x, y) = echo("f1: ", x, y) 0.5 * x * x + 4 * y + 1; 9 | 10 | r1 = f1(3, 5); 11 | 12 | // To output the result, there are multiple possibilities, the 13 | // easiest is to use let() to assign the result to a variable 14 | // (y here) which is used for both echo() output and result. 15 | function f2(x) = let(y = pow(x, 3)) echo("f2: ", y) y; 16 | 17 | r2 = f2(4); 18 | 19 | // Another option is using a helper function where the argument 20 | // is evaluated first and then passed to the result() helper 21 | // where it's printed using echo() and returned as result. 22 | function result(x) = echo("f3: ", x) x; 23 | function f3(x) = result(x * x - 5); 24 | 25 | r3 = f3(5); 26 | 27 | // A more complex example is a recursive function. Combining 28 | // the two different ways of printing values before and after 29 | // evaluation it's possible to output the input value x when 30 | // descending into the recursion and the result y collected 31 | // when returning. 32 | function f4(x) = echo("f4: ", x = x) 33 | let(y = x == 1 ? 1 : x * f4(x - 1)) 34 | echo("f4: ", y = y) 35 | y; 36 | 37 | r4 = f4(5); 38 | 39 | // Written in 2018 by Torsten Paul 40 | // 41 | // To the extent possible under law, the author(s) have dedicated all 42 | // copyright and related and neighboring rights to this software to the 43 | // public domain worldwide. This software is distributed without any 44 | // warranty. 45 | // 46 | // You should have received a copy of the CC0 Public Domain 47 | // Dedication along with this software. 48 | // If not, see . 49 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example022.scad: -------------------------------------------------------------------------------- 1 | // size is a vector [w, h, d] 2 | module roundedBox(size, radius, sidesonly) 3 | { 4 | rot = [ [0,0,0], [90,0,90], [90,90,0] ]; 5 | if (sidesonly) { 6 | cube(size - [2*radius,0,0], true); 7 | cube(size - [0,2*radius,0], true); 8 | for (x = [radius-size[0]/2, -radius+size[0]/2], 9 | y = [radius-size[1]/2, -radius+size[1]/2]) { 10 | translate([x,y,0]) cylinder(r=radius, h=size[2], center=true); 11 | } 12 | } 13 | else { 14 | cube([size[0], size[1]-radius*2, size[2]-radius*2], center=true); 15 | cube([size[0]-radius*2, size[1], size[2]-radius*2], center=true); 16 | cube([size[0]-radius*2, size[1]-radius*2, size[2]], center=true); 17 | 18 | for (axis = [0:2]) { 19 | for (x = [radius-size[axis]/2, -radius+size[axis]/2], 20 | y = [radius-size[(axis+1)%3]/2, -radius+size[(axis+1)%3]/2]) { 21 | rotate(rot[axis]) 22 | translate([x,y,0]) 23 | cylinder(h=size[(axis+2)%3]-2*radius, r=radius, center=true); 24 | } 25 | } 26 | for (x = [radius-size[0]/2, -radius+size[0]/2], 27 | y = [radius-size[1]/2, -radius+size[1]/2], 28 | z = [radius-size[2]/2, -radius+size[2]/2]) { 29 | translate([x,y,z]) sphere(radius); 30 | } 31 | } 32 | } 33 | 34 | echo(version=version()); 35 | translate([-15,0,0])roundedBox([20,30,40], 5, true); 36 | translate([15,0,0]) roundedBox([20,30,40], 5, false); 37 | 38 | // Written by Clifford Wolf and Marius 39 | // Kintel 40 | // 41 | // To the extent possible under law, the author(s) have dedicated all 42 | // copyright and related and neighboring rights to this software to the 43 | // public domain worldwide. This software is distributed without any 44 | // warranty. 45 | // 46 | // You should have received a copy of the CC0 Public Domain 47 | // Dedication along with this software. 48 | // If not, see . 49 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Parametric/sign.scad: -------------------------------------------------------------------------------- 1 | // First example of parameteric model 2 | // 3 | // syntax: 4 | // //Description 5 | // variable=value; //Parameter 6 | // 7 | // This type of comment tells the name of group to which parameters below 8 | // this comment will belong 9 | // 10 | // /*[ group name ]*/ 11 | // 12 | 13 | 14 | //Below comment tells the group to which a variable will belong 15 | /*[ properties of Sign]*/ 16 | 17 | //The resolution of the curves. Higher values give smoother curves but may increase the model render time. 18 | resolution = 10; //[10, 20, 30, 50, 100] 19 | 20 | //The horizontal radius of the outer ellipse of the sign. 21 | radius = 80;//[60 : 200] 22 | 23 | //Total height of the sign 24 | height = 2;//[1 : 10] 25 | 26 | /*[ Content To be written ] */ 27 | 28 | //Message to be write 29 | Message = "Welcome to..."; //["Welcome to...", "Happy Birthday!", "Happy Anniversary", "Congratulations", "Thank You"] 30 | 31 | //Name of Person, company etc. 32 | To = "Parametric Designs"; 33 | 34 | $fn = resolution; 35 | 36 | scale([1, 0.5]) difference() { 37 | cylinder(r = radius, h = 2 * height, center = true); 38 | translate([0, 0, height]) 39 | cylinder(r = radius - 10, h = height + 1, center = true); 40 | } 41 | linear_extrude(height = height) { 42 | translate([0, --4]) text(Message, halign = "center"); 43 | translate([0, -16]) text(To, halign = "center"); 44 | } 45 | 46 | // Written by Amarjeet Singh Kapoor 47 | // 48 | // To the extent possible under law, the author(s) have dedicated all 49 | // copyright and related and neighboring rights to this software to the 50 | // public domain worldwide. This software is distributed without any 51 | // warranty. 52 | // 53 | // You should have received a copy of the CC0 Public Domain 54 | // Dedication along with this software. 55 | // If not, see . 56 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/text_on_cube.scad: -------------------------------------------------------------------------------- 1 | // text_on_cube.scad - Example for text() usage in OpenSCAD 2 | 3 | echo(version=version()); 4 | 5 | font = "Liberation Sans"; //["Liberation Sans", "Liberation Sans:style=Bold", "Liberation Sans:style=Italic", "Liberation Mono", "Liberation Serif"] 6 | 7 | cube_size = 60; 8 | letter_size = 50; 9 | letter_height = 5; 10 | 11 | o = cube_size / 2 - letter_height / 2; 12 | 13 | module letter(l) { 14 | // Use linear_extrude() to make the letters 3D objects as they 15 | // are only 2D shapes when only using text() 16 | linear_extrude(height = letter_height) { 17 | text(l, size = letter_size, font = font, halign = "center", valign = "center", $fn = 16); 18 | } 19 | } 20 | 21 | difference() { 22 | union() { 23 | color("gray") cube(cube_size, center = true); 24 | translate([0, -o, 0]) rotate([90, 0, 0]) letter("C"); 25 | translate([o, 0, 0]) rotate([90, 0, 90]) letter("U"); 26 | translate([0, o, 0]) rotate([90, 0, 180]) letter("B"); 27 | translate([-o, 0, 0]) rotate([90, 0, -90]) letter("E"); 28 | } 29 | 30 | // Put some symbols on top and bottom using symbols from the 31 | // Unicode symbols table. 32 | // (see https://en.wikipedia.org/wiki/Miscellaneous_Symbols) 33 | // 34 | // Note that depending on the font used, not all the symbols 35 | // are actually available. 36 | translate([0, 0, o]) letter("\u263A"); 37 | translate([0, 0, -o - letter_height]) letter("\u263C"); 38 | } 39 | 40 | 41 | 42 | // Written in 2014 by Torsten Paul 43 | // 44 | // To the extent possible under law, the author(s) have dedicated all 45 | // copyright and related and neighboring rights to this software to the 46 | // public domain worldwide. This software is distributed without any 47 | // warranty. 48 | // 49 | // You should have received a copy of the CC0 Public Domain 50 | // Dedication along with this software. 51 | // If not, see . 52 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Functions/list_comprehensions.scad: -------------------------------------------------------------------------------- 1 | // list_comprehensions.scad - Examples of list comprehension usage 2 | 3 | // Basic list comprehension: 4 | // Returns a 2D vertex per iteration of the for loop 5 | // Note: subsequent assignments inside the for loop is allowed 6 | module ngon(num, r) { 7 | polygon([for (i=[0:num-1], a=i*360/num) [ r*cos(a), r*sin(a) ]]); 8 | } 9 | 10 | ngon(3, 10); 11 | translate([20,0]) ngon(6, 8); 12 | translate([36,0]) ngon(10, 6); 13 | 14 | // More complex list comprehension: 15 | // Similar to ngon(), but uses an inner function to calculate 16 | // the vertices. the let() keyword allows assignment of temporary variables. 17 | module rounded_ngon(num, r, rounding = 0) { 18 | function v(a) = let (d = 360/num, v = floor((a+d/2)/d)*d) (r-rounding) * [cos(v), sin(v)]; 19 | polygon([for (a=[0:360-1]) v(a) + rounding*[cos(a),sin(a)]]); 20 | } 21 | 22 | translate([0,22]) rounded_ngon(3, 10, 5); 23 | translate([20,22]) rounded_ngon(6, 8, 4); 24 | translate([36,22]) rounded_ngon(10, 6, 3); 25 | 26 | // Gear/star generator 27 | // Uses a list comprehension taking a list of radii to generate a star shape 28 | module star(num, radii) { 29 | function r(a) = (floor(a / 10) % 2) ? 10 : 8; 30 | polygon([for (i=[0:num-1], a=i*360/num, r=radii[i%len(radii)]) [ r*cos(a), r*sin(a) ]]); 31 | } 32 | 33 | translate([0,44]) star(20, [6,10]); 34 | translate([20,44]) star(40, [6,8,8,6]); 35 | translate([36,44]) star(30, [3,4,5,6,5,4]); 36 | 37 | echo(version=version()); 38 | // Written by Marius Kintel 39 | // 40 | // To the extent possible under law, the author(s) have dedicated all 41 | // copyright and related and neighboring rights to this software to the 42 | // public domain worldwide. This software is distributed without any 43 | // warranty. 44 | // 45 | // You should have received a copy of the CC0 Public Domain 46 | // Dedication along with this software. 47 | // If not, see . 48 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Functions/polygon_areas.scad: -------------------------------------------------------------------------------- 1 | // polygon_areas.scad: Another recursion example 2 | 3 | // Draw all geometry 4 | translate([0,20]) color("Red") text("Areas:", size=8, halign="center"); 5 | translate([-44,0]) shapeWithArea(3, 10); 6 | translate([-22,0]) shapeWithArea(4, 10); 7 | translate([0,0]) shapeWithArea(6, 10); 8 | translate([22,0]) shapeWithArea(10, 10); 9 | translate([44,0]) shapeWithArea(360, 10); 10 | 11 | // One shape with corresponding text 12 | module shapeWithArea(num, r) { 13 | polygon(ngon(num, r)); 14 | translate([0,-20]) 15 | color("Cyan") 16 | text(str(round(area(ngon(num, r)))), halign="center", size=8); 17 | } 18 | 19 | // Simple list comprehension for creating N-gon vertices 20 | function ngon(num, r) = 21 | [for (i=[0:num-1], a=i*360/num) [ r*cos(a), r*sin(a) ]]; 22 | 23 | // Area of a triangle with the 3rd vertex in the origin 24 | function triarea(v0, v1) = cross(v0, v1) / 2; 25 | 26 | // Area of a polygon using the Shoelace formula 27 | function area(vertices) = 28 | let (areas = [let (num=len(vertices)) 29 | for (i=[0:num-1]) 30 | triarea(vertices[i], vertices[(i+1)%num]) 31 | ]) 32 | sum(areas); 33 | 34 | // Recursive helper function: Sums all values in a list. 35 | // In this case, sum all partial areas into the final area. 36 | function sum(values,s=0) = 37 | s == len(values) - 1 ? values[s] : values[s] + sum(values,s+1); 38 | 39 | 40 | echo(version=version()); 41 | // Written in 2015 by Marius Kintel 42 | // 43 | // To the extent possible under law, the author(s) have dedicated all 44 | // copyright and related and neighboring rights to this software to the 45 | // public domain worldwide. This software is distributed without any 46 | // warranty. 47 | // 48 | // You should have received a copy of the CC0 Public Domain 49 | // Dedication along with this software. 50 | // If not, see . 51 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Parametric/candleStand.json: -------------------------------------------------------------------------------- 1 | { 2 | "parameterSets": { 3 | "Medium Size": { 4 | "CenterCandleWidth": "6", 5 | "candleSize": "9", 6 | "centerCandle": "true", 7 | "count": "7", 8 | "heightOfRing": "4", 9 | "heightOfSupport": "3", 10 | "holeSize": "3", 11 | "length": "50", 12 | "radius": "25", 13 | "width": "5", 14 | "widthOfRing": "23", 15 | "widthOfSupport": "3" 16 | }, 17 | "small": { 18 | "CenterCandleWidth": "4", 19 | "candleSize": "6", 20 | "centerCandle": "true", 21 | "count": "7", 22 | "heightOfRing": "2", 23 | "heightOfSupport": "2", 24 | "holeSize": "2", 25 | "length": "30", 26 | "radius": "16", 27 | "width": "4", 28 | "widthOfRing": "13", 29 | "widthOfSupport": "2" 30 | }, 31 | "With Ball": { 32 | "CenterCandleWidth": "7", 33 | "candleSize": "6", 34 | "centerCandle": "false", 35 | "count": "5", 36 | "heightOfRing": "2", 37 | "heightOfSupport": "2", 38 | "holeSize": "2", 39 | "length": "30", 40 | "radius": "16", 41 | "width": "4", 42 | "widthOfRing": "13", 43 | "widthOfSupport": "2" 44 | }, 45 | "Large": { 46 | "CenterCandleWidth": "10", 47 | "candleSize": "9", 48 | "centerCandle": "false", 49 | "count": "7", 50 | "heightOfRing": "4", 51 | "heightOfSupport": "3", 52 | "holeSize": "3", 53 | "length": "70", 54 | "radius": "25", 55 | "width": "5", 56 | "widthOfRing": "23", 57 | "widthOfSupport": "3" 58 | } 59 | }, 60 | "fileFormatVersion": "1" 61 | } 62 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example007.scad: -------------------------------------------------------------------------------- 1 | 2 | module cutout() 3 | { 4 | intersection() { 5 | rotate(90, [1, 0, 0]) 6 | translate([0, 0, -50]) 7 | linear_extrude(height = 100, convexity = 1) 8 | import(file = "example007.dxf", layer = "cutout1"); 9 | 10 | rotate(90, [0, 0, 1]) 11 | rotate(90, [1, 0, 0]) 12 | translate([0, 0, -50]) 13 | linear_extrude(height = 100, convexity = 2) 14 | import(file = "example007.dxf", layer = "cutout2"); 15 | } 16 | } 17 | 18 | module clip() 19 | { 20 | difference() { 21 | rotate_extrude(convexity = 3, $fn = 0, $fa = 12, $fs = 2) { 22 | import(file = "example007.dxf", layer = "dorn"); 23 | } 24 | for (r = [0, 90]) 25 | rotate(r, [0, 0, 1]) 26 | cutout(); 27 | } 28 | } 29 | 30 | module cutview() 31 | { 32 | difference() { 33 | difference() { 34 | translate([0, 0, -10]) clip(); 35 | 36 | rotate(20, [0, 0, 1]) 37 | rotate(-20, [0, 1, 0]) 38 | translate([18, 0, 0]) 39 | cube(30, center = true); 40 | } 41 | 42 | # render(convexity = 5) intersection() { 43 | translate([0, 0, -10]) 44 | clip(); 45 | 46 | rotate(20, [0, 0, 1]) 47 | rotate(-20, [0, 1, 0]) 48 | translate([18, 0, 0]) 49 | cube(30, center = true); 50 | } 51 | } 52 | } 53 | 54 | echo(version=version()); 55 | 56 | translate([0, 0, -10]) clip(); 57 | 58 | // cutview(); 59 | 60 | // Written by Clifford Wolf and Marius 61 | // Kintel 62 | // 63 | // To the extent possible under law, the author(s) have dedicated all 64 | // copyright and related and neighboring rights to this software to the 65 | // public domain worldwide. This software is distributed without any 66 | // warranty. 67 | // 68 | // You should have received a copy of the CC0 Public Domain 69 | // Dedication along with this software. 70 | // If not, see . 71 | -------------------------------------------------------------------------------- /.github/workflows/qtDeploy.yaml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [macos-latest, ubuntu-latest, windows-latest] 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | runs-on: ${{ matrix.os }} 15 | # timeout-minutes: 360 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: '3.10' 22 | 23 | - name: install python poetry 24 | uses: abatilo/actions-poetry@v2 25 | 26 | - name: install project 27 | run: poetry install --all-extras --no-interaction --no-cache -vvv 28 | continue-on-error: true 29 | - run: QT_QPA_PLATFORM=minimal poetry run pyinstaller pysdfscad_qtgui/main.py --onefile --name=pySdfScad-${{runner.os}} --collect-data pysdfscad --collect-data pysdfscad_qtgui --windowed --noupx 30 | 31 | - run: brew install create-dmg 32 | if: runner.os == 'macOS' 33 | 34 | - run: create-dmg dist/PySdfScad-macOS.dmg dist/pySdfScad-macOS.app 35 | if: runner.os == 'macOS' 36 | 37 | - run: rm -R dist/pySdfScad-macOS.app 38 | if: runner.os == 'macOS' 39 | 40 | # Optionally verify that it works (provided that it does not need user interaction) 41 | #- run: ./dist/your-code/your-code 42 | - uses: actions/upload-artifact@v3 43 | with: 44 | name: build-${{runner.os}} 45 | path: dist/* 46 | 47 | release: 48 | runs-on: ubuntu-latest 49 | needs: [build] 50 | steps: 51 | - uses: actions/download-artifact@v3 52 | with: 53 | path: artifacts/ 54 | - name: Display structure of downloaded files 55 | run: ls -R artifacts/ 56 | - uses: "marvinpinto/action-automatic-releases@latest" 57 | with: 58 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 59 | automatic_release_tag: "latest" 60 | prerelease: true 61 | title: "Development Build" 62 | files: artifacts/ 63 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example006.scad: -------------------------------------------------------------------------------- 1 | module rounded_cube(size,r,center=false) 2 | { 3 | s = is_list(size) ? size : [size,size,size]; 4 | translate(center ? -s/2 : [0,0,0]) 5 | hull() { 6 | translate([ r, r, r]) sphere(r=r); 7 | translate([ r, r,s.z-r]) sphere(r=r); 8 | translate([ r,s.y-r, r]) sphere(r=r); 9 | translate([ r,s.y-r,s.z-r]) sphere(r=r); 10 | translate([s.x-r, r, r]) sphere(r=r); 11 | translate([s.x-r, r,s.z-r]) sphere(r=r); 12 | translate([s.x-r,s.y-r, r]) sphere(r=r); 13 | translate([s.x-r,s.y-r,s.z-r]) sphere(r=r); 14 | } 15 | } 16 | 17 | module example006() 18 | { 19 | 20 | difference() { 21 | rounded_cube(100, 10, center=true); 22 | union() { 23 | for (i = [ 24 | [ 0, 0, [ [0, 0] ] ], 25 | [ 90, 0, [ [-20, -20], [+20, +20] ] ], 26 | [ 180, 0, [ [-20, -25], [-20, 0], [-20, +25], [+20, -25], [+20, 0], [+20, +25] ] ], 27 | [ 270, 0, [ [0, 0], [-25, -25], [+25, -25], [-25, +25], [+25, +25] ] ], 28 | [ 0, 90, [ [-25, -25], [0, 0], [+25, +25] ] ], 29 | [ 0, -90, [ [-25, -25], [+25, -25], [-25, +25], [+25, +25] ] ] 30 | ]) { 31 | rotate(i[0], [0, 0, 1]) 32 | rotate(i[1], [1, 0, 0]) 33 | translate([0, -50, 0]) 34 | for (j = i[2]) { 35 | translate([j[0], 0, j[1]]) sphere(10); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | echo(version=version()); 43 | 44 | example006(); 45 | 46 | // Written by Clifford Wolf and Marius 47 | // Kintel 48 | // 49 | // To the extent possible under law, the author(s) have dedicated all 50 | // copyright and related and neighboring rights to this software to the 51 | // public domain worldwide. This software is distributed without any 52 | // warranty. 53 | // 54 | // You should have received a copy of the CC0 Public Domain 55 | // Dedication along with this software. 56 | // If not, see . 57 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/GEB.scad: -------------------------------------------------------------------------------- 1 | font = "Liberation Sans"; 2 | // Nicer, but not generally installed: 3 | // font = "Bank Gothic"; 4 | 5 | module G() offset(0.3) text("G", size=10, halign="center", valign="center", font = font); 6 | module E() offset(0.3) text("E", size=10, halign="center", valign="center", font = font); 7 | module B() offset(0.5) text("B", size=10, halign="center", valign="center", font = font); 8 | 9 | $fn=64; 10 | 11 | module GEB() { 12 | intersection() { 13 | linear_extrude(height = 20, convexity = 3, center=true) B(); 14 | 15 | rotate([90, 0, 0]) 16 | linear_extrude(height = 20, convexity = 3, center=true) E(); 17 | 18 | rotate([90, 0, 90]) 19 | linear_extrude(height = 20, convexity = 3, center=true) G(); 20 | } 21 | } 22 | 23 | color("Ivory") GEB(); 24 | 25 | color("MediumOrchid") 26 | translate([0,0,-20]) 27 | linear_extrude(1) 28 | difference() { 29 | square(40, center=true); 30 | projection() GEB(); 31 | } 32 | 33 | color("DarkMagenta") 34 | rotate([90,0,0]) 35 | translate([0,0,-20]) 36 | linear_extrude(1) 37 | difference() { 38 | translate([0,0.5]) square([40,39], center=true); 39 | projection() rotate([-90,0,0]) GEB(); 40 | } 41 | 42 | color("MediumSlateBlue") 43 | rotate([90,0,90]) 44 | translate([0,0,-20]) 45 | linear_extrude(1) 46 | difference() { 47 | translate([-0.5,0.5]) square([39,39], center=true); 48 | projection() rotate([0,-90,-90]) GEB(); 49 | } 50 | 51 | echo(version=version()); 52 | // Written in 2015 by Marius Kintel 53 | // 54 | // To the extent possible under law, the author(s) have dedicated all 55 | // copyright and related and neighboring rights to this software to the 56 | // public domain worldwide. This software is distributed without any 57 | // warranty. 58 | // 59 | // You should have received a copy of the CC0 Public Domain 60 | // Dedication along with this software. 61 | // If not, see . 62 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/rotate_extrude.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | // rotate_extrude() rotates a 2D shape around the Z axis. 4 | // Note that the 2D shape must be either completely on the 5 | // positive or negative side of the X axis. 6 | color("red") 7 | rotate_extrude() 8 | translate([10, 0]) 9 | square(5); 10 | 11 | // rotate_extrude() uses the global $fn/$fa/$fs settings, but 12 | // it's possible to give a different value as parameter. 13 | color("cyan") 14 | translate([40, 0, 0]) 15 | rotate_extrude($fn = 80) 16 | text(" J"); 17 | 18 | // Using a shape that touches the X axis is allowed and produces 19 | // 3D objects that don't have a hole in the center. 20 | color("green") 21 | translate([0, 30, 0]) 22 | rotate_extrude($fn = 80) 23 | polygon( points=[[0,0],[8,4],[4,8],[4,12],[12,16],[0,20]] ); 24 | 25 | 26 | // By default rotate_extrude forms a full 360 degree circle, 27 | // but a partial rotation can be performed by using the angle parameter. 28 | // Positive angles create an arc starting from the X axis, going counter-clockwise. 29 | // Negative angles generate an arc in the clockwise direction. 30 | color("magenta") 31 | translate([40,40]){ 32 | rotate_extrude(angle=180) 33 | translate([12.5,0]) 34 | square(5); 35 | translate([7.5,0]) 36 | rotate_extrude(angle=180) 37 | translate([5,0]) 38 | square(5); 39 | translate([-7.5,0]) 40 | rotate_extrude(angle=-180) 41 | translate([5,0]) 42 | square(5); 43 | } 44 | 45 | 46 | // Written in 2015 by Torsten Paul 47 | // 48 | // To the extent possible under law, the author(s) have dedicated all 49 | // copyright and related and neighboring rights to this software to the 50 | // public domain worldwide. This software is distributed without any 51 | // warranty. 52 | // 53 | // You should have received a copy of the CC0 Public Domain 54 | // Dedication along with this software. 55 | // If not, see . 56 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/children_indexed.scad: -------------------------------------------------------------------------------- 1 | // children_indexed.scad - Usage of indexed children() 2 | 3 | // children() with a parameter allows access to a specific child 4 | // object with children(0) being the first one. In addition the 5 | // $children variable is automatically set to the number of child 6 | // objects. 7 | 8 | color("red") 9 | translate([-100, -20, 0]) 10 | align_in_grid_and_add_text(); 11 | 12 | color("yellow") 13 | translate([-50, -20, 0]) 14 | align_in_grid_and_add_text() { 15 | cube(5, center = true); 16 | } 17 | 18 | color("cyan") 19 | translate([0, -20, 0]) 20 | align_in_grid_and_add_text() { 21 | cube(5, center = true); 22 | sphere(4); 23 | } 24 | 25 | color("green") 26 | translate([50, -20, 0]) 27 | align_in_grid_and_add_text() { 28 | cube(5, center = true); 29 | sphere(4); 30 | cylinder(r = 4, h = 5); 31 | } 32 | 33 | 34 | module align_in_grid_and_add_text() 35 | { 36 | if ($children == 0) { 37 | linear_extrude(height = 1, center = true) 38 | text("Nothing...", 6, halign = "center"); 39 | } else { 40 | t = $children == 1 ? "one object" : str($children, " objects "); 41 | linear_extrude(height = 1, center = true) 42 | text(t, 6, halign = "center"); 43 | 44 | for (y = [0 : $children - 1]) 45 | for (x = [0 : $children - 1]) 46 | translate([15 * (x - ($children - 1) / 2), 20 * y + 40, 0]) 47 | scale(1 + x / $children) 48 | children(y); 49 | } 50 | } 51 | 52 | echo(version=version()); 53 | // Written in 2015 by Torsten Paul 54 | // 55 | // To the extent possible under law, the author(s) have dedicated all 56 | // copyright and related and neighboring rights to this software to the 57 | // public domain worldwide. This software is distributed without any 58 | // warranty. 59 | // 60 | // You should have received a copy of the CC0 Public Domain 61 | // Dedication along with this software. 62 | // If not, see . 63 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/projection.scad: -------------------------------------------------------------------------------- 1 | echo(version=version()); 2 | 3 | %import("projection.stl"); 4 | 5 | // projection() without the cut = true parameter will project 6 | // the outline of the object onto the X/Y plane. The result is 7 | // a 2D shape. 8 | 9 | color("red") 10 | translate([0, 0, -20]) 11 | linear_extrude(height = 2, center = true) 12 | difference() { 13 | square(30, center = true); 14 | projection() 15 | import("projection.stl"); 16 | } 17 | 18 | color("green") 19 | rotate([0, 90, 0]) 20 | translate([0, 0, -20]) 21 | linear_extrude(height = 2, center = true) 22 | difference() { 23 | square(30, center = true); 24 | projection() 25 | rotate([0, 90, 0]) 26 | import("projection.stl"); 27 | } 28 | 29 | color("cyan") 30 | rotate([-90, 0, 0]) 31 | translate([0, 0, 20]) 32 | linear_extrude(height = 2, center = true) 33 | difference() { 34 | square(30, center = true); 35 | projection() 36 | rotate([90, 0, 0]) 37 | import("projection.stl"); 38 | } 39 | 40 | // Including the cut = true uses the outline of the cut at 41 | // the X/Y plane.at Z = 0. This can make internal features 42 | // of the model visible. 43 | 44 | color("yellow", 0.5) 45 | translate([0, 0, 20]) 46 | linear_extrude(height = 2, center = true) 47 | difference() { 48 | square(30, center = true); 49 | projection(cut = true) 50 | import("projection.stl"); 51 | } 52 | 53 | 54 | 55 | // Written in 2015 by Torsten Paul 56 | // 57 | // To the extent possible under law, the author(s) have dedicated all 58 | // copyright and related and neighboring rights to this software to the 59 | // public domain worldwide. This software is distributed without any 60 | // warranty. 61 | // 62 | // You should have received a copy of the CC0 Public Domain 63 | // Dedication along with this software. 64 | // If not, see . 65 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/module_recursion.scad: -------------------------------------------------------------------------------- 1 | // Recursive calls of modules can generate complex geometry, especially 2 | // fractal style objects. 3 | // The example uses a recursive module to generate a random tree as 4 | // described in http://natureofcode.com/book/chapter-8-fractals/ 5 | 6 | // number of levels for the recursion 7 | levels = 10; // [1:1:14] 8 | // length of the first segment 9 | len = 100; // [10:10:200] 10 | // thickness of the first segment 11 | thickness = 5; //[1:1:20] 12 | 13 | // the identity matrix 14 | identity = [ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ]; 15 | 16 | // random generator, to generate always the same output for the example, 17 | // this uses a seed for rands() and stores the array of random values in 18 | // the random variable. To generate different output, remove the seed or 19 | // replace the function rnd() to just call rands(s, e, 1)[0]. 20 | rcnt = 1000; 21 | random = rands(0, 1, rcnt, 18); 22 | function rnd(s, e, r) = random[r % rcnt] * (e - s) + s; 23 | 24 | // generate 4x4 translation matrix 25 | function mt(x, y) = [ [ 1, 0, 0, x ], [ 0, 1, 0, y ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ]; 26 | 27 | // generate 4x4 rotation matrix around Z axis 28 | function mr(a) = [ [ cos(a), -sin(a), 0, 0 ], [ sin(a), cos(a), 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ]; 29 | 30 | module tree(length, thickness, count, m = identity, r = 1) { 31 | color([0, 1 - (0.8 / levels * count), 0]) 32 | multmatrix(m) 33 | square([thickness, length]); 34 | 35 | if (count > 0) { 36 | tree(rnd(0.6, 0.8, r) * length, 0.8 * thickness, count - 1, m * mt(0, length) * mr(rnd(20, 35, r + 1)), 8 * r); 37 | tree(rnd(0.6, 0.8, r + 1) * length, 0.8 * thickness, count - 1, m * mt(0, length) * mr(-rnd(20, 35, r + 3)), 8 * r + 4); 38 | } 39 | } 40 | 41 | tree(len, thickness, levels); 42 | 43 | echo(version=version()); 44 | // Written in 2015 by Torsten Paul 45 | // 46 | // To the extent possible under law, the author(s) have dedicated all 47 | // copyright and related and neighboring rights to this software to the 48 | // public domain worldwide. This software is distributed without any 49 | // warranty. 50 | // 51 | // You should have received a copy of the CC0 Public Domain 52 | // Dedication along with this software. 53 | // If not, see . 54 | -------------------------------------------------------------------------------- /pysdfscad/openscad.lark: -------------------------------------------------------------------------------- 1 | start: value* 2 | 3 | ?value: "$"? NAME "=" sum ";" -> assign_var 4 | | ifelse 5 | | function_def 6 | | module_def 7 | | COMMENT 8 | | forloop 9 | | operator 10 | 11 | ?sum: product 12 | | sum "+" product -> add 13 | | sum "-" product -> sub 14 | 15 | ?product: atom 16 | | sum "?" sum ":" sum -> conditional_op 17 | | product "*" atom -> mul 18 | | product "/" atom -> div 19 | | product "%" atom -> mod 20 | | product "^" atom -> exp 21 | | product "|" atom -> or_op 22 | | product "&&" atom -> and_op 23 | | product "!=" atom -> inequality 24 | | product "==" atom -> equality 25 | | product "<" atom -> lt_op 26 | | product ">" atom -> gt_op 27 | 28 | ?atom: NUMBER -> number 29 | | "-" atom -> neg 30 | | "$"? NAME -> var 31 | | "(" sum ")" 32 | | function 33 | | range 34 | | vector 35 | | vector_index 36 | 37 | vector_index: sum "[" sum "]" 38 | forloop: "for " "(" combined_args ")" block -> for_loop 39 | operator: NAME "(" combined_args ")" block -> operator_call 40 | block: ifelse 41 | | operator 42 | | "{" value* "}" ";"? 43 | | ";" 44 | 45 | ifelse: "if" "(" sum ")" block ("else" block )? 46 | 47 | function: NAME "(" combined_args ")" -> function_call 48 | function_def: "function" NAME "(" args_definition ")" "=" sum ";"-> function_def 49 | 50 | // Defines an operator 51 | module_def: "module" NAME "(" args_definition ")" block -> module_def 52 | 53 | range: "[" sum ":" sum ":" sum "]" 54 | | "[" sum ":" sum "]" 55 | 56 | combined_args: args ("," kwargs)* 57 | | kwargs? 58 | 59 | //Like combined args but args are names 60 | args_definition: arg_def_name* ("," kwargs)* 61 | | kwargs? 62 | arg_def_name:(name ("," name)*) 63 | 64 | args: _argvalue ("," _argvalue)* 65 | kwargs: kwargvalue ("," kwargvalue)* 66 | kwargvalue: "$"? NAME "=" _argvalue 67 | _argvalue: ESCAPED_STRING | sum | function | vector | range 68 | 69 | vector: "[" args? "]" -> vector 70 | 71 | 72 | COMMENT: C_COMMENT | CPP_COMMENT 73 | name: NAME 74 | _name: NAME 75 | 76 | %import common.CNAME -> NAME 77 | %import common.C_COMMENT 78 | %import common.CPP_COMMENT 79 | %import common.NUMBER 80 | %import common.ESCAPED_STRING 81 | %import common.WS_INLINE 82 | %import common.WS 83 | %import common.NEWLINE 84 | 85 | %ignore WS 86 | %ignore NEWLINE 87 | -------------------------------------------------------------------------------- /tests/test_geometry.py: -------------------------------------------------------------------------------- 1 | """Test cases based on 3D models stored in data/test_geom. 2 | 3 | When adding a new test case you must manually invoke this file 4 | to get it to generate new geometry. 5 | 6 | `poetry run python tests/test_geometry.py` or the like. 7 | 8 | The intended way to use this is to manually verify the results 9 | every time you add a new test case, make sure the meshes look 10 | like what you're expecting. There's no good way to verify that 11 | a translate *actually* translates anything. 12 | 13 | This is a bit brittle, but currently we don't really have a 14 | good solution. If fogleman's SDF library gets the ability to 15 | import geometry and check geometry volume there are some fun 16 | hacks we can do that would let us compare the output directly 17 | to openscad generated meshes. 18 | 19 | """ 20 | 21 | import inspect, sys, os 22 | import warnings 23 | from test_interpretor import eval_scad 24 | import tempfile 25 | from pathlib import Path 26 | import hashlib 27 | 28 | dir_path = os.path.dirname(os.path.realpath(__file__)) 29 | 30 | def geometry_testcase(func): 31 | def wrapped(*args,test=True,**kwargs): 32 | out = func(*args,**kwargs)[0] 33 | if test==True: 34 | tmp = tempfile.NamedTemporaryFile(suffix=".stl") 35 | out.save(tmp.name) 36 | out_hash = file_digest(tmp) 37 | #Hash the file 38 | outpath = Path(dir_path)/"data"/"test_geom"/(func.__name__.removeprefix("test_")+".stl") 39 | assert outpath.exists() 40 | file_hash = file_digest(outpath.open("rb")) 41 | assert out_hash == file_hash 42 | return 43 | return out 44 | return wrapped 45 | 46 | @geometry_testcase 47 | def test_sphere(): 48 | geom = eval_scad("sphere(r=20);") 49 | return geom 50 | 51 | def file_digest(file): 52 | file_hash = hashlib.md5() 53 | while chunk := file.read(8192): 54 | file_hash.update(chunk) 55 | return file_hash.digest() 56 | 57 | def main(): 58 | """Generate models for test cases... 59 | """ 60 | fset = ( out for out in inspect.getmembers(sys.modules[__name__]) if inspect.isfunction(out[1])) 61 | fset = {name:obj for name,obj in fset if name.startswith("test_")} 62 | for name, func in fset.items(): 63 | outgeom = func(test=False) 64 | outpath = Path(dir_path)/"data"/"test_geom"/(name.removeprefix("test_")+".stl") 65 | orig_hash = file_digest(outpath.open("rb")) 66 | outgeom.save(str(outpath)) 67 | new_hash = file_digest(outpath.open("rb")) 68 | if new_hash != orig_hash: 69 | warnings.warn("New file at `{outpath} has different hash, old:{old_hash} new:{new_hash}`") 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Basics/CSG-modules.scad: -------------------------------------------------------------------------------- 1 | // CSG-modules.scad - Basic usage of modules, if, color, $fs/$fa 2 | 3 | // Change this to false to remove the helper geometry 4 | debug = true; 5 | 6 | // Global resolution 7 | $fs = 0.1; // Don't generate smaller facets than 0.1 mm 8 | $fa = 5; // Don't generate larger angles than 5 degrees 9 | 10 | // Main geometry 11 | difference() { 12 | intersection() { 13 | body(); 14 | intersector(); 15 | } 16 | holes(); 17 | } 18 | 19 | // Helpers 20 | if (debug) helpers(); 21 | 22 | // Core geometric primitives. 23 | // These can be modified to create variations of the final object 24 | 25 | module body() { 26 | color("Blue") sphere(10); 27 | } 28 | 29 | module intersector() { 30 | color("Red") cube(15, center=true); 31 | } 32 | 33 | module holeObject() { 34 | color("Lime") cylinder(h=20, r=5, center=true); 35 | } 36 | 37 | // Various modules for visualizing intermediate components 38 | 39 | module intersected() { 40 | intersection() { 41 | body(); 42 | intersector(); 43 | } 44 | } 45 | 46 | module holeA() rotate([0,90,0]) holeObject(); 47 | module holeB() rotate([90,0,0]) holeObject(); 48 | module holeC() holeObject(); 49 | 50 | module holes() { 51 | union() { 52 | holeA(); 53 | holeB(); 54 | holeC(); 55 | } 56 | } 57 | 58 | module helpers() { 59 | // Inner module since it's only needed inside helpers 60 | module line() color("Black") cylinder(r=1, h=10, center=true); 61 | 62 | scale(0.5) { 63 | translate([-30,0,-40]) { 64 | intersected(); 65 | translate([-15,0,-35]) body(); 66 | translate([15,0,-35]) intersector(); 67 | translate([-7.5,0,-17.5]) rotate([0,30,0]) line(); 68 | translate([7.5,0,-17.5]) rotate([0,-30,0]) line(); 69 | } 70 | translate([30,0,-40]) { 71 | holes(); 72 | translate([-10,0,-35]) holeA(); 73 | translate([10,0,-35]) holeB(); 74 | translate([30,0,-35]) holeC(); 75 | translate([5,0,-17.5]) rotate([0,-20,0]) line(); 76 | translate([-5,0,-17.5]) rotate([0,30,0]) line(); 77 | translate([15,0,-17.5]) rotate([0,-45,0]) line(); 78 | } 79 | translate([-20,0,-22.5]) rotate([0,45,0]) line(); 80 | translate([20,0,-22.5]) rotate([0,-45,0]) line(); 81 | } 82 | } 83 | 84 | echo(version=version()); 85 | // Written by Marius Kintel 86 | // 87 | // To the extent possible under law, the author(s) have dedicated all 88 | // copyright and related and neighboring rights to this software to the 89 | // public domain worldwide. This software is distributed without any 90 | // warranty. 91 | // 92 | // You should have received a copy of the CC0 Public Domain 93 | // Dedication along with this software. 94 | // If not, see . 95 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example020.scad: -------------------------------------------------------------------------------- 1 | 2 | module screw(type = 2, r1 = 15, r2 = 20, n = 7, h = 100, t = 8) 3 | { 4 | linear_extrude(height = h, twist = 360*t/n, convexity = t) 5 | difference() { 6 | circle(r2); 7 | for (i = [0:n-1]) { 8 | if (type == 1) rotate(i*360/n) polygon([ 9 | [ 2*r2, 0 ], 10 | [ r2, 0 ], 11 | [ r1*cos(180/n), r1*sin(180/n) ], 12 | [ r2*cos(360/n), r2*sin(360/n) ], 13 | [ 2*r2*cos(360/n), 2*r2*sin(360/n) ], 14 | ]); 15 | if (type == 2) rotate(i*360/n) polygon([ 16 | [ 2*r2, 0 ], 17 | [ r2, 0 ], 18 | [ r1*cos(90/n), r1*sin(90/n) ], 19 | [ r1*cos(180/n), r1*sin(180/n) ], 20 | [ r2*cos(270/n), r2*sin(270/n) ], 21 | [ 2*r2*cos(270/n), 2*r2*sin(270/n) ], 22 | ]); 23 | } 24 | } 25 | } 26 | 27 | module nut(type = 2, r1 = 16, r2 = 21, r3 = 30, s = 6, n = 7, h = 100/5, t = 8/5) 28 | { 29 | difference() { 30 | cylinder($fn = s, r = r3, h = h); 31 | translate([ 0, 0, -h/2 ]) screw(type, r1, r2, n, h*2, t*2); 32 | } 33 | } 34 | 35 | module spring(r1 = 100, r2 = 10, h = 100, hr = 12) 36 | { 37 | stepsize = 1/16; 38 | module segment(i1, i2) { 39 | alpha1 = i1 * 360*r2/hr; 40 | alpha2 = i2 * 360*r2/hr; 41 | len1 = sin(acos(i1*2-1))*r2; 42 | len2 = sin(acos(i2*2-1))*r2; 43 | if (len1 < 0.01) { 44 | polygon([ 45 | [ cos(alpha1)*r1, sin(alpha1)*r1 ], 46 | [ cos(alpha2)*(r1-len2), sin(alpha2)*(r1-len2) ], 47 | [ cos(alpha2)*(r1+len2), sin(alpha2)*(r1+len2) ] 48 | ]); 49 | } 50 | if (len2 < 0.01) { 51 | polygon([ 52 | [ cos(alpha1)*(r1+len1), sin(alpha1)*(r1+len1) ], 53 | [ cos(alpha1)*(r1-len1), sin(alpha1)*(r1-len1) ], 54 | [ cos(alpha2)*r1, sin(alpha2)*r1 ], 55 | ]); 56 | } 57 | if (len1 >= 0.01 && len2 >= 0.01) { 58 | polygon([ 59 | [ cos(alpha1)*(r1+len1), sin(alpha1)*(r1+len1) ], 60 | [ cos(alpha1)*(r1-len1), sin(alpha1)*(r1-len1) ], 61 | [ cos(alpha2)*(r1-len2), sin(alpha2)*(r1-len2) ], 62 | [ cos(alpha2)*(r1+len2), sin(alpha2)*(r1+len2) ] 63 | ]); 64 | } 65 | } 66 | linear_extrude(height = 100, twist = 180*h/hr, 67 | $fn = (hr/r2)/stepsize, convexity = 5) { 68 | for (i = [ stepsize : stepsize : 1+stepsize/2 ]) 69 | segment(i-stepsize, min(i, 1)); 70 | } 71 | } 72 | 73 | echo(version=version()); 74 | translate([ -30, 0, 0 ]) screw(); 75 | 76 | translate([ 30, 0, 0 ]) nut(); 77 | 78 | spring(); 79 | 80 | // Written by Clifford Wolf and Marius 81 | // Kintel 82 | // 83 | // To the extent possible under law, the author(s) have dedicated all 84 | // copyright and related and neighboring rights to this software to the 85 | // public domain worldwide. This software is distributed without any 86 | // warranty. 87 | // 88 | // You should have received a copy of the CC0 Public Domain 89 | // Dedication along with this software. 90 | // If not, see . 91 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/logWidget.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from PySide6.QtCore import QObject, Slot as pyqtSlot, Signal as pyqtSignal 3 | from PySide6.QtWidgets import QTextEdit 4 | from PySide6.QtGui import QTextCursor 5 | import shlex 6 | import re 7 | import html 8 | 9 | class QTextEditLogger(QTextEdit,logging.Handler): 10 | 11 | _update_signal=pyqtSignal() 12 | 13 | def __init__(self,*args,**kwargs): 14 | super().__init__(*args,**kwargs) 15 | self.setFontFamily("monospace") 16 | self.setReadOnly(True) 17 | self._text=[] 18 | self.update_in_place=False 19 | self._update_signal.connect(lambda:self.update()) 20 | self.update() 21 | 22 | def emit(self, record): 23 | msg = self.format(record) 24 | #Check to see if there's a carriege return in the output, if there is we simply 25 | # overwrite the last line. 26 | # Won't work for more advanced carriege return hackery, but if you just want to make a simple 27 | # progress bar it's good enough. 28 | if "\r" in msg: 29 | if not self.update_in_place: 30 | self.update_in_place=True 31 | self._text.append("") 32 | self._text[-1]=msg.rstrip() 33 | else: 34 | self.update_in_place=False 35 | self._text.append(msg) 36 | self._update_signal.emit() 37 | 38 | @pyqtSlot() 39 | def update(self): 40 | #ToDO, ideally we wouldn't re-render the entire thing every time 41 | # poor performance for the scroll bars 42 | text = self._text 43 | text = (html.escape(i) for i in text) 44 | text = (replace_ansi(i) for i in text) 45 | #text = (repr(i.encode("utf-8")) for i in text) 46 | text = (f"
{i}
" for i in text) 47 | self.setHtml("
"+"\n".join(text)+"
") 48 | #Scroll widget to bottom 49 | self.verticalScrollBar().setValue( 50 | self.verticalScrollBar().maximum()) 51 | 52 | 53 | ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]') 54 | 55 | def replace_ansi(text): 56 | text = text.replace("\x1b[0m","") 57 | text = text.replace("\x1b[1m",'') 58 | text = text.replace("\x1b[3m",'') 59 | text = text.replace("\x1b[8m",'') 60 | text = text.replace("\x1b[4m",'') 61 | text = text.replace("\x1b[30m",'') 62 | text = text.replace("\x1b[31m",'') 63 | text = text.replace("\x1b[31m",'') 64 | text = text.replace("\x1b[32m",'') 65 | text = text.replace("\x1b[33m",'') 66 | text = text.replace("\x1b[34m",'') 67 | text = text.replace("\x1b[35m",'') 68 | text = text.replace("\x1b[36m",'')#I can't deal with cyan on a white background 69 | text = text.replace("\x1b[37m",'') 70 | return text 71 | return ansi_escape.sub('', text) 72 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Parametric/candleStand.scad: -------------------------------------------------------------------------------- 1 | /*[ Candle Stand ]*/ 2 | //Length of candle stand 3 | length=50; // [70:large,50:medium,30:small] 4 | 5 | // Center stand 6 | cylinder(length,width-2); 7 | 8 | //Radius of ring of stand 9 | radius=25; 10 | 11 | /* [ Number of candle holders ]*/ 12 | // Number of candle holders 13 | count=7; //[3:14] 14 | 15 | //Do you want center Candle 16 | centerCandle=true; 17 | 18 | /* [ Candle Holder ]*/ 19 | //Length of candle holder 20 | candleSize=7; 21 | 22 | //Width of candle holder 23 | width=4; 24 | 25 | //Size of hole for candle holder 26 | holeSize=3; 27 | 28 | CenterCandleWidth=4; 29 | 30 | /*[Properties of support]*/ 31 | 32 | heightOfSupport=3; 33 | widthOfSupport=3; 34 | 35 | /*[Properties of Ring]*/ 36 | 37 | heightOfRing=4; 38 | 39 | widthOfRing=23; 40 | 41 | 42 | //Create center candle 43 | translate([0,0,length-candleSize/2]) 44 | if(centerCandle){ 45 | difference(){ 46 | $fn=360; 47 | cylinder(candleSize,r=CenterCandleWidth); 48 | cylinder(candleSize+1,r=CenterCandleWidth-2); 49 | } 50 | }else{ 51 | sphere(CenterCandleWidth); 52 | } 53 | 54 | //make ring 55 | translate([0,0,length-candleSize/2]){ 56 | make(radius, count,candleSize,length); 57 | //make bottom cover for candle holders 58 | make_ring_of(radius, count){ 59 | cylinder(1,r=width); 60 | } 61 | } 62 | 63 | 64 | //Base of candle stand 65 | for (a = [0 : count - 1]) { 66 | rotate(a*360/count) { 67 | translate([0, -width/2, 0]) 68 | cube([radius, widthOfSupport, heightOfSupport]); 69 | } 70 | } 71 | 72 | //make ring with candle holders 73 | module make(radius, count,candleSize,length){ 74 | 75 | $fa = 0.5; 76 | $fs = 0.5; 77 | difference(){ 78 | union(){ 79 | //making holders 80 | make_ring_of(radius, count){ 81 | cylinder(candleSize,r=width); 82 | } 83 | 84 | //Attaching holders to stand 85 | for (a = [0 : count - 1]) { 86 | rotate(a*360/count) { 87 | translate([0, -width/2, 0]) 88 | cube([radius, widthOfSupport, heightOfSupport]); 89 | } 90 | } 91 | 92 | // make ring 93 | linear_extrude(heightOfRing, convexity=2) 94 | difference(){ 95 | circle(radius); 96 | circle(widthOfRing); 97 | } 98 | } 99 | //Making holes in candle holder 100 | make_ring_of(radius, count){ 101 | cylinder(candleSize+1,r=holeSize); 102 | } 103 | } 104 | } 105 | 106 | 107 | module make_ring_of(radius, count){ 108 | for (a = [0 : count - 1]) { 109 | angle = a * 360 / count; 110 | translate(radius * [cos(angle), -sin(angle), 0]) 111 | children(); 112 | } 113 | } 114 | 115 | // Written by Amarjeet Singh Kapoor 116 | // 117 | // To the extent possible under law, the author(s) have dedicated all 118 | // copyright and related and neighboring rights to this software to the 119 | // public domain worldwide. This software is distributed without any 120 | // warranty. 121 | // 122 | // You should have received a copy of the CC0 Public Domain 123 | // Dedication along with this software. 124 | // If not, see . 125 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Advanced/animation.scad: -------------------------------------------------------------------------------- 1 | // animation.scad - Demo of animation usage 2 | 3 | // The animation functionality is based simply on a variable $t 4 | // that is changed automatically by OpenSCAD while repeatedly 5 | // showing the model. 6 | // To activate animation, select "View->Animate" from the 7 | // menu; this will cause three fields to appear 8 | // underneath the Preview console: Time, FPS & Steps. 9 | // To commence animation, enter values into the FPS and Steps input 10 | // fields (e.g. 5 FPS and 200 Steps for this animation). 11 | // This is not intended to directly produce real-time animations 12 | // but the image sequence can be exported to generate videos of 13 | // the animation. 14 | 15 | // Length of the 2 arm segments, change to see the effects on 16 | // the arm movements. 17 | 18 | //length of the red arm 19 | arm1_length = 70; 20 | //length of the green arm 21 | arm2_length = 50; 22 | 23 | r = 2; 24 | $fn = 30; 25 | 26 | plate(); 27 | pos = position($t); 28 | arm(pos[0], pos[1], arm1_length, arm2_length); 29 | 30 | // Function describing the X/Y position that should be traced 31 | // by the arm over time. 32 | // The $t variable will be used as parameter for this function 33 | // so the range for t is [0..1]. 34 | function position(t) = t < 0.5 35 | ? [ 200 * t - 50, 30 * sin(5 * 360 * t) + 60 ] 36 | : [ 50 * cos(360 * (t - 0.5)), 100 * -sin(360 * (t- 0.5)) + 60 ]; 37 | 38 | // Inverse kinematics functions for a scara style arm 39 | // See http://forums.reprap.org/read.php?185,283327 40 | function sq(x, y) = x * x + y * y; 41 | function angB(x, y, l1, l2) = 180 - acos((l2 * l2 + l1 * l1 - sq(x, y)) / (2 * l1 * l2)); 42 | function ang2(x, y, l1, l2) = 90 - acos((l2 * l2 - l1 * l1 + sq(x, y)) / (2 * l2 * sqrt(sq(x, y)))) - atan2(x, y); 43 | function ang1(x, y, l1, l2) = ang2(x, y, l1, l2) + angB(x, y, l1, l2); 44 | 45 | // Draw an arm segment with the given color and length. 46 | module segment(col, l) { 47 | color(col) { 48 | hull() { 49 | sphere(r); 50 | translate([l, 0, 0]) sphere(r); 51 | } 52 | } 53 | } 54 | 55 | // Draw the whole 2 segmented arm trying to reach position x/y. 56 | // Parameters l1 and l2 are the length of the two arm segments. 57 | module arm(x, y, l1, l2) { 58 | a1 = ang1(x, y, l1, l2); 59 | a2 = ang2(x, y, l1, l2); 60 | sphere(r = 2 * r); 61 | cylinder(r = 2, h = 6 * r, center = true); 62 | rotate([0, 0, a1]) segment("red", l1); 63 | translate(l1 * [cos(a1), sin(a1), 0]) { 64 | sphere(r = 2 * r); 65 | rotate([0, 0, a2]) segment("green", l2); 66 | } 67 | translate([x, y, -r/2]) 68 | cylinder(r1 = 0, r2 = r, h = 4 * r, center = true); 69 | } 70 | 71 | module curve() polygon([for (a = [ 0 : 0.004 : 1]) position(a)]); 72 | 73 | // Draws the plate and the traced function using small black cubes. 74 | module plate() { 75 | %translate([0, 0, -3*r]) { 76 | translate([0,25,0]) cube([150, 150, 0.1], center = true); 77 | color("Black") linear_extrude(0.1) difference() { 78 | curve(); 79 | offset(-1) curve(); 80 | } 81 | } 82 | } 83 | 84 | echo(version=version()); 85 | // Written in 2015 by Torsten Paul 86 | // 87 | // To the extent possible under law, the author(s) have dedicated all 88 | // copyright and related and neighboring rights to this software to the 89 | // public domain worldwide. This software is distributed without any 90 | // warranty. 91 | // 92 | // You should have received a copy of the CC0 Public Domain 93 | // Dedication along with this software. 94 | // If not, see . 95 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | PySdfScad 15 | 16 | 17 | 18 | 19 | 20 | 21 | Qt::Horizontal 22 | 23 | 24 | 25 | -1 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Qt::Vertical 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 0 48 | 800 49 | 30 50 | 51 | 52 | 53 | 54 | &File 55 | 56 | 57 | 58 | Examples 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | &Design 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | &Open... 82 | 83 | 84 | 85 | 86 | &Save 87 | 88 | 89 | 90 | 91 | S&ave As 92 | 93 | 94 | 95 | 96 | &Export Mesh 97 | 98 | 99 | 100 | 101 | E&xit 102 | 103 | 104 | 105 | 106 | &Render 107 | 108 | 109 | F5 110 | 111 | 112 | 113 | 114 | foo 115 | 116 | 117 | 118 | 119 | &New 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.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 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 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 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | .aider* 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **ALPHA SOFTWARE, NOT READY FOR USE** 2 | 3 | This is currently made available for developers, don't expect this to be 4 | usable for a while. Core language primitives work for the most part, but 5 | things are missing and the stuff that does work doesn't work exactly like it 6 | does in openscad. 7 | 8 | An openscad interpretor (compiler?) written in python and using signed-distance functions. 9 | 10 | Compiles openscad code to a python abstract-syntax-tree (which can then be converted into 11 | python text or run on as-is) 12 | 13 | We use [fogleman's SDF library](https://github.com/fogleman/sdf) which makes exentsive use 14 | of numpy. 15 | 16 | # Instalation 17 | 18 | This is early alpha software, so there aren't any nice installers and it may not even work 19 | outside of linux (or at all). With that in mind... 20 | 21 | Ensure that [pipx](https://pypa.github.io/pipx/) is installed and working, then run 22 | 23 | pipx install "pysdfscad[qtgui] @ git+https://github.com/traverseda/PySdfScad.git" 24 | 25 | You can also try downloading a pre-built binary from [here](https://github.com/traverseda/PySdfScad/releases). 26 | These aren't currently tested. 27 | 28 | ## Compiled versions 29 | 30 | working on making compiled versions available, you can compile this yourself using the 31 | command in `./compile.sh`. 32 | 33 | At the moment this build is not great, and it's definitly prefered to install this project 34 | as a python package if you are able to. 35 | 36 | # Faq 37 | 38 | ## Differences from openscad 39 | 40 | While we aim to be fully compatible with openscad there are some difference. If 41 | you don't see that difference mentioned here, well that's probably a bug 42 | that needs fixing. 43 | 44 | Right now we are **NOT FEATURE COMPLETE WITH OPENSCAD**. A number of openscad 45 | feature remain unimplemented. 46 | 47 | * OpenScad meshes will be simpler and have smaller filesizes 48 | 49 | PySdfScad constructes meshes by sampling a signed distance field, this essentially means that 50 | the entire object has the same amount of triangles for a given surface area, whether that surface 51 | is a large flat plain or a complicated curve. 52 | 53 | This is something that can be improved upon in the future (look into collinear mesh simplification), 54 | but it's likely PySdfScad meshes will always be a bit "messier". 55 | 56 | * There are a lot more options for modifying a mesh 57 | 58 | This is really the reason this project exists, our [underlying library](https://github.com/fogleman/sdf#miscellaneous) 59 | supports a number of more complicated ways of modifying geometry. and we expose 60 | that as new openscad operators. For example you can use the `shell(thickness=0.2){...}` 61 | operator to make objects hollow (amazing for things like pipes), or use 62 | `smooth_union` to join two objects with a smooth fillet. 63 | 64 | * You can't color a mesh, or make parts of it transparent 65 | 66 | This is another thing that can probably be fixed eventually, but is still quite challenging. 67 | 68 | ## Development 69 | 70 | * Wouldn't an interpreter be better than a compiler? 71 | 72 | Probably, yeah. The openscad to AST got a bit out of hand. Now that it does exist though 73 | there are some pretty significant advantages. We can do proper tracebacks and other cool 74 | exception handling. We can also, furthur down the line, do things like import/introspect/modify 75 | an openscad file from another language (like python, or a theoretical future openscad-ish 76 | language). 77 | 78 | More complicated than it probably needed to be, but I'm hopeful I can do some cool 79 | stuff with it in the future. The original (unfinished) interpreter code was also a handful, 80 | and I don't think this is all that much more unreadable. The output is generally more easily 81 | debugged, as we have a nicer intermediate state (python code) than we did when using an 82 | interpreter. 83 | 84 | 85 | ## It's still alpha software 86 | 87 | I don't want to give a false impression that it works from this carefully staged screenshot, 88 | it mostly doesn't. 89 | 90 | ![](docs/Screenshot_0.png) 91 | 92 | ## Development 93 | 94 | I use dev containers and nixos. 95 | 96 | `nix-shell -p xorg.xhost --run "xhost +local:docker"` -------------------------------------------------------------------------------- /tests/test_interpretor.py: -------------------------------------------------------------------------------- 1 | from pysdfscad.main import OpenscadFile,colorize_ansi 2 | import logging 3 | import pytest 4 | from _pytest.logging import caplog as _caplog 5 | from loguru import logger 6 | 7 | @pytest.fixture 8 | def caplog(_caplog): 9 | """Fix caplog to work with loguru 10 | """ 11 | class PropogateHandler(logging.Handler): 12 | def emit(self, record): 13 | logging.getLogger(record.name).handle(record) 14 | 15 | handler_id = logger.add(PropogateHandler(), format="{message}") 16 | yield _caplog 17 | logger.remove(handler_id) 18 | 19 | def eval_scad(data): 20 | interpreter = OpenscadFile() 21 | interpreter.text = data 22 | print(data) 23 | print(colorize_ansi(interpreter.as_python())) 24 | out = interpreter.run() 25 | return out 26 | 27 | def test_echo(caplog): 28 | eval_scad(""" 29 | echo("Hello World"); 30 | """) 31 | print(*caplog.text) 32 | assert "ECHO: 'Hello World'" in caplog.text 33 | 34 | def test_basic_types(caplog): 35 | eval_scad(""" 36 | echo( 37 | vector=[1.0,2, 3.0], 38 | number=1, 39 | undef=undef, 40 | true=true, 41 | false=false, 42 | string="Hello" 43 | ); 44 | """) 45 | expected = 'ECHO: '\ 46 | 'vector=[1.0, 2, 3.0], '\ 47 | 'number=1, '\ 48 | 'undef=None, '\ 49 | 'true=True, '\ 50 | 'false=False, '\ 51 | "string='Hello'"\ 52 | "\n" 53 | 54 | assert expected in caplog.text 55 | 56 | def test_basic_math(caplog): 57 | eval_scad(""" 58 | echo( 59 | add=1+1, 60 | sub=1-2, 61 | mod=7%2, 62 | exp=10^2, 63 | div=10/2, 64 | mul=1*4 65 | ); 66 | """) 67 | expected = 'ECHO: '\ 68 | 'add=2, '\ 69 | 'sub=-1, '\ 70 | 'mod=1, '\ 71 | 'exp=100, '\ 72 | 'div=5.0, '\ 73 | 'mul=4'\ 74 | "\n" 75 | assert expected in caplog.text 76 | 77 | def test_set_variable(caplog): 78 | eval_scad(""" 79 | foo= 1+1; 80 | echo(foo); 81 | """) 82 | assert "ECHO: 2" in caplog.text 83 | 84 | def test_def_function(caplog): 85 | eval_scad(""" 86 | function func1(r)=r; 87 | echo(func1(2)); 88 | """) 89 | assert 'ECHO: 2' in caplog.text 90 | 91 | def test_vector_index(caplog): 92 | eval_scad(""" 93 | foo=[0,1,2][0]; 94 | echo(foo); 95 | """) 96 | assert 'ECHO: 0\n' in caplog.text 97 | 98 | 99 | def test_range(caplog): 100 | eval_scad(""" 101 | echo([0:2:20]); 102 | """) 103 | assert 'ECHO: [0.0:2.0:20.0]\n' in caplog.text 104 | 105 | def test_conditional_op(caplog): 106 | eval_scad(""" 107 | echo(false ? 0 : 1); 108 | """) 109 | assert 'ECHO: 1\n' in caplog.text 110 | 111 | def test_if_op(caplog): 112 | eval_scad(""" 113 | if (true) echo(true); 114 | if (false) echo(true); else echo(false); 115 | """) 116 | lines = caplog.text.split("\n") 117 | assert 'ECHO: True' in lines[0] 118 | assert 'ECHO: False' in lines[1] 119 | 120 | def test_for(caplog): 121 | out = eval_scad("for (x=[1.0,2.0,3.0],y=[1.0,2.0,3.0])echo(x,y);") 122 | expected=["ECHO: 1.0, 1.0", 123 | "ECHO: 1.0, 2.0", 124 | "ECHO: 1.0, 3.0", 125 | "ECHO: 2.0, 1.0", 126 | "ECHO: 2.0, 2.0", 127 | "ECHO: 2.0, 3.0", 128 | "ECHO: 3.0, 1.0", 129 | "ECHO: 3.0, 2.0", 130 | "ECHO: 3.0, 3.0"] 131 | for expected, line in zip(expected, caplog.text.split("\n")): 132 | assert expected in line 133 | 134 | def test_def_function_nested(caplog): 135 | eval_scad(""" 136 | function func1(r)=r; 137 | echo(1+func1(1)); 138 | """) 139 | assert 'ECHO: 2' in caplog.text 140 | 141 | def test_nested_child_modules(caplog): 142 | eval_scad(""" 143 | module nested()children(); 144 | nested()nested()nested()echo("nested"); 145 | """) 146 | assert "ECHO: 'nested'" in caplog.text 147 | 148 | def test_scope(caplog): 149 | """Test to make sure blocks are properly 150 | maintaining scope 151 | """ 152 | eval_scad(""" 153 | foo=3; 154 | union(){ 155 | foo=14; 156 | echo(foo); 157 | } 158 | echo(foo); 159 | """) 160 | loglines = caplog.text.split("\n") 161 | assert "ECHO: 14" in loglines[0] 162 | assert "ECHO: 3" in loglines[1] 163 | 164 | -------------------------------------------------------------------------------- /pysdfscad/main.py: -------------------------------------------------------------------------------- 1 | from lark import Lark 2 | from loguru import logger 3 | import pathlib, sys 4 | from pathlib import Path 5 | from pysdfscad.compiler import OpenscadToPy 6 | import numpy as np 7 | import astor 8 | 9 | #We can get general language definitions here: https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#Chapter_1_--_General 10 | # I try to stick to the same terminology as the book, but it's really not a 11 | # direct 1 to 1 translation. For example I can't really have objects that aren't fundamentally function calls, 12 | # which is why I'd have to go out of my way to keep you from storing an object in a variable. 13 | 14 | from pygments import highlight 15 | from pygments.lexers import PythonLexer 16 | from pygments.formatters import TerminalTrueColorFormatter 17 | 18 | import pysdfscad.openscad_builtins 19 | 20 | def colorize_ansi(source): 21 | from pygments import highlight 22 | import pygments.lexers.python #Required or nuitka won't find it 23 | import pygments.formatters.terminal256 24 | from pygments.lexers import PythonLexer 25 | from pygments.formatters import TerminalTrueColorFormatter 26 | return highlight(source, PythonLexer(), TerminalTrueColorFormatter()) 27 | 28 | def colorize_html(source): 29 | from pygments import highlight 30 | from pygments.lexers import PythonLexer 31 | import pygments.formatters.html 32 | from pygments.formatters import HtmlFormatter 33 | return highlight(source, PythonLexer(), HtmlFormatter()) 34 | 35 | class OpenscadFile(): 36 | def __init__(self,file=None): 37 | self.text="" 38 | self.file=file 39 | self.compiled=None 40 | self.reload() 41 | 42 | def reload(self): 43 | if self.file: 44 | self.text=self.file.read_text() 45 | else: 46 | self.text="" 47 | return self 48 | 49 | def save(self): 50 | self.file.write_text(self.text) 51 | return self 52 | 53 | def ast(self): 54 | parser = Lark.open("openscad.lark", rel_to=__file__, propagate_positions=True) 55 | tree = parser.parse(self.text) 56 | return OpenscadToPy().transform(tree) 57 | 58 | def run(self): 59 | """Compile and run the file, returning a 60 | generator with top level SDF objects. 61 | """ 62 | scad_locals = {} 63 | exec( 64 | compile( 65 | self.ast(), 66 | filename=str(self.file), 67 | mode="exec", 68 | ), 69 | scad_locals, 70 | ) 71 | return list(scad_locals['main']()) 72 | 73 | def as_image(self): 74 | from PyQt5 import QtWidgets, QtCore, QtGui, QtOpenGL 75 | import pyqtgraph.opengl as gl 76 | import pyqtgraph as pg 77 | 78 | app = QtWidgets.QApplication([]) 79 | 80 | #Horrible hack to reset the shared opengl context 81 | 82 | viewport = gl.GLViewWidget() 83 | viewport.setCameraPosition(distance=40) 84 | 85 | result = list(self.run())[0] 86 | 87 | points = result.generate() 88 | points, cells = np.unique(points, axis=0, return_inverse=True) 89 | cells = cells.reshape((-1, 3)) 90 | 91 | meshdata = gl.MeshData(vertexes=points, faces=cells) 92 | mesh = gl.GLMeshItem(meshdata=meshdata, 93 | smooth=False, drawFaces=True, 94 | drawEdges=False, 95 | shader="normalColor", 96 | color = (1,1,1,1), edgeColor=(0.2, 0.5, 0.2, 1) 97 | ) 98 | 99 | g = gl.GLGridItem() 100 | g.setSize(200, 200) 101 | g.setSpacing(10, 10) 102 | 103 | a=gl.GLAxisItem() 104 | a.setSize(10,10,10) 105 | 106 | viewport.addItem(a) 107 | viewport.addItem(g) 108 | viewport.addItem(mesh) 109 | viewport.show() 110 | 111 | imageData = viewport.renderToArray((1000, 1000)) 112 | image = pg.makeQImage(imageData).transformed(QtGui.QTransform().rotate(90)) 113 | 114 | return image 115 | 116 | def as_ast(self): 117 | return astor.dump_tree(self.ast()) 118 | def as_python(self): 119 | return astor.to_source(self.ast(), add_line_information=True) 120 | 121 | 122 | 123 | openscad_parser = Lark((pathlib.Path(__file__).parent/"openscad.lark").read_text(), propagate_positions=True) 124 | 125 | def main(): 126 | f = Path(sys.argv[1]) 127 | interpreter = OpenscadFile(f) 128 | print(colorize_ansi(interpreter.as_ast())) 129 | print(colorize_ansi(interpreter.as_python())) 130 | result = list(interpreter.run()) 131 | if not result: 132 | logger.info("No top level geometry to render") 133 | else: 134 | result[0].save('test.stl') 135 | 136 | if __name__ == '__main__': 137 | main() 138 | 139 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example017.scad: -------------------------------------------------------------------------------- 1 | 2 | // To render the DXF file from the command line: 3 | // openscad -o example017.dxf -D'mode="parts"' example017.scad 4 | 5 | //Mode can be either "parts", "exploded" or "assembled". 6 | mode = "assembled"; // ["parts", "exploded", "assembled"] 7 | 8 | thickness = 6; 9 | locklen1 = 15; 10 | locklen2 = 10; 11 | boltlen = 15; 12 | midhole = 10; 13 | inner1_to_inner2 = 50; 14 | total_height = 80; 15 | 16 | module shape_tripod() 17 | { 18 | x1 = 0; 19 | x2 = x1 + thickness; 20 | x3 = x2 + locklen1; 21 | x4 = x3 + thickness; 22 | x5 = x4 + inner1_to_inner2; 23 | x6 = x5 - thickness; 24 | x7 = x6 - locklen2; 25 | x8 = x7 - thickness; 26 | x9 = x8 - thickness; 27 | x10 = x9 - thickness; 28 | 29 | y1 = 0; 30 | y2 = y1 + thickness; 31 | y3 = y2 + thickness; 32 | y4 = y3 + thickness; 33 | y5 = y3 + total_height - 3*thickness; 34 | y6 = y5 + thickness; 35 | 36 | union() { 37 | difference() { 38 | polygon([ 39 | [ x1, y2 ], [ x2, y2 ], 40 | [ x2, y1 ], [ x3, y1 ], [ x3, y2 ], 41 | [ x4, y2 ], [ x4, y1 ], [ x5, y1 ], 42 | [ x5 + thickness, y3 ], [ x5, y4 ], 43 | [ x5, y5 ], 44 | [ x6, y5 ], [ x6, y6 ], [ x7, y6 ], [ x7, y5 ], [ x8, y5 ], 45 | [ x8, y6 ], [ x9, y5 ], 46 | [ x9, y4 ], [ x10, y3 ], 47 | [ x2, y3 ] 48 | ]); 49 | translate([ x10, y4 ]) circle(thickness); 50 | translate([ x5 + thickness, y4 ]) circle(thickness); 51 | } 52 | 53 | translate([ x5, y1 ]) square([ boltlen - thickness, thickness*2 ]); 54 | 55 | translate([ x5 + boltlen - thickness, y2 ]) circle(thickness); 56 | 57 | translate([ x2, y2 ]) intersection() { 58 | circle(thickness); 59 | translate([ -thickness*2, 0 ]) square(thickness*2); 60 | } 61 | 62 | translate([ x8, y5 ]) intersection() { 63 | circle(thickness); 64 | translate([ -thickness*2, 0 ]) square(thickness*2); 65 | } 66 | } 67 | } 68 | 69 | module shape_inner_disc() 70 | { 71 | difference() { 72 | circle(midhole + boltlen + 2*thickness + locklen2); 73 | for (alpha = [ 0, 120, 240 ]) { 74 | rotate(alpha) translate([ 0, midhole + boltlen + thickness + locklen2/2 ]) square([ thickness, locklen2 ], true); 75 | } 76 | circle(midhole + boltlen); 77 | } 78 | } 79 | 80 | module shape_outer_disc() 81 | { 82 | difference() { 83 | circle(midhole + boltlen + inner1_to_inner2 + 2*thickness + locklen1); 84 | for (alpha = [ 0, 120, 240 ]) { 85 | rotate(alpha) translate([ 0, midhole + boltlen + inner1_to_inner2 + thickness + locklen1/2 ]) square([ thickness, locklen1 ], true); 86 | } 87 | circle(midhole + boltlen + inner1_to_inner2); 88 | } 89 | } 90 | 91 | module parts() 92 | { 93 | tripod_x_off = locklen1 - locklen2 + inner1_to_inner2; 94 | tripod_y_off = max(midhole + boltlen + inner1_to_inner2 + 4*thickness + locklen1, total_height); 95 | 96 | shape_inner_disc(); 97 | shape_outer_disc(); 98 | 99 | for (s = [ [1,1], [-1,1], [1,-1] ]) { 100 | scale(s) translate([ tripod_x_off, -tripod_y_off ]) shape_tripod(); 101 | } 102 | } 103 | 104 | module exploded() 105 | { 106 | translate([ 0, 0, total_height + 2*thickness ]) linear_extrude(height = thickness, convexity = 4) shape_inner_disc(); 107 | linear_extrude(height = thickness, convexity = 4) shape_outer_disc(); 108 | 109 | color([ 0.7, 0.7, 1 ]) for (alpha = [ 0, 120, 240 ]) { 110 | rotate(alpha) 111 | translate([ 0, thickness*2 + locklen1 + inner1_to_inner2 + boltlen + midhole, 1.5*thickness ]) 112 | rotate([ 90, 0, -90 ]) 113 | linear_extrude(height = thickness, convexity = 10, center = true) shape_tripod(); 114 | } 115 | } 116 | 117 | module bottle() 118 | { 119 | r = boltlen + midhole; 120 | h = total_height - thickness*2; 121 | 122 | rotate_extrude(convexity = 2) { 123 | square([ r, h ]); 124 | 125 | translate([ 0, h ]) { 126 | intersection() { 127 | square([ r, r ]); 128 | scale([ 1, 0.7 ]) circle(r); 129 | } 130 | } 131 | translate([ 0, h+r ]) { 132 | intersection() { 133 | translate([ 0, -r/2 ]) square([ r/2, r ]); 134 | circle(r/2); 135 | } 136 | } 137 | } 138 | } 139 | 140 | module assembled() 141 | { 142 | translate([ 0, 0, total_height - thickness ]) linear_extrude(height = thickness, convexity = 4) shape_inner_disc(); 143 | linear_extrude(height = thickness, convexity = 4) shape_outer_disc(); 144 | 145 | color([ 0.7, 0.7, 1 ]) for (alpha = [ 0, 120, 240 ]) { 146 | rotate(alpha) 147 | translate([ 0, thickness*2 + locklen1 + inner1_to_inner2 + boltlen + midhole, 0 ]) 148 | rotate([ 90, 0, -90 ]) 149 | linear_extrude(height = thickness, convexity = 10, center = true) shape_tripod(); 150 | } 151 | 152 | % translate([ 0, 0, thickness*2]) bottle(); 153 | } 154 | 155 | echo(version=version()); 156 | 157 | if (mode == "parts") parts(); 158 | 159 | if (mode == "exploded") exploded(); 160 | 161 | if (mode == "assembled") assembled(); 162 | 163 | // Written by Clifford Wolf and Marius 164 | // Kintel 165 | // 166 | // To the extent possible under law, the author(s) have dedicated all 167 | // copyright and related and neighboring rights to this software to the 168 | // public domain worldwide. This software is distributed without any 169 | // warranty. 170 | // 171 | // You should have received a copy of the CC0 Public Domain 172 | // Dedication along with this software. 173 | // If not, see . 174 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/COPYING-CC0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /pysdfscad/openscad_builtins.py: -------------------------------------------------------------------------------- 1 | import sdf #type: ignore 2 | from functools import reduce, wraps 3 | import inspect 4 | from loguru import logger #type: ignore 5 | import itertools 6 | import lark 7 | import math 8 | import numpy as np 9 | from appdirs import AppDirs 10 | from pathlib import Path 11 | from urllib.request import urlopen 12 | import urllib.parse 13 | from zipfile import ZipFile 14 | import typing 15 | 16 | dirs = AppDirs("pySdfScad", "pySdfScad") 17 | 18 | Geometry = typing.Iterator[sdf.SDF3|sdf.SDF2] 19 | 20 | def child(func=lambda: tuple()): 21 | def indexer(idx=None): 22 | def inner(module_children): 23 | if not idx: 24 | yield from func() 25 | elif isinstance(idx,int): 26 | yield list(func())[idx] 27 | else: 28 | result = list(func()) 29 | for i in idx: 30 | yield result[i] 31 | return inner 32 | return indexer 33 | 34 | no_children = child() 35 | 36 | def module_children(): 37 | def inner(children=no_children): 38 | yield from children() 39 | return inner 40 | 41 | def module_echo(*args,**kwargs): 42 | def inner(children): 43 | out = [repr(i) for i in args] 44 | for k,v in kwargs.items(): 45 | k=k.removeprefix("var_") 46 | out.append(k+"="+repr(v)) 47 | logger.opt(depth=1).info("ECHO: "+", ".join(out)) 48 | yield from children()(no_children) 49 | return inner 50 | 51 | def div(left,right): 52 | """Openscad compatible division, returns inf 53 | on division by zero. 54 | """ 55 | if right == 0: return float("inf") 56 | return left/right 57 | 58 | def function_version(): 59 | from importlib.metadata import version 60 | return version('pysdfscad') 61 | 62 | def function_str(*args): 63 | args = (str(i) for i in args) 64 | return "".join(args) 65 | 66 | def function_cos(a): 67 | return math.cos(a) 68 | 69 | def function_sin(a): 70 | return math.sin(a) 71 | 72 | def function_atan2(left,right): 73 | return math.atan2(left,right) 74 | 75 | def function_min(*a): 76 | return min(*a) 77 | 78 | def function_max(*a): 79 | return max(*a) 80 | 81 | def function_sqrt(a): 82 | return math.sqrt(a) 83 | 84 | def function_pow(left,right): 85 | return math.pow(left,right) 86 | 87 | var_undef = None 88 | var_true = True 89 | var_false = False 90 | var_PI = math.pi 91 | 92 | class Range: 93 | """Generator that works like an openscad range 94 | """ 95 | def __init__(self,start,stop,step): 96 | self.start=float(start) 97 | self.stop=float(stop) 98 | self.step=float(step) 99 | 100 | def __iter__(self): 101 | count = 0 102 | while True: 103 | temp = float(self.start + count * self.step) 104 | if self.step > 0 and temp >= self.stop: 105 | break 106 | elif self.step < 0 and temp <= self.stop: 107 | break 108 | yield temp 109 | count += 1 110 | 111 | def __repr__(self): 112 | return f"[{self.start}:{self.step}:{self.stop}]" 113 | 114 | def scad_range(start,stop,step): 115 | return Range(start,stop,step) 116 | 117 | #def function_S(i): 118 | # """Convert the given string into a symbolic representation. 119 | # """ 120 | # return S(i) 121 | 122 | #def function_N(i): 123 | # """Convert a sympy object into a float 124 | # """ 125 | # return N(i) 126 | 127 | def module_sphere(var_r): 128 | def inner(children): 129 | yield sdf.sphere(var_r) 130 | return inner 131 | 132 | def module_circle(var_r,var_fn=None): 133 | def inner(children): 134 | yield sdf.circle(var_r) 135 | return inner 136 | 137 | def module_square(var_size,var_center=False): 138 | def inner(children=no_children): 139 | x,y=var_size 140 | offset=(0,0) 141 | if not var_center: 142 | offset=[x/2,y/2] 143 | yield sdf.rectangle(var_size).translate(offset) 144 | return inner 145 | 146 | def module_cylinder(var_r=0, var_r1=None, var_r2=None, var_h=None, var_center=False): 147 | 148 | if var_r1 == None : var_r1=var_r 149 | if var_r2 == None : var_r2=var_r 150 | 151 | def inner(children=lambda:()): 152 | if var_center == False: 153 | yield sdf.capped_cone([0,0,0], sdf.Z*var_h, var_r1, var_r2) 154 | elif var_center==True: 155 | yield sdf.capped_cone([0,0,0], sdf.Z*var_h, var_r1, var_r2).translate(-sdf.Z*var_h/2) 156 | return inner 157 | 158 | def module_linear_extrude(var_height=1,var_center=True, var_convexity=None,var_twist=0): 159 | def inner(children=lambda:()): 160 | children = list(module_union()(children))[0] 161 | yield children.extrude(var_height) 162 | return inner 163 | 164 | 165 | def module_cube(var_size, var_center=False): 166 | def inner(children=lambda:()): 167 | x,y,z=var_size 168 | offset=(0,0,0) 169 | if not var_center: 170 | offset=[x/2,y/2,z/2] 171 | yield sdf.box(var_size).translate(offset) 172 | return inner 173 | 174 | def module_union(var_smooth=1): 175 | def inner(children): 176 | children = list(children()(no_children)) 177 | if not children: return 178 | yield reduce(lambda a,b: sdf.union(a,b,k=var_smooth), children) 179 | return inner 180 | 181 | def module_intersection(var_smooth=0): 182 | def inner(children): 183 | children = list(children()(no_children)) 184 | if not children: return 185 | yield reduce(lambda a,b: sdf.intersection(a,b,k=var_smooth), children) 186 | return inner 187 | 188 | def module_difference(var_smooth=0): 189 | def inner(children): 190 | children = list(children()(no_children)) 191 | if not children: return 192 | yield reduce(lambda a,b: sdf.difference(a,b,k=var_smooth), children) 193 | return inner 194 | 195 | def module_blend(var_ratio=0.5): 196 | def inner(children): 197 | children = list(children()(no_children)) 198 | child1 = children[0] 199 | child2 = reduce(sdf.union,children[1:]) 200 | yield child1.blend(child2,k=var_ratio) 201 | return inner 202 | 203 | def module_shell(var_thickness=10): 204 | def inner(children): 205 | children = list(module_union()(children))[0] 206 | yield children.shell(var_thickness) 207 | return inner 208 | 209 | def twist(context,degrees): 210 | #Has some significant weirdness that seems to be related to translation. 211 | return union(context).twist(degrees) 212 | 213 | def module_rotate(vector): 214 | def inner(children): 215 | if isinstance(vector,(int,float)): 216 | x=0 217 | y=0 218 | z=vector 219 | elif len(vector)==1: 220 | x=vector[0] 221 | y=0 222 | z=0 223 | elif len(vector)==2: 224 | x,y = vector 225 | z=0 226 | elif len(vector)==3: 227 | x,y,z=vector 228 | else: 229 | raise TypeError(f"Unable to convert translate({vector}) parameter to a vec3 or vec2 of numbers") 230 | children = list(module_union()(children))[0] 231 | if not children: return 232 | 233 | #Convert degrees to radians 234 | x,y,z = (i*(math.pi/180) for i in (x,y,z)) 235 | #ToDo, these are not degrees 236 | if isinstance(children,sdf.SDF3): 237 | yield children.rotate(x,sdf.X).rotate(y,sdf.Y).rotate(z,sdf.Z) 238 | elif isinstance(children,sdf.SDF2): 239 | yield children.rotate(z) 240 | else: 241 | raise TypeError(f"{type(children)} not expected") 242 | return inner 243 | 244 | def module_translate(vector): 245 | def inner(children): 246 | if len(vector)==2: 247 | x,y = vector 248 | z=0 249 | elif len(vector)==3: 250 | x,y,z=vector 251 | else: 252 | raise TypeError(f"Unable to convert translate({vector}) parameter to a vec3 or vec2 of numbers") 253 | children = list(module_union()(children))[0] 254 | if not children: return 255 | yield children.translate((x,y,z)) 256 | return inner 257 | 258 | def module_extrude(height): 259 | def inner(children=lambda:()): 260 | children = list(module_union()(children))[0] 261 | if not children: return 262 | yield children.extrude((height)) 263 | return inner 264 | 265 | 266 | def module_text(var_text,var_size=10,var_font="Arimo",var_width=None,var_height=None): 267 | #ToDo: https://stackoverflow.com/questions/43060479/how-to-get-the-font-pixel-height-using-pils-imagefont-class 268 | #Set up halign and valign 269 | if not var_height: var_height=var_size 270 | 271 | fontparts = var_font.split("-") 272 | if len(fontparts)==2: 273 | family, varient = fontparts 274 | elif len(fontparts)==1: 275 | family=fontparts[0] 276 | varient="Regular" 277 | else: 278 | raise Exception(f"Can't handle font {var_font}, too many hyphens in name") 279 | 280 | 281 | fontpath = Path(dirs.user_cache_dir)/"fonts" 282 | fontpath.mkdir(parents=True, exist_ok=True) 283 | 284 | name_stripped=family.replace(" ","") 285 | font_file = fontpath/f"{name_stripped}-{varient}.ttf" 286 | print(font_file) 287 | if not font_file.exists(): 288 | fonturl = "https://fonts.google.com/download?family="+urllib.parse.quote(family) 289 | logger.opt(depth=1).debug(f"Downloading font family from {fonturl}") 290 | with urlopen(fonturl) as zipresp: 291 | with ZipFile(BytesIO(zipresp.read())) as theZip: 292 | fileNames = theZip.namelist() 293 | for fileName in fileNames: 294 | if fileName.endswith('ttf'): 295 | content = theZip.open(fileName).read() 296 | (fontpath/Path(fileName).name).write_bytes(content) 297 | else: 298 | logger.opt(depth=1).debug(f"Found {name_stripped}-{varient}.ttf in font cache") 299 | 300 | def text_inner(children=lambda:()): 301 | w, h = sdf.measure_text(str(font_file), var_text,height=var_size,width=var_width) 302 | yield sdf.text(str(font_file), var_text, height=var_height,width=var_width,).translate((w/2,h/2)) 303 | 304 | return text_inner 305 | 306 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/main.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import textwrap 3 | import pkgutil 4 | import pysdfscad 5 | import sdf 6 | #from pysdfscad.main import EvalOpenscad, openscad_parser 7 | from pysdfscad.main import OpenscadFile, colorize_html 8 | import importlib.resources 9 | from loguru import logger 10 | from pathlib import Path 11 | import os 12 | 13 | from PySide6 import QtWidgets, QtCore, QtGui 14 | from PySide6.QtGui import QColor, QFont, QFontMetrics, QKeySequence, QAction, QShortcut 15 | from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QMenuBar, QMenu,\ 16 | QHBoxLayout, QWidget, QSplitter, QFileDialog, QMessageBox, QFrame,\ 17 | QGridLayout, QTextEdit, QTabWidget 18 | from PySide6.QtCore import Qt, QSettings, QPoint, QSize, QThread, Slot as pyqtSlot, Signal as pyqtSignal, QObject, QBuffer, QIODevice 19 | from PySide6.QtUiTools import QUiLoader 20 | 21 | import pyqtgraph as pg 22 | import pyqtgraph.opengl as gl 23 | import numpy as np 24 | 25 | 26 | from lark import Lark 27 | import json 28 | 29 | from threading import Thread 30 | from pathlib import Path 31 | 32 | from collections import defaultdict 33 | 34 | logger = logger.opt(ansi=True) 35 | 36 | #Try to get pyinstaller to work... 37 | import OpenGL.platform.egl 38 | 39 | def themes(): 40 | themeData = importlib.resources.open_binary('pysdfscad_qtgui', 'themes.json').read() 41 | data = json.loads(themeData) 42 | out = {} 43 | for item in data['themes']: 44 | name = item['name'] 45 | del item['name'] 46 | out[name]=item 47 | return out 48 | 49 | class THEME: 50 | black=0 51 | red=1 52 | green=2 53 | yellow=3 54 | blue=4 55 | purple=5 56 | cyan=6 57 | white=7 58 | brightBlack=8 59 | brightRed=9 60 | brightGreen=10 61 | brightYellow=11 62 | brightBlue=12 63 | brightPurple=13 64 | brightCyan=14 65 | brightWhite=15 66 | foreground=16 67 | background=17 68 | all_items = ('black','red','green','yellow','blue','purple','cyan','white','brightBlack','brightRed', 69 | 'brightGreen','brightYellow','brightBlue','brightPurple','brightCyan', 70 | 'brightWhite','foreground','background') 71 | 72 | 73 | 74 | 75 | from pathlib import Path 76 | 77 | EXAMPLE_TEXT=""" 78 | // intersection.scad - Example for intersection() usage in OpenSCAD 79 | 80 | echo(version=version()); 81 | 82 | module example_intersection() 83 | { 84 | intersection() { 85 | difference() { 86 | union(smooth=10) { 87 | cube([30, 30, 30], center = true); 88 | translate([0, 0, -25]) 89 | cube([15, 15, 50], center = true); 90 | } 91 | union() { 92 | cube([50, 10, 10], center = true); 93 | cube([10, 50, 10], center = true); 94 | cube([10, 10, 50], center = true); 95 | } 96 | } 97 | translate([0, 0, 5]) 98 | cylinder(h = 50, r1 = 20, r2 = 5, center = true); 99 | } 100 | } 101 | 102 | example_intersection(); 103 | 104 | 105 | 106 | // Written by Clifford Wolf and Marius 107 | // Kintel 108 | // 109 | // To the extent possible under law, the author(s) have dedicated all 110 | // copyright and related and neighboring rights to this software to the 111 | // public domain worldwide. This software is distributed without any 112 | // warranty. 113 | // 114 | // You should have received a copy of the CC0 Public Domain 115 | // Dedication along with this software. 116 | // If not, see . 117 | """ 118 | 119 | from pysdfscad_qtgui.logWidget import QTextEditLogger 120 | from contextlib import redirect_stdout 121 | 122 | class LoggerWriter: 123 | def __init__(self, level): 124 | # self.level is really like using log.debug(message) 125 | # at least in my case 126 | self.level = level 127 | 128 | def write(self, message): 129 | # if statement reduces the amount of newlines that are 130 | # printed to the logger 131 | if message != '\n': 132 | self.level(message) 133 | 134 | def flush(self): 135 | pass 136 | 137 | import os 138 | 139 | log_format='{level: <8} | {name}:{function}:{line} - {message}' 140 | 141 | class RenderWorker(QObject): 142 | ast_ready = pyqtSignal(str) 143 | python_ready = pyqtSignal(str) 144 | mesh_ready = pyqtSignal(object) 145 | log_message = pyqtSignal(str) 146 | finished = pyqtSignal() 147 | 148 | def __init__(self, openscad_file): 149 | super().__init__() 150 | self.openscad_file = openscad_file 151 | 152 | @pyqtSlot() 153 | def run(self): 154 | try: 155 | #Try and set background threads to a *low* priority, 156 | # since people on the internet seem confused about this 157 | # a higher nice value means your program is *nicer* to 158 | # other programs, and will get out of their way. 159 | try: 160 | os.nice(14) 161 | except: pass 162 | 163 | ast_text = self.openscad_file.as_ast() 164 | self.ast_ready.emit(ast_text) 165 | 166 | python_text = self.openscad_file.as_python() 167 | self.python_ready.emit(python_text) 168 | 169 | result = list(self.openscad_file.run()) 170 | if not result: 171 | self.log_message.emit("No top level geometry to render") 172 | else: 173 | res = result[0] 174 | if isinstance(res, sdf.SDF2): 175 | res = res.extrude(0.1) 176 | 177 | with redirect_stdout(LoggerWriter(logger.opt(depth=1).info)): 178 | points = res.generate() 179 | 180 | points, cells = np.unique(points, axis=0, return_inverse=True) 181 | cells = cells.reshape((-1, 3)) 182 | 183 | self.mesh_ready.emit((points, cells, res)) 184 | 185 | except Exception: 186 | logger.exception("Error during render") 187 | finally: 188 | self.finished.emit() 189 | 190 | 191 | class MainUi(QMainWindow): 192 | def __init__(self): 193 | super().__init__() # Call the inherited classes __init__ method 194 | loader = QUiLoader() 195 | ui_file_data = importlib.resources.open_binary('pysdfscad_qtgui', 'main.ui').read() 196 | ui_buffer = QBuffer() 197 | ui_buffer.setData(ui_file_data) 198 | ui_buffer.open(QIODevice.ReadOnly) 199 | self.ui_window = loader.load(ui_buffer) 200 | 201 | self.setCentralWidget(self.ui_window.centralWidget()) 202 | self.setMenuBar(self.ui_window.menuBar()) 203 | self.setStatusBar(self.ui_window.statusBar()) 204 | self.setWindowTitle(self.ui_window.windowTitle()) 205 | 206 | self.sideSplitter = self.findChild(QSplitter, "sideSplitter") 207 | self.tabWidget = self.findChild(QTabWidget, "tabWidget") 208 | self.action_New = self.ui_window.findChild(QAction, "action_New") 209 | self.actionOpen = self.ui_window.findChild(QAction, "actionOpen") 210 | self.actionSave = self.ui_window.findChild(QAction, "actionSave") 211 | self.actionSave_As = self.ui_window.findChild(QAction, "actionSave_As") 212 | self.actionExit = self.ui_window.findChild(QAction, "actionExit") 213 | self.actionRender = self.ui_window.findChild(QAction, "actionRender") 214 | self.actionExport_Mesh = self.ui_window.findChild(QAction, "actionExport_Mesh") 215 | 216 | self.readSettings() 217 | 218 | self.openscadFile=OpenscadFile() 219 | self.mesh=None 220 | self.result=None 221 | 222 | self.preview3d=gl.GLViewWidget(self.sideSplitter) 223 | self.preview3d.setCameraPosition(distance=40) 224 | 225 | self.logger=QTextEditLogger(self.sideSplitter) 226 | self._logger_handle_id=logger.add(self.logger, colorize=True,format=log_format) 227 | 228 | self.editor=QTextEdit() 229 | font = QFont() 230 | font.setFamily('Consolas') 231 | font.setFixedPitch(True) 232 | font.setPointSize(10) 233 | font.setBold(True) 234 | self.editor.setFont(font) 235 | self.editor.setPlainText(EXAMPLE_TEXT) 236 | 237 | self.tabWidget.addTab(self.editor,"Source") 238 | 239 | #self._exampleMenu(self.findChild(QMenu,"menuExamples")) 240 | 241 | self.astPreview=QTextEdit() 242 | self.astPreview.setReadOnly(True) 243 | self.tabWidget.addTab(self.astPreview,"AST") 244 | 245 | self.pythonPreview=QTextEdit() 246 | self.pythonPreview.setReadOnly(True) 247 | self.tabWidget.addTab(self.pythonPreview,"Python") 248 | 249 | self._connectActions() 250 | 251 | def _connectActions(self): 252 | # Connect File actions 253 | self.action_New.triggered.connect(self.newFile) 254 | self.actionOpen.triggered.connect(self.openFile) 255 | self.actionSave.triggered.connect(self.saveFile) 256 | self.actionSave_As.triggered.connect(self.saveFileAs) 257 | self.actionExit.triggered.connect(self.close) 258 | self.actionRender.triggered.connect(self.render) 259 | self.actionExport_Mesh.triggered.connect(self.exportMesh) 260 | # Connect Edit actions 261 | # self.copyAction.triggered.connect(self.copyContent) 262 | # self.pasteAction.triggered.connect(self.pasteContent) 263 | # self.cutAction.triggered.connect(self.cutContent) 264 | # Connect Help actions 265 | # self.helpContentAction.triggered.connect(self.helpContent) 266 | # self.aboutAction.triggered.connect(self.about) 267 | 268 | def newFile(self): 269 | self.openscadFile.file=OpenscadFile() 270 | self.openscadFile.reload() 271 | 272 | def saveFileAs(self): 273 | dlg = QFileDialog() 274 | dlg.setFileMode(QFileDialog.AnyFile) 275 | if dlg.exec_(): 276 | filename = dlg.selectedFiles()[0] 277 | self.openscadFile.file=Path(filename) 278 | self.saveFile() 279 | self.setWindowTitle(f"{self.openscadFile.file} - pySdfScad") 280 | 281 | 282 | def update_mesh(self, data): 283 | points, cells, result_obj = data 284 | self.result = result_obj 285 | self.mesh = (points, cells) 286 | 287 | meshdata = gl.MeshData(vertexes=points, faces=cells) 288 | mesh = gl.GLMeshItem(meshdata=meshdata, 289 | smooth=False, drawFaces=True, 290 | drawEdges=False, 291 | shader="normalColor", 292 | color = (1,1,1,1), edgeColor=(0.2, 0.5, 0.2, 1) 293 | ) 294 | self.preview3d.clear() 295 | g = gl.GLGridItem() 296 | g.setSize(200, 200) 297 | g.setSpacing(10, 10) 298 | 299 | a=gl.GLAxisItem() 300 | a.setSize(10,10,10) 301 | 302 | self.preview3d.addItem(g) 303 | self.preview3d.addItem(a) 304 | self.preview3d.addItem(mesh) 305 | 306 | def exportMesh(self): 307 | if not self.result: 308 | return 309 | dlg = QFileDialog() 310 | dlg.setFileMode(QFileDialog.AnyFile) 311 | dlg.setNameFilters(["STL file (*.stl)","Guess from extention (*)"]) 312 | dlg.selectNameFilter("STL file (*.stl)") 313 | if dlg.exec_(): 314 | filename = dlg.selectedFiles()[0] 315 | self.result.save(filename) 316 | 317 | def openFile(self): 318 | dlg = QFileDialog() 319 | dlg.setFileMode(QFileDialog.ExistingFile) 320 | dlg.setNameFilters(["Scad files (*.scad)","All files (*)"]) 321 | dlg.selectNameFilter("Scad files (*.scad)") 322 | if dlg.exec_(): 323 | filename = dlg.selectedFiles()[0] 324 | self.openscadFile.file=Path(filename) 325 | self.openscadFile.reload() 326 | self.editor.setPlainText(self.openscadFile.text) 327 | self.setWindowTitle(f"{self.openscadFile.file} - pySdfScad") 328 | 329 | def saveFile(self): 330 | if not self.openscadFile.file: 331 | self.saveFileAs() 332 | else: 333 | self.openscadFile.text=self.editor.toPlainText() 334 | self.openscadFile.save() 335 | 336 | def render(self): 337 | self.logger._text=[] 338 | logger.warning(f"Early \x1b[31malpha!\x1b[0m Really!") 339 | logger.warning(f'Save your files \x1b[31mregularly\x1b[0m') 340 | logger.info(f"Started new render with file {self.openscadFile.file}") 341 | self.preview3d.clear() 342 | self.astPreview.setPlainText("") 343 | self.pythonPreview.setPlainText("") 344 | 345 | self.openscadFile.text = self.editor.toPlainText() 346 | 347 | self.thread = QThread() 348 | self.worker = RenderWorker(self.openscadFile) 349 | self.worker.moveToThread(self.thread) 350 | self.thread.started.connect(self.worker.run) 351 | self.worker.finished.connect(self.thread.quit) 352 | self.worker.finished.connect(self.worker.deleteLater) 353 | self.thread.finished.connect(self.thread.deleteLater) 354 | self.worker.ast_ready.connect(self.astPreview.setPlainText) 355 | self.worker.python_ready.connect(self.pythonPreview.setPlainText) 356 | self.worker.mesh_ready.connect(self.update_mesh) 357 | self.worker.log_message.connect(logger.info) 358 | self.thread.start() 359 | 360 | def closeEvent(self, event): 361 | logger.remove(self._logger_handle_id) 362 | settings = QSettings() 363 | settings.setValue('geometry',self.saveGeometry()) 364 | settings.setValue('windowState',self.saveState()) 365 | super().closeEvent(event) 366 | 367 | def readSettings(self): 368 | settings = QSettings() 369 | try: 370 | self.restoreGeometry(settings.value("geometry")) 371 | self.restoreState(settings.value("windowState")) 372 | except: 373 | logger.warning("Couldn't restore window state from settings") 374 | 375 | def _loadExample(self,file): 376 | self.editor.setPlainText(file.read_text()) 377 | 378 | def _exampleMenu(self,parent): 379 | """Generate examples based on folder structure 380 | """ 381 | #ToDO: this is a bunch of files we need to check for during 382 | # startup, which will reduce startup time (especially on non-ssds). 383 | # Can we generate this menu dynamically? 384 | module_path = Path(os.path.dirname(os.path.realpath(__file__))) 385 | 386 | nested_dict = lambda: defaultdict(nested_dict) 387 | nest = nested_dict() 388 | 389 | for a in (module_path/"examples").glob("**/*.scad"): 390 | rel = a.relative_to(module_path/"examples") 391 | nested=nest 392 | for b in rel.parent.parts: 393 | nested=nested[b] 394 | nested[a.name]=a 395 | 396 | def recursive_menu(item,parent): 397 | for key, value in item.items(): 398 | if isinstance(value, dict): 399 | menu = QMenu(key, parent) 400 | for i in recursive_menu(value,menu): 401 | menu.addMenu(i) 402 | yield menu 403 | else: 404 | text = value.read_text 405 | action = parent.addAction(key) 406 | action.triggered.connect(lambda: self.editor.setPlainText(text())) 407 | 408 | menu_items=list(recursive_menu(nest,parent)) 409 | parent.addMenu(menu_items[0]) 410 | 411 | 412 | def main(): 413 | app = QApplication(sys.argv) 414 | app.setDesktopFileName("pysdfscad") 415 | # app.setWindowIcon(QtGui.QIcon(str(ui_dir/'logo.png'))) 416 | # win = Window() 417 | win=MainUi() 418 | win.show() 419 | sys.exit(app.exec_()) 420 | 421 | 422 | if __name__ == "__main__": 423 | main() 424 | -------------------------------------------------------------------------------- /pysdfscad_qtgui/examples/openscad/Old/example012.stl: -------------------------------------------------------------------------------- 1 | solid ascii 2 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 3 | outer loop 4 | vertex 3.422440e+000 -1.067730e+001 5.000000e+000 5 | vertex 6.347434e+000 -1.067730e+001 5.000000e+000 6 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 7 | endloop 8 | endfacet 9 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 10 | outer loop 11 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 12 | vertex 6.347434e+000 -1.067730e+001 5.000000e+000 13 | vertex 6.347434e+000 1.133150e+001 5.000000e+000 14 | endloop 15 | endfacet 16 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 17 | outer loop 18 | vertex 2.280978e+000 1.133150e+001 5.000000e+000 19 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 20 | vertex 6.347434e+000 1.133150e+001 5.000000e+000 21 | endloop 22 | endfacet 23 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 24 | outer loop 25 | vertex 2.280978e+000 1.133150e+001 5.000000e+000 26 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 27 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 28 | endloop 29 | endfacet 30 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 31 | outer loop 32 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 33 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 34 | vertex -2.320538e+000 -4.613285e+000 5.000000e+000 35 | endloop 36 | endfacet 37 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 38 | outer loop 39 | vertex -2.320538e+000 -4.613285e+000 5.000000e+000 40 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 41 | vertex -4.104071e+000 -4.613285e+000 5.000000e+000 42 | endloop 43 | endfacet 44 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 45 | outer loop 46 | vertex -4.104071e+000 -4.613285e+000 5.000000e+000 47 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 48 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 49 | endloop 50 | endfacet 51 | facet normal -0.000000e+000 -0.000000e+000 1.000000e+000 52 | outer loop 53 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 54 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 55 | vertex -8.527233e+000 1.133150e+001 5.000000e+000 56 | endloop 57 | endfacet 58 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 59 | outer loop 60 | vertex -1.252235e+001 1.133150e+001 5.000000e+000 61 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 62 | vertex -8.527233e+000 1.133150e+001 5.000000e+000 63 | endloop 64 | endfacet 65 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 66 | outer loop 67 | vertex -1.252235e+001 1.133150e+001 5.000000e+000 68 | vertex -1.252235e+001 -1.067730e+001 5.000000e+000 69 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 70 | endloop 71 | endfacet 72 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 73 | outer loop 74 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 75 | vertex -1.252235e+001 -1.067730e+001 5.000000e+000 76 | vertex -9.793542e+000 -1.067730e+001 5.000000e+000 77 | endloop 78 | endfacet 79 | facet normal 0.000000e+000 -1.000000e+000 0.000000e+000 80 | outer loop 81 | vertex -1.252235e+001 1.133150e+001 1.500000e+001 82 | vertex -1.252235e+001 1.133150e+001 5.000000e+000 83 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 84 | endloop 85 | endfacet 86 | facet normal 0.000000e+000 -1.000000e+000 -0.000000e+000 87 | outer loop 88 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 89 | vertex -1.252235e+001 1.133150e+001 5.000000e+000 90 | vertex -8.527233e+000 1.133150e+001 5.000000e+000 91 | endloop 92 | endfacet 93 | facet normal -9.124922e-001 -4.090940e-001 -0.000000e+000 94 | outer loop 95 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 96 | vertex -8.527233e+000 1.133150e+001 5.000000e+000 97 | vertex -3.033951e+000 -9.213713e-001 1.500000e+001 98 | endloop 99 | endfacet 100 | facet normal -9.124922e-001 -4.090940e-001 0.000000e+000 101 | outer loop 102 | vertex -3.033951e+000 -9.213713e-001 1.500000e+001 103 | vertex -8.527233e+000 1.133150e+001 5.000000e+000 104 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 105 | endloop 106 | endfacet 107 | facet normal 9.174094e-001 -3.979447e-001 0.000000e+000 108 | outer loop 109 | vertex -3.033951e+000 -9.213713e-001 1.500000e+001 110 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 111 | vertex 2.280978e+000 1.133150e+001 1.500000e+001 112 | endloop 113 | endfacet 114 | facet normal 9.174094e-001 -3.979447e-001 0.000000e+000 115 | outer loop 116 | vertex 2.280978e+000 1.133150e+001 1.500000e+001 117 | vertex -3.033951e+000 -9.213713e-001 5.000000e+000 118 | vertex 2.280978e+000 1.133150e+001 5.000000e+000 119 | endloop 120 | endfacet 121 | facet normal 0.000000e+000 -1.000000e+000 0.000000e+000 122 | outer loop 123 | vertex 2.280978e+000 1.133150e+001 1.500000e+001 124 | vertex 2.280978e+000 1.133150e+001 5.000000e+000 125 | vertex 6.347434e+000 1.133150e+001 1.500000e+001 126 | endloop 127 | endfacet 128 | facet normal 0.000000e+000 -1.000000e+000 -0.000000e+000 129 | outer loop 130 | vertex 6.347434e+000 1.133150e+001 1.500000e+001 131 | vertex 2.280978e+000 1.133150e+001 5.000000e+000 132 | vertex 6.347434e+000 1.133150e+001 5.000000e+000 133 | endloop 134 | endfacet 135 | facet normal -1.000000e+000 -0.000000e+000 -0.000000e+000 136 | outer loop 137 | vertex 6.347434e+000 1.133150e+001 1.500000e+001 138 | vertex 6.347434e+000 1.133150e+001 5.000000e+000 139 | vertex 6.347434e+000 -1.067730e+001 1.500000e+001 140 | endloop 141 | endfacet 142 | facet normal -1.000000e+000 -0.000000e+000 -0.000000e+000 143 | outer loop 144 | vertex 6.347434e+000 -1.067730e+001 1.500000e+001 145 | vertex 6.347434e+000 1.133150e+001 5.000000e+000 146 | vertex 6.347434e+000 -1.067730e+001 5.000000e+000 147 | endloop 148 | endfacet 149 | facet normal 0.000000e+000 1.000000e+000 0.000000e+000 150 | outer loop 151 | vertex 6.347434e+000 -1.067730e+001 1.500000e+001 152 | vertex 6.347434e+000 -1.067730e+001 5.000000e+000 153 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 154 | endloop 155 | endfacet 156 | facet normal 0.000000e+000 1.000000e+000 0.000000e+000 157 | outer loop 158 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 159 | vertex 6.347434e+000 -1.067730e+001 5.000000e+000 160 | vertex 3.422440e+000 -1.067730e+001 5.000000e+000 161 | endloop 162 | endfacet 163 | facet normal 1.000000e+000 -0.000000e+000 0.000000e+000 164 | outer loop 165 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 166 | vertex 3.422440e+000 -1.067730e+001 5.000000e+000 167 | vertex 3.422440e+000 8.281660e+000 1.500000e+001 168 | endloop 169 | endfacet 170 | facet normal 1.000000e+000 -0.000000e+000 0.000000e+000 171 | outer loop 172 | vertex 3.422440e+000 8.281660e+000 1.500000e+001 173 | vertex 3.422440e+000 -1.067730e+001 5.000000e+000 174 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 175 | endloop 176 | endfacet 177 | facet normal -9.134987e-001 4.068417e-001 0.000000e+000 178 | outer loop 179 | vertex 3.422440e+000 8.281660e+000 1.500000e+001 180 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 181 | vertex -2.320538e+000 -4.613285e+000 1.500000e+001 182 | endloop 183 | endfacet 184 | facet normal -9.134987e-001 4.068417e-001 0.000000e+000 185 | outer loop 186 | vertex -2.320538e+000 -4.613285e+000 1.500000e+001 187 | vertex 3.422440e+000 8.281660e+000 5.000000e+000 188 | vertex -2.320538e+000 -4.613285e+000 5.000000e+000 189 | endloop 190 | endfacet 191 | facet normal -4.979881e-016 1.000000e+000 0.000000e+000 192 | outer loop 193 | vertex -2.320538e+000 -4.613285e+000 1.500000e+001 194 | vertex -2.320538e+000 -4.613285e+000 5.000000e+000 195 | vertex -4.104071e+000 -4.613285e+000 1.500000e+001 196 | endloop 197 | endfacet 198 | facet normal -4.979881e-016 1.000000e+000 0.000000e+000 199 | outer loop 200 | vertex -4.104071e+000 -4.613285e+000 1.500000e+001 201 | vertex -2.320538e+000 -4.613285e+000 5.000000e+000 202 | vertex -4.104071e+000 -4.613285e+000 5.000000e+000 203 | endloop 204 | endfacet 205 | facet normal 9.149041e-001 4.036714e-001 0.000000e+000 206 | outer loop 207 | vertex -4.104071e+000 -4.613285e+000 1.500000e+001 208 | vertex -4.104071e+000 -4.613285e+000 5.000000e+000 209 | vertex -9.793542e+000 8.281660e+000 1.500000e+001 210 | endloop 211 | endfacet 212 | facet normal 9.149041e-001 4.036714e-001 0.000000e+000 213 | outer loop 214 | vertex -9.793542e+000 8.281660e+000 1.500000e+001 215 | vertex -4.104071e+000 -4.613285e+000 5.000000e+000 216 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 217 | endloop 218 | endfacet 219 | facet normal -1.000000e+000 -0.000000e+000 -0.000000e+000 220 | outer loop 221 | vertex -9.793542e+000 8.281660e+000 1.500000e+001 222 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 223 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 224 | endloop 225 | endfacet 226 | facet normal -1.000000e+000 -0.000000e+000 -0.000000e+000 227 | outer loop 228 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 229 | vertex -9.793542e+000 8.281660e+000 5.000000e+000 230 | vertex -9.793542e+000 -1.067730e+001 5.000000e+000 231 | endloop 232 | endfacet 233 | facet normal 0.000000e+000 1.000000e+000 0.000000e+000 234 | outer loop 235 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 236 | vertex -9.793542e+000 -1.067730e+001 5.000000e+000 237 | vertex -1.252235e+001 -1.067730e+001 1.500000e+001 238 | endloop 239 | endfacet 240 | facet normal 0.000000e+000 1.000000e+000 0.000000e+000 241 | outer loop 242 | vertex -1.252235e+001 -1.067730e+001 1.500000e+001 243 | vertex -9.793542e+000 -1.067730e+001 5.000000e+000 244 | vertex -1.252235e+001 -1.067730e+001 5.000000e+000 245 | endloop 246 | endfacet 247 | facet normal 1.000000e+000 -0.000000e+000 0.000000e+000 248 | outer loop 249 | vertex -1.252235e+001 -1.067730e+001 1.500000e+001 250 | vertex -1.252235e+001 -1.067730e+001 5.000000e+000 251 | vertex -1.252235e+001 1.133150e+001 1.500000e+001 252 | endloop 253 | endfacet 254 | facet normal 1.000000e+000 -0.000000e+000 0.000000e+000 255 | outer loop 256 | vertex -1.252235e+001 1.133150e+001 1.500000e+001 257 | vertex -1.252235e+001 -1.067730e+001 5.000000e+000 258 | vertex -1.252235e+001 1.133150e+001 5.000000e+000 259 | endloop 260 | endfacet 261 | facet normal 1.000000e+000 -0.000000e+000 0.000000e+000 262 | outer loop 263 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 264 | vertex 1.156780e+001 -1.469280e+001 0.000000e+000 265 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 266 | endloop 267 | endfacet 268 | facet normal 1.000000e+000 -0.000000e+000 0.000000e+000 269 | outer loop 270 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 271 | vertex 1.156780e+001 -1.469280e+001 0.000000e+000 272 | vertex 1.156780e+001 1.530720e+001 0.000000e+000 273 | endloop 274 | endfacet 275 | facet normal 1.184238e-016 -1.000000e+000 0.000000e+000 276 | outer loop 277 | vertex -1.843220e+001 -1.469280e+001 1.500000e+001 278 | vertex -1.843220e+001 -1.469280e+001 0.000000e+000 279 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 280 | endloop 281 | endfacet 282 | facet normal 1.184238e-016 -1.000000e+000 0.000000e+000 283 | outer loop 284 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 285 | vertex -1.843220e+001 -1.469280e+001 0.000000e+000 286 | vertex 1.156780e+001 -1.469280e+001 0.000000e+000 287 | endloop 288 | endfacet 289 | facet normal -1.000000e+000 -1.184238e-016 -0.000000e+000 290 | outer loop 291 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 292 | vertex -1.843220e+001 1.530720e+001 0.000000e+000 293 | vertex -1.843220e+001 -1.469280e+001 1.500000e+001 294 | endloop 295 | endfacet 296 | facet normal -1.000000e+000 -1.184238e-016 0.000000e+000 297 | outer loop 298 | vertex -1.843220e+001 -1.469280e+001 1.500000e+001 299 | vertex -1.843220e+001 1.530720e+001 0.000000e+000 300 | vertex -1.843220e+001 -1.469280e+001 0.000000e+000 301 | endloop 302 | endfacet 303 | facet normal 0.000000e+000 1.000000e+000 0.000000e+000 304 | outer loop 305 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 306 | vertex 1.156780e+001 1.530720e+001 0.000000e+000 307 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 308 | endloop 309 | endfacet 310 | facet normal 0.000000e+000 1.000000e+000 0.000000e+000 311 | outer loop 312 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 313 | vertex 1.156780e+001 1.530720e+001 0.000000e+000 314 | vertex -1.843220e+001 1.530720e+001 0.000000e+000 315 | endloop 316 | endfacet 317 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 318 | outer loop 319 | vertex -1.252235e+001 -1.067730e+001 1.500000e+001 320 | vertex -1.252235e+001 1.133150e+001 1.500000e+001 321 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 322 | endloop 323 | endfacet 324 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 325 | outer loop 326 | vertex -1.843220e+001 -1.469280e+001 1.500000e+001 327 | vertex -1.252235e+001 -1.067730e+001 1.500000e+001 328 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 329 | endloop 330 | endfacet 331 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 332 | outer loop 333 | vertex -1.843220e+001 -1.469280e+001 1.500000e+001 334 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 335 | vertex -1.252235e+001 -1.067730e+001 1.500000e+001 336 | endloop 337 | endfacet 338 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 339 | outer loop 340 | vertex -1.843220e+001 -1.469280e+001 1.500000e+001 341 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 342 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 343 | endloop 344 | endfacet 345 | facet normal -0.000000e+000 -0.000000e+000 1.000000e+000 346 | outer loop 347 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 348 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 349 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 350 | endloop 351 | endfacet 352 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 353 | outer loop 354 | vertex -2.320538e+000 -4.613285e+000 1.500000e+001 355 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 356 | vertex 3.422440e+000 8.281660e+000 1.500000e+001 357 | endloop 358 | endfacet 359 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 360 | outer loop 361 | vertex -2.320538e+000 -4.613285e+000 1.500000e+001 362 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 363 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 364 | endloop 365 | endfacet 366 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 367 | outer loop 368 | vertex -2.320538e+000 -4.613285e+000 1.500000e+001 369 | vertex -4.104071e+000 -4.613285e+000 1.500000e+001 370 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 371 | endloop 372 | endfacet 373 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 374 | outer loop 375 | vertex -9.793542e+000 -1.067730e+001 1.500000e+001 376 | vertex -4.104071e+000 -4.613285e+000 1.500000e+001 377 | vertex -9.793542e+000 8.281660e+000 1.500000e+001 378 | endloop 379 | endfacet 380 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 381 | outer loop 382 | vertex -3.033951e+000 -9.213713e-001 1.500000e+001 383 | vertex 2.280978e+000 1.133150e+001 1.500000e+001 384 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 385 | endloop 386 | endfacet 387 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 388 | outer loop 389 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 390 | vertex 2.280978e+000 1.133150e+001 1.500000e+001 391 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 392 | endloop 393 | endfacet 394 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 395 | outer loop 396 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 397 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 398 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 399 | endloop 400 | endfacet 401 | facet normal -0.000000e+000 0.000000e+000 1.000000e+000 402 | outer loop 403 | vertex -1.843220e+001 1.530720e+001 1.500000e+001 404 | vertex -1.252235e+001 1.133150e+001 1.500000e+001 405 | vertex -8.527233e+000 1.133150e+001 1.500000e+001 406 | endloop 407 | endfacet 408 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 409 | outer loop 410 | vertex 2.280978e+000 1.133150e+001 1.500000e+001 411 | vertex 6.347434e+000 1.133150e+001 1.500000e+001 412 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 413 | endloop 414 | endfacet 415 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 416 | outer loop 417 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 418 | vertex 6.347434e+000 1.133150e+001 1.500000e+001 419 | vertex 6.347434e+000 -1.067730e+001 1.500000e+001 420 | endloop 421 | endfacet 422 | facet normal 0.000000e+000 0.000000e+000 1.000000e+000 423 | outer loop 424 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 425 | vertex 6.347434e+000 -1.067730e+001 1.500000e+001 426 | vertex 3.422440e+000 -1.067730e+001 1.500000e+001 427 | endloop 428 | endfacet 429 | facet normal 0.000000e+000 -0.000000e+000 1.000000e+000 430 | outer loop 431 | vertex 1.156780e+001 -1.469280e+001 1.500000e+001 432 | vertex 1.156780e+001 1.530720e+001 1.500000e+001 433 | vertex 6.347434e+000 -1.067730e+001 1.500000e+001 434 | endloop 435 | endfacet 436 | facet normal 0.000000e+000 -0.000000e+000 -1.000000e+000 437 | outer loop 438 | vertex -1.843220e+001 1.530720e+001 0.000000e+000 439 | vertex 1.156780e+001 1.530720e+001 0.000000e+000 440 | vertex -1.843220e+001 -1.469280e+001 0.000000e+000 441 | endloop 442 | endfacet 443 | facet normal 0.000000e+000 0.000000e+000 -1.000000e+000 444 | outer loop 445 | vertex -1.843220e+001 -1.469280e+001 0.000000e+000 446 | vertex 1.156780e+001 1.530720e+001 0.000000e+000 447 | vertex 1.156780e+001 -1.469280e+001 0.000000e+000 448 | endloop 449 | endfacet 450 | endsolid -------------------------------------------------------------------------------- /pysdfscad/compiler.py: -------------------------------------------------------------------------------- 1 | """ 2 | "Compiles" and openscad source file into a python abstract syntax 3 | tree. 4 | 5 | A lot more involved than the previous interpretor, but surprisignly easier 6 | to debug and generally deal with. 7 | 8 | This looks a lot my intimidating than it is (I tell myself, as I try to 9 | wrangle with the monstrocity I've writen). 10 | 11 | Since AST trees are really annoying to look at and work on we're formatting 12 | this file using `black`. I'm generally not a fan of auto format like 13 | this but I'll be damned if I'm going do manually format all these crazy 14 | AST constructors. 15 | 16 | macropy3 has a good introduction on the topic, but since we're only 17 | worried about outputting/generating an AST we can ignore more than half 18 | of it. Mostly I used it for the overview and for its recomened reading. 19 | 20 | https://macropy3.readthedocs.io/ 21 | 22 | Macropy recomends green tree snakes, which is also the main thing 23 | I use as a reference for python's AST nodes. 24 | 25 | https://greentreesnakes.readthedocs.io/en/latest/ 26 | 27 | Basically, work on the lark syntax first, keep track of what nodes 28 | are unhandled, and write a new method to handle that node. The order 29 | of the lark syntax does matter, so if you're getting some weird order 30 | or operations issues than try changing the order terms are defined in the 31 | lark syntax. 32 | 33 | You probably don't need to work on this unless you're implemnting completly 34 | new syntax though. 35 | 36 | Note that we're not actually compiling to python, we're compiling to 37 | python bytecode (more or less). 38 | We use a third-party module to turn that bytecode into 39 | python source code, but it really shouldn't matter if that representation 40 | looks good or is idiomatic. 41 | 42 | This code is hard to read, I'm not sure how to make it better or more readable. 43 | Part of the problem is that we need to do some very weird stuff to the python 44 | AST in order to make the openscad Scope work properly (which is why most everything 45 | is a decorator instead of just a regular function. There *might* be a better way to 46 | handle that by being more explicity about what scopre we're passing). 47 | 48 | Any ideas that make this more readable and debuggable would be veryy helpful, 49 | and hopefully python AST type hints will be more useful in the future. 50 | """ 51 | 52 | from lark import Lark, Transformer, v_args, Tree, Token, Discard 53 | import lark 54 | import types 55 | import ast 56 | import itertools 57 | from pathlib import Path 58 | import functools 59 | 60 | 61 | def lines(arg): 62 | """Convert various lark primitives into python AST compatible line/column metadata.""" 63 | if isinstance(arg, lark.tree.Meta): 64 | return { 65 | "lineno": arg.line, 66 | "col_offset": arg.column, 67 | "end_lineno": arg.end_line, 68 | "end_col_offset": arg.end_column, 69 | } 70 | if isinstance(arg, lark.lexer.Token): 71 | return { 72 | "lineno": arg.line, 73 | "col_offset": arg.column, 74 | "end_lineno": arg.end_line, 75 | "end_col_offset": arg.end_column, 76 | } 77 | elif isinstance(arg, ast.AST): 78 | return { 79 | "lineno": arg.lineno, 80 | "col_offset": arg.col_offset, 81 | "end_lineno": arg.end_lineno, 82 | "end_col_offset": arg.end_col_offset, 83 | } 84 | 85 | argtype = type(arg) 86 | raise TypeError(f"Unknown type {argtype} for {arg}") 87 | 88 | 89 | @v_args(meta=True, inline=True) 90 | class OpenscadToPy(Transformer): 91 | def start(self, meta, *args): 92 | argsnew = list(self._normalize_block(args)) 93 | 94 | return ast.Module( 95 | [ 96 | ast.ImportFrom( 97 | module="pysdfscad.openscad_builtins", 98 | lineno=0, 99 | col_offset=0, 100 | names=[ 101 | ast.alias(name="*", asname=None, lineno=0, col_offset=0), 102 | ], 103 | level=0, 104 | ), 105 | ast.FunctionDef( 106 | name="main", 107 | decorator_list=[], 108 | body=argsnew, 109 | args=ast.arguments( 110 | args=[], 111 | posonlyargs=[], 112 | kwonlyargs=[], 113 | kw_defaults=[], 114 | defaults=[], 115 | ), 116 | lineno=0, 117 | col_offset=0, 118 | ), 119 | ], 120 | type_ignores=[], 121 | ) 122 | 123 | def _normalize_block(self, expressions): 124 | """This function pulls a lot of weight, there are a few things we 125 | need to do when assembling a code block in our AST. 126 | 127 | We wrap top level expressions that don't save any data in an ast.Expr object, which is requires. 128 | We re order definitions, so that definitions always appear before the following code. 129 | We yield from modules. 130 | """ 131 | new_expression = [] 132 | module_definition = [] 133 | function_definition = [] 134 | # logger.opt(depth=1).info(f"{expressions}") 135 | for arg in expressions: 136 | if isinstance(arg, types.GeneratorType): 137 | yield from self._normalize_block(arg) 138 | 139 | elif isinstance(arg, ast.Call): 140 | # Wrap modules in a yield from, so we can unwind our openscad tree into one 141 | # top level piece of geometry. 142 | if arg.func.id.startswith("module_"): 143 | new_expression.append( 144 | ast.Expr( 145 | ast.YieldFrom( 146 | arg, 147 | **lines(arg), 148 | ), 149 | **lines(arg), 150 | ) 151 | ) 152 | # "When an expression, such as a function call, appears as a statement by itself (an expression statement), with its return value not used or stored, it is wrapped in this container." 153 | # So we need to wrap lone calls that are just hanging out on a line in an Expr 154 | else: 155 | new_expression.append( 156 | ast.Expr( 157 | value=arg, 158 | **lines(arg), 159 | ) 160 | ) 161 | elif isinstance(arg, ast.FunctionDef): 162 | 163 | if False: # arg.func.id.startswith("module_"): 164 | module_definition.append(arg) 165 | else: 166 | function_definition.append(arg) 167 | else: 168 | new_expression.append(arg) 169 | yield from [*function_definition, *module_definition, *new_expression] 170 | 171 | def operator_call(self, meta, f_name: Token, args, block): 172 | block_children = list(self._normalize_block(block)) 173 | args, kwargs = args 174 | 175 | if not block_children: 176 | """Simplify our call tree if there are no children""" 177 | yield ast.Expr( 178 | ast.YieldFrom( 179 | ast.Call( 180 | ast.Call( 181 | ast.Name( 182 | id="module_" + f_name.value, 183 | ctx=ast.Load(), 184 | **lines(f_name), 185 | ), 186 | args=[*args], 187 | keywords=list(kwargs), 188 | **lines(meta), 189 | ), 190 | args=[ 191 | ast.Name( 192 | id="no_children", 193 | ctx=ast.Load(), 194 | **lines(meta), 195 | ), 196 | ], 197 | keywords=[], 198 | **lines(meta), 199 | ), 200 | **lines(meta), 201 | ), 202 | **lines(meta), 203 | ) 204 | 205 | elif block_children: 206 | yield ast.FunctionDef( 207 | name="children_" + f_name.value, 208 | decorator_list=[ 209 | ast.Call( 210 | ast.Name( 211 | id="module_" + f_name.value, 212 | ctx=ast.Load(), 213 | **lines(f_name), 214 | ), 215 | args=[*args], 216 | keywords=list(kwargs), 217 | **lines(meta), 218 | ), 219 | ast.Name( 220 | id="child", 221 | ctx=ast.Load(), 222 | **lines(f_name), 223 | ), 224 | ], 225 | body=block_children 226 | or [ 227 | ast.Pass(**lines(meta)), 228 | ], 229 | args=ast.arguments( 230 | args=[], 231 | posonlyargs=[], 232 | kwonlyargs=[], 233 | kw_defaults=[], 234 | defaults=[], 235 | ), 236 | **lines(meta), 237 | ) 238 | 239 | yield ast.Expr( 240 | ast.YieldFrom( 241 | ast.Name( 242 | id="children_" + f_name.value, 243 | ctx=ast.Load(), 244 | **lines(f_name), 245 | ), 246 | **lines(meta), 247 | ), 248 | **lines(meta), 249 | ) 250 | 251 | def COMMENT(self, token): 252 | return ast.Expr( 253 | ast.Constant(token.value, **lines(token)), 254 | **lines(token), 255 | ) 256 | 257 | def function_call(self, meta, f_name: Token, args): 258 | args, kwargs = args 259 | return ast.Call( 260 | ast.Name( 261 | id="function_" + f_name.value, 262 | ctx=ast.Load(), 263 | **lines(f_name), 264 | ), 265 | args=list(args), 266 | keywords=list(kwargs), 267 | **lines(meta), 268 | ) 269 | 270 | def combined_args(self, meta, *args_orig): 271 | if len(args_orig) == 2: 272 | args, kwargs = args_orig 273 | elif len(args_orig) == 1: 274 | if isinstance(args_orig[0][0], ast.keyword): 275 | args = [] 276 | kwargs = args_orig[0] 277 | else: 278 | args = args_orig[0] 279 | kwargs = [] 280 | elif len(args_orig) == 0: 281 | args = [] 282 | kwargs = [] 283 | else: 284 | raise Exception( 285 | "That's the wrong nuber of args, something has gone very wrong" 286 | ) 287 | return args, kwargs 288 | 289 | def for_loop(self, meta, args, block): 290 | args, kwargs = args 291 | block = list(self._normalize_block(block)) 292 | assert not args 293 | target = [] 294 | values = [] 295 | for keyword in kwargs: 296 | target.append(ast.Name(id=keyword.arg, ctx=ast.Store(), **lines(meta))) 297 | values.append(keyword.value) 298 | if len(target) > 1: 299 | target = ast.Tuple( 300 | target, 301 | ast.Store(), 302 | **lines(meta), 303 | ) 304 | else: 305 | target = target[0] 306 | if len(values) > 1: 307 | values = ast.Call( 308 | func=ast.Attribute( 309 | value=ast.Name(id="itertools", ctx=ast.Load(), **lines(meta)), 310 | attr="product", 311 | ctx=ast.Load(), 312 | **lines(meta), 313 | ), 314 | args=values, 315 | keywords=[], 316 | **lines(meta), 317 | body=block, 318 | ) 319 | else: 320 | values = values[0] 321 | result = ast.For( 322 | target=target, iter=values, body=block, orelse=[], **lines(meta) 323 | ) 324 | yield result 325 | 326 | def kwargs(self, meta, *children): 327 | return children 328 | 329 | def args(self, meta, *children): 330 | return children 331 | 332 | def ESCAPED_STRING(self, token): 333 | 334 | out = ast.literal_eval(token.value) 335 | assert type(out) == str 336 | return ast.Constant( 337 | out, 338 | None, 339 | **lines(token), 340 | ) 341 | 342 | def number(self, meta, token): 343 | # Convert to int or float depending... 344 | out = ast.literal_eval(token.value) 345 | return ast.Constant(out, None, **lines(meta)) 346 | 347 | def var(self, meta, token): 348 | return ast.Name(id="var_" + token.value, ctx=ast.Load(), **lines(token)) 349 | 350 | def kwargvalue(self, meta, token, value): 351 | return ast.keyword( 352 | arg="var_" + token.value, 353 | value=value, 354 | **lines(token), 355 | ) 356 | 357 | def assign_var(self, meta, name, value): 358 | yield ast.Assign( 359 | targets=[ 360 | ast.Name(id="var_" + name.value, ctx=ast.Store(), **lines(name)), 361 | ], 362 | value=value, 363 | **lines(meta), 364 | ) 365 | 366 | def args_definition(self, meta, *args_orig): 367 | if len(args_orig) == 2: 368 | args, kwargs = args_orig 369 | elif len(args_orig) == 1: 370 | if isinstance(args_orig[0][0], ast.keyword): 371 | args = [] 372 | kwargs = args_orig[0] 373 | else: 374 | args = args_orig[0] 375 | kwargs = [] 376 | elif len(args_orig) == 0: 377 | args = [] 378 | kwargs = [] 379 | else: 380 | raise Exception( 381 | "That's the wrong nuber of args, something has gone very wrong" 382 | ) 383 | 384 | newargs = [] 385 | for arg in args: 386 | newargs.append(ast.arg(arg="var_" + arg.value, **lines(arg))) 387 | return newargs, kwargs 388 | 389 | def arg_def_name(self, meta, *children): 390 | return children 391 | 392 | def name(self, meta, children): 393 | return children 394 | 395 | def add(self, meta, left, right): 396 | return ast.BinOp(left=left, right=right, op=ast.Add(), **lines(meta)) 397 | 398 | def mul(self, meta, left, right): 399 | return ast.BinOp(left=left, right=right, op=ast.Mult(), **lines(meta)) 400 | 401 | def sub(self, meta, left, right): 402 | return ast.BinOp(left=left, right=right, op=ast.Sub(), **lines(meta)) 403 | 404 | def mod(self, meta, left, right): 405 | return ast.BinOp(left=left, right=right, op=ast.Mod(), **lines(meta)) 406 | 407 | def exp(self, meta, left, right): 408 | return ast.BinOp(left=left, right=right, op=ast.Pow(), **lines(meta)) 409 | 410 | def div(self, meta, left, right): 411 | return ast.Call( 412 | ast.Name(id="div", ctx=ast.Load(), **lines(meta)), 413 | args=[left, right], 414 | keywords=[], 415 | **lines(meta), 416 | ) 417 | 418 | def ifelse(self, meta, test, body, orelse=None): 419 | if orelse: 420 | orelse = list(self._normalize_block(orelse)) 421 | else: 422 | orelse = [] 423 | body = list(self._normalize_block(body)) 424 | return ast.If(test=test, body=body, orelse=orelse, **lines(meta)) 425 | 426 | def inequality(self, meta, left, right): 427 | return ast.Compare( 428 | left=left, 429 | ops=[ 430 | ast.NotEq(), 431 | ], 432 | comparators=[ 433 | right, 434 | ], 435 | **lines(meta), 436 | ) 437 | 438 | def equality(self, meta, left, right): 439 | return ast.Compare( 440 | left=left, 441 | ops=[ 442 | ast.Eq(), 443 | ], 444 | comparators=[ 445 | right, 446 | ], 447 | **lines(meta), 448 | ) 449 | 450 | def and_op(self, meta, left, right): 451 | return ast.BoolOp( 452 | values=[ 453 | left, 454 | right, 455 | ], 456 | op=ast.And(), 457 | **lines(meta), 458 | ) 459 | 460 | def lt_op(self, meta, left, right): 461 | return ast.Compare( 462 | left=left, 463 | ops=[ 464 | ast.Lt(), 465 | ], 466 | comparators=[ 467 | right, 468 | ], 469 | **lines(meta), 470 | ) 471 | 472 | def gt_op(self, meta, left, right): 473 | return ast.Compare( 474 | left=left, 475 | ops=[ 476 | ast.Gt(), 477 | ], 478 | comparators=[ 479 | right, 480 | ], 481 | **lines(meta), 482 | ) 483 | 484 | def vector_index(self, meta, obj, idx): 485 | return ast.Subscript(obj, idx, ctx=ast.Load(), **lines(meta)) 486 | 487 | def range(self, meta, start, stop, step=None): 488 | if step != None: 489 | step, stop = stop, step 490 | else: 491 | step = ast.Constant(value=1, kind=None, **lines(meta)) 492 | return ast.Call( 493 | func=ast.Name(id="scad_range", ctx=ast.Load(), **lines(meta)), 494 | args=[start, stop, step], 495 | keywords=[], 496 | **lines(meta), 497 | ) 498 | 499 | def conditional_op(self, meta, test, body, orelse): 500 | return ast.IfExp( 501 | test=test, 502 | body=body, 503 | orelse=orelse, 504 | **lines(meta), 505 | ) 506 | 507 | def neg(self, meta, token): 508 | return ast.UnaryOp( 509 | op=ast.USub(), 510 | operand=token, 511 | **lines(token), 512 | ) 513 | 514 | def block(self, meta, *children): 515 | return children 516 | 517 | def module_def(self, meta, name, args, body): 518 | args, kwargs = args 519 | for kwarg in kwargs: 520 | args.append( 521 | ast.arg( 522 | kwarg.arg, 523 | **lines(kwarg), 524 | ) 525 | ) 526 | 527 | defaults = [i.value for i in kwargs] 528 | inner_body = list(self._normalize_block(body)) 529 | inner_defaults = [] 530 | for arg in args: 531 | inner_defaults.append( 532 | ast.Name( 533 | id=arg.arg, 534 | ctx=ast.Load(), 535 | **lines(meta), 536 | ) 537 | ) 538 | 539 | body = [ 540 | ast.FunctionDef( 541 | name=name.value + "_inner", 542 | decorator_list=[], 543 | body=inner_body, 544 | args=ast.arguments( 545 | args=[ 546 | ast.arg( 547 | "module_children", 548 | **lines(meta), 549 | ), 550 | *args, 551 | ], 552 | posonlyargs=[], 553 | kwonlyargs=[], 554 | kw_defaults=[], 555 | defaults=inner_defaults, 556 | ), 557 | **lines(meta), 558 | ), 559 | ast.Return( 560 | value=ast.Name( 561 | name.value + "_inner", 562 | ctx=ast.Load(), 563 | **lines(meta), 564 | ), 565 | **lines(meta), 566 | ), 567 | ] 568 | yield ast.FunctionDef( 569 | name="module_" + name.value, 570 | decorator_list=[], 571 | body=body, 572 | # returns=ast.Name("Geometry", 573 | # ctx=ast.Load(), 574 | # **lines(meta), 575 | # ), 576 | args=ast.arguments( 577 | args=args, 578 | posonlyargs=[], 579 | kwonlyargs=[], 580 | kw_defaults=[], 581 | defaults=defaults, 582 | ), 583 | **lines(meta), 584 | ) 585 | 586 | def function_def(self, meta, name, args, body): 587 | args, kwargs = args 588 | for kwarg in kwargs: 589 | args.append( 590 | ast.arg( 591 | kwarg.arg, 592 | **lines(kwarg), 593 | ) 594 | ) 595 | 596 | defaults = [i.value for i in kwargs] 597 | yield ast.FunctionDef( 598 | name="function_" + name.value, 599 | decorator_list=[], 600 | body=[ 601 | ast.Return( 602 | value=body, 603 | **lines(meta), 604 | ) 605 | ], 606 | args=ast.arguments( 607 | args=args, 608 | posonlyargs=[], 609 | kwonlyargs=[], 610 | kw_defaults=[], 611 | defaults=defaults, 612 | ), 613 | **lines(meta), 614 | ) 615 | 616 | def vector(self, meta, args): 617 | return ast.List( 618 | list(args), 619 | ctx=ast.Load(), 620 | **lines(meta), 621 | ) 622 | 623 | @v_args(meta=False, inline=False) 624 | def __default__(self, data, children, meta): 625 | raise 626 | 627 | 628 | parser = Lark.open("openscad.lark", rel_to=__file__, propagate_positions=True) 629 | example_text = """ 630 | """ 631 | 632 | 633 | from loguru import logger # type: ignore 634 | 635 | 636 | @logger.catch 637 | def main(): 638 | 639 | example_py = """ 640 | foo = (1+1)/2 641 | """ 642 | if example_py: 643 | print("====Python AST=====") 644 | result = ast.parse(example_py) 645 | print("-------------------") 646 | 647 | tree = parser.parse(example_text) 648 | result = OpenscadToPy().transform(tree) 649 | print("====AST=====") 650 | print(result) 651 | print("=====generated_code=====") 652 | 653 | from pygments import highlight 654 | from pygments.lexers import PythonLexer 655 | from pygments.formatters import TerminalTrueColorFormatter 656 | 657 | print(highlight(source, PythonLexer(), TerminalTrueColorFormatter())) 658 | print("-----------") 659 | print("===Running generated code===") 660 | scad_locals = {} 661 | exec( 662 | compile( 663 | result, 664 | filename="", 665 | mode="exec", 666 | ), 667 | scad_locals, 668 | ) 669 | print(scad_locals["main"]()) 670 | print(*scad_locals["main"]()) 671 | 672 | 673 | if __name__ == "__main__": 674 | main() 675 | --------------------------------------------------------------------------------