├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── examples ├── bearing.ko ├── blend.ko ├── box.ko ├── fab_ISP.ko ├── gear.ko ├── gik.ko ├── hinge.ko ├── mandala.ko ├── mtm_az.ko ├── text.ko ├── tippy.ko └── trinket.ko ├── koko ├── __init__.py ├── about.py ├── app.py ├── c │ ├── __init__.py │ ├── asdf.py │ ├── interval.py │ ├── libfab.py │ ├── mesh.py │ ├── multithread.py │ ├── path.py │ ├── region.py │ └── vec3f.py ├── cam │ ├── __init__.py │ ├── inputs │ │ ├── __init__.py │ │ ├── asdf.py │ │ ├── cad.py │ │ └── image.py │ ├── machines │ │ ├── __init__.py │ │ ├── epilog.py │ │ ├── gcode.py │ │ ├── modela.py │ │ ├── null.py │ │ ├── shopbot.py │ │ ├── shopbot5.py │ │ └── universal.py │ ├── panel.py │ ├── path_panels.py │ └── workflow.py ├── canvas.py ├── dialogs.py ├── editor.py ├── export.py ├── fab │ ├── __init__.py │ ├── asdf.py │ ├── fabvars.py │ ├── image.py │ ├── mesh.py │ ├── path.py │ └── tree.py ├── frame.py ├── glcanvas.py ├── lib │ ├── __init__.py │ ├── pcb.py │ ├── shapes.py │ ├── shapes2d.py │ ├── shapes3d.py │ └── text.py ├── prims │ ├── __init__.py │ ├── core.py │ ├── editpanel.py │ ├── evaluator.py │ ├── lines.py │ ├── menu.py │ ├── points.py │ └── utils.py ├── render.py ├── struct.py ├── taskbot.py ├── template.py ├── themes.py └── vol.py ├── kokopelli ├── libfab ├── CMakeLists.txt ├── asdf │ ├── asdf.c │ ├── asdf.h │ ├── cache.c │ ├── cache.h │ ├── cms.c │ ├── cms.h │ ├── contour.c │ ├── contour.h │ ├── distance.c │ ├── distance.h │ ├── file_io.c │ ├── file_io.h │ ├── import.c │ ├── import.h │ ├── neighbors.c │ ├── neighbors.h │ ├── render.c │ ├── render.h │ ├── triangulate.c │ └── triangulate.h ├── cam │ ├── distance.c │ ├── distance.h │ ├── slices.c │ ├── slices.h │ ├── toolpath.c │ └── toolpath.h ├── formats │ ├── mesh.c │ ├── mesh.h │ ├── png_image.c │ ├── png_image.h │ ├── stl.c │ └── stl.h ├── tree │ ├── eval.c │ ├── eval.h │ ├── math │ │ ├── math_f.c │ │ ├── math_f.h │ │ ├── math_i.c │ │ ├── math_i.h │ │ ├── math_r.c │ │ └── math_r.h │ ├── node │ │ ├── node.c │ │ ├── node.h │ │ ├── opcodes.c │ │ ├── opcodes.h │ │ ├── printers.c │ │ ├── printers.h │ │ ├── results.c │ │ └── results.h │ ├── packed.c │ ├── packed.h │ ├── parser.c │ ├── parser.h │ ├── render.c │ ├── render.h │ ├── tree.c │ └── tree.h └── util │ ├── constants.h │ ├── interval.h │ ├── macros.h │ ├── path.c │ ├── path.h │ ├── region.c │ ├── region.h │ ├── squares.h │ ├── switches.h │ ├── vec3f.c │ └── vec3f.h └── util ├── app ├── .gitignore ├── README ├── make_app.py └── make_icon.py ├── doxygen ├── Makefile ├── koko │ ├── Doxyfile │ └── doxypy.py └── libfab │ └── Doxyfile └── install_wxpython3.0.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.DS_Store 3 | *.tags 4 | 5 | libfab/libfab.dylib 6 | libfab/libfab.so 7 | build/* 8 | util/app/ko.icns 9 | util/app/kokopelli.app 10 | util/app/kokopelli.zip 11 | 12 | util/doxygen/*/html/* 13 | util/doxygen/*/latex/* 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | This work is available under two different licensing options: 5 | 6 | 7 | The [CBA](https://cba.mit.edu) License 8 | -------------------------------------- 9 | (c) 2012-2013 Massachusetts Institute of Technology 10 | (c) 2013 Matt Keeter 11 | 12 | This work may be reproduced, modified, distributed, performed, and displayed for any purpose, but must acknowledge the `kokopelli` project. Copyright is retained and must be preserved. The work is provided as is; no warranty is provided, and users accept all liability. 13 | 14 | 15 | The MIT License 16 | --------------- 17 | (c) 2012-2013 Massachusetts Institute of Technology 18 | (c) 2013 Matt Keeter 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in 28 | all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | THE SOFTWARE. 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p build 3 | cd build && cmake ../libfab && make && make install 4 | 5 | install: all 6 | cp -rf kokopelli koko /usr/local/bin/ 7 | cp -rf libfab/libfab.* /usr/local/lib/ 8 | if which ldconfig; then ldconfig; fi 9 | 10 | clean: 11 | rm -rf build 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `kokopelli` is deprecated 2 | ========================= 3 | Its successors are [Antimony](https://github.com/mkeeter/antimony) and [Ao](https://github.com/mkeeter/ao). 4 | 5 | -------------------------------------------------------------------------------------------------------- 6 | 7 | About 8 | ===== 9 | `kokopelli` is an open-source tool for computer-aided design and manufacturing (CAD/CAM). 10 | 11 | It uses Python as a hardware description language for solid models. A set of core libraries define common shapes and transforms, but users are free to extend their designs with their own definitions. 12 | 13 | ![CAD](http://i.imgur.com/L1RQUxA.png) 14 | 15 | The CAM tools enable path planning for two, three, and five-axis machines. At the moment, paths can be exported to Universal and Epilog laser cutters, the Roland Modela mini-mill, three and five-axis Shopbot machines, and plain G-code. A modular workflow system makes adding new machines easy. 16 | 17 | ![CAM](http://i.imgur.com/sb0uQq5.png) 18 | 19 | In addition, models can be saved as `.svg` and water-tight `.stl` files. 20 | 21 | Warning 22 | ======= 23 | `kokopelli` stores designs as Python scripts and executes them. This means that you can do cool things like using `numpy` to process arrays, load and process images with `PIL`, or even scrape web data and use it to inform designs. 24 | 25 | However, it also means that bad actors can write malicious scripts. 26 | 27 | As such, **do not open a .ko file from an untrusted source** without first examining it in a text editor to confirm that it is not malicious. 28 | 29 | Download 30 | ======== 31 | `kokopelli` has been tested on Mac OS 10.6+ and Ubuntu 12.04 LTS. 32 | A Mac application is available [here](http://mattkeeter.com/projects/kokopelli/kokopelli.tar.gz). 33 | To build from source, check out the instructions on the [wiki](https://github.com/mkeeter/kokopelli/wiki/Installing). 34 | 35 | Background 36 | ========== 37 | `kokopelli` grew out of the MIT course ["How to Make Something that Makes (Almost) Anything"](http://fab.cba.mit.edu/classes/S62.12/index.html). 38 | In that course, I worked on [fast geometry solvers](http://fab.cba.mit.edu/classes/S62.12/people/keeter.matt/solver/index.html) and developed a [fairly basic UI](http://fab.cba.mit.edu/classes/S62.12/people/keeter.matt/gui/index.html). My work expanded on the [fab modules](http://kokompe.cba.mit.edu/) project, which allows [fab lab](http://fab.cba.mit.edu/about/faq/) users to make physical artifacts on a variety of machines. 39 | 40 | This work grew into my [Master's thesis](http://cba.mit.edu/docs/theses/13.05.Keeter.pdf) at the MIT [Center for Bits and Atoms](http://cba.mit.edu). This thesis focused on volumetric CAD/CAM workflows. Now that it is complete, I'm releasing this tool for others to use and develop. It has already been used by folks in [How to Make (Almost) Anything](http://fab.cba.mit.edu/classes/863.12/) and [Fab Academy](http://www.fabacademy.org/), but I'm excited to offer it to a larger community. 41 | 42 | Copyright 43 | ========= 44 | (c) 2012-2013 Massachusetts Institute of Technology 45 | (c) 2013 Matt Keeter 46 | 47 | -------------------------------------------------------------------------------- /examples/bearing.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/bearing.ko 3 | 4 | A simple bearing model. Demonstrates multi-part objects: 5 | The balls and the bearing are two distinct models, which prevents 6 | them from being merged during triangulation. 7 | 8 | Based on the original bearing.cad: 9 | http://academy.cba.mit.edu/classes/scanning_printing/bearing.cad 10 | [Amy Sun & Neil Gershenfeld, 12/23/06] 11 | """ 12 | 13 | 14 | from koko.lib.shapes import * 15 | from math import sin, cos, pi 16 | 17 | ################################################################################ 18 | 19 | height = 0.3 # bearing height 20 | wall = 0.1 # wall thickness 21 | OD = 1.0 # outer diameter 22 | ID = 0.25 # inner diameter 23 | BD = 0.25 # bearing ball diameter 24 | Nb = 6 # Number of balls in bearing 25 | 26 | ################################################################################ 27 | 28 | # Make a 2D cross section, then revolve it to create the bearing's shape 29 | 30 | base = ( 31 | rectangle(ID/2, OD/2, -height/2, height/2) - 32 | rectangle(ID/2 + wall, OD/2 - wall, -height, height) 33 | ) 34 | 35 | R_r = (OD + ID) / 4 36 | race = rectangle(R_r - BD/2., R_r + BD/2., -height/6., height/6.) 37 | bearing = rotate_x(revolve_y(base - race), 90) 38 | 39 | 40 | ################################################################################ 41 | 42 | balls = [ 43 | sphere(cos(i*2*pi/Nb)*R_r, sin(i*2*pi/Nb)*R_r, 0, BD/2.) 44 | for i in range(Nb) 45 | ] 46 | 47 | ################################################################################ 48 | 49 | cutout = cube(0, 2, -2, 0, -2, 2) 50 | cad.shapes = balls + [bearing] 51 | cad.shapes = [s - cutout for s in cad.shapes] 52 | -------------------------------------------------------------------------------- /examples/blend.ko: -------------------------------------------------------------------------------- 1 | from koko.lib.shapes import * 2 | from math import sqrt 3 | 4 | cad.shapes = [ 5 | blend(sphere(i, 0, 0, 0.5), 6 | cube(i,i+1,0, 1,0,0.5), sqrt(i)*0.25) 7 | for i in range(0,10,2)] 8 | -------------------------------------------------------------------------------- /examples/box.ko: -------------------------------------------------------------------------------- 1 | ## Geometry header ## 2 | [(koko.prims.utils.Slider, {'name': 'width', 'min': '3', 'max': '6', 'value': '5', 'y': '1.97375873717', 'x': '-4.91360555318', 'size': '4'}),(koko.prims.utils.Slider, {'name': 'height', 'min': '1', 'max': '3', 'value': '1.75', 'y': '0.884683022512', 'x': '-4.81565072685', 'size': '4'}),(koko.prims.utils.Slider, {'name': 'depth', 'min': '2', 'max': '4', 'value': '3', 'y': '-0.329360592173', 'x': '-4.91794326139', 'size': '4'}),(koko.prims.utils.Slider, {'name': 'thickness', 'min': '0.1', 'max': '0.3', 'value': '0.23', 'y': '-1.36551208642', 'x': '-4.97365963174', 'size': '4.0'}),(koko.prims.points.Point, {'y': '(0)', 'x': '-1', 'name': 'divider'})] 3 | ## End of geometry header ## 4 | """ 5 | examples/box.ko 6 | 7 | A simple press-fit jewelry box that uses UI sliders to parameterize design 8 | (sliders are shown when looking at cut sheet). Change the 'mode' variable 9 | to switch between a cut sheet and an assembled view. 10 | 11 | In the cut sheet, parts are positioned relative to their bounds, which are 12 | automatically tracked and propagated. 13 | """ 14 | 15 | from koko.lib.shapes2d import * 16 | from koko.lib.shapes3d import * 17 | 18 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 19 | 20 | # Change the selected mode to switch from 2D to 3D 21 | mode = 'cut sheet' 22 | #mode = 'assembled' 23 | 24 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 25 | 26 | # Box settings 27 | width = width.value #inches 28 | height = height.value #inches 29 | depth = depth.value #inches 30 | thickness = thickness.value #inches 31 | tabsize = 0.5 #inches 32 | 33 | dividerloc = 0.3 # percent 34 | dividerheight = 0.7 # percent 35 | 36 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 37 | 38 | dividerloc = divider.x 39 | dividerheight = (height-thickness*2)*dividerheight 40 | 41 | base = ( 42 | rectangle(-width/2, width/2, -depth/2, depth/2) - 43 | rectangle(-tabsize/2, tabsize/2, depth/2-thickness, depth/2) - 44 | rectangle(width/2-thickness, width/2, -tabsize/2, tabsize/2) - 45 | rectangle(-tabsize/2, tabsize/2, -depth/2, -depth/2+thickness) - 46 | rectangle(-width/2, -width/2+thickness, -tabsize/2, tabsize/2) 47 | ) 48 | base -= rectangle(dividerloc-thickness/2, dividerloc+thickness/2, -tabsize/2, tabsize/2) 49 | 50 | inset1 = ( 51 | rectangle(thickness, dividerheight+thickness, -depth/2+thickness, depth/2-thickness) + 52 | tab(thickness, 0, tabsize, thickness, 90) - 53 | rectangle(thickness+dividerheight/2., thickness+dividerheight, -thickness/2, thickness/2) 54 | ) 55 | inset2 = ( 56 | rectangle(-width/2+thickness, dividerloc+thickness/2, 0, dividerheight) - 57 | rectangle(dividerloc-thickness/2, dividerloc+thickness/2, 0, dividerheight/2) 58 | ) 59 | 60 | short_side = ( 61 | rectangle(thickness, height, -depth/2+thickness, depth/2-thickness) - 62 | rectangle(height-thickness, height, -depth/2+tabsize, depth/2-tabsize) + 63 | tab(thickness, 0, tabsize, thickness, 90) + 64 | tab(height/2, -depth/2+thickness, tabsize, thickness, 180) + 65 | tab(height/2, depth/2-thickness, tabsize, thickness, 0) 66 | ) 67 | long_side = ( 68 | rectangle(-width/2, width/2, thickness, height) - 69 | rectangle(-width/2+tabsize, width/2-tabsize, height-thickness, height) - 70 | rectangle(-width/2, -width/2+thickness, height/2-tabsize/2, height/2+tabsize/2) - 71 | rectangle(width/2-thickness, width/2, height/2-tabsize/2, height/2+tabsize/2) + 72 | tab(0, thickness, tabsize, thickness, 180) 73 | ) 74 | 75 | top = ( 76 | rectangle(-width/2, width/2, -depth/2, depth/2) 77 | ) 78 | corner = ( 79 | rectangle(-width/2, -width/2+thickness, -depth/2, -depth/2+tabsize) + 80 | rectangle(-width/2, -width/2+tabsize, -depth/2, -depth/2+thickness) 81 | ) 82 | top -= (corner + reflect_x(corner) + reflect_y(corner) + reflect_x(reflect_y(corner))) 83 | 84 | 85 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 86 | 87 | if mode == 'assembled': 88 | base = extrusion(base, -thickness, 0) 89 | short_side = rotate_y(extrusion(short_side, -thickness/2, thickness/2), 90) 90 | short_side = ( 91 | move(short_side, width/2-thickness/2, 0, -thickness) + 92 | move(short_side, -width/2+thickness/2, 0, -thickness) 93 | ) 94 | long_side = rotate_x(extrusion(long_side, -thickness/2, thickness/2), 90) 95 | long_side = ( 96 | move(long_side, 0, depth/2-thickness/2, -thickness) + 97 | move(long_side, 0, -depth/2+thickness/2, -thickness) 98 | ) 99 | top = extrusion(top, 0, thickness) 100 | top = move(top, 0, 0, height + 2*thickness) 101 | 102 | inset1 = extrusion(inset1, -thickness/2, thickness/2) 103 | inset1 = move(rotate_y(inset1, 90), dividerloc, 0, -thickness) 104 | 105 | inset2 = extrusion(inset2, -thickness/2, thickness/2) 106 | inset2 = rotate_x(inset2, 90) 107 | cad.render_mode = '3D' 108 | 109 | elif mode == 'cut sheet': 110 | gap = 0.2 111 | short_side = move(short_side, gap + base.xmax - short_side.xmin, 0) 112 | short_side += move(short_side, gap + short_side.xmax - short_side.xmin - thickness, 0) 113 | long_side = move(long_side, gap + base.xmax - long_side.xmin, gap + base.ymax - long_side.ymin) 114 | long_side += move(long_side, 0, gap + long_side.ymax - long_side.ymin - thickness) 115 | top = move(top, 0, base.ymax - top.ymin + gap) 116 | inset1 = move(inset1, gap + short_side.xmax - inset1.xmin - thickness, 0) 117 | inset2 = rotate(inset2, 90) 118 | inset2 = move(inset2, inset1.xmax - inset2.xmin + gap, inset1.ymin - inset2.ymin) 119 | cad.render_mode = '2D' 120 | 121 | cad.shapes = ( 122 | color(inset1,'cyan'), color(inset2, 'magenta'), 123 | color(long_side, 'blue'), 124 | color(short_side,'green'), 125 | color(base, 'yellow'), 126 | color(top, 'red'), 127 | ) 128 | -------------------------------------------------------------------------------- /examples/fab_ISP.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/fab_ISP.ko 3 | 4 | The fab in-circuit programmer, demonstrating kokopelli's PCB 5 | design library. 6 | 7 | Based on hello.ISP.44.cad: 8 | http://academy.cba.mit.edu/classes/embedded_programming/hello.ISP.44.cad 9 | [Neil Gershenfeld 2/10/11, based on FabISP designed by David Mellis] 10 | """ 11 | 12 | from koko.lib.pcb import * 13 | 14 | # Initialize the circuit board with xy position, width, and height 15 | pcb = PCB(0, 0, 0.805, 1.68) 16 | 17 | # We use an ATtiny44 as the brains of the programmer 18 | IC1 = ATtiny44_SOIC(0.51, 0.67, 0, 'IC1\nt44') 19 | pcb += IC1 20 | 21 | # Add an ISP header to program the programmer 22 | J1 = Header_ISP(IC1.x + 0.05, IC1[7].y - 0.24, 90, 'SPI') 23 | pcb += J1 24 | 25 | # Connect the in-circuit programming header to the chip 26 | pcb.connectH(IC1['PA4'], J1['SCK']) 27 | pcb.connectH(IC1['PA5'], J1['MISO']) 28 | pcb.connectH(IC1['PA6'], (IC1[7].x, J1.y), J1['MOSI']) 29 | pcb.connectH(IC1['PA3'], (J1['RST'].x+0.05, J1['RST'].y), J1['RST']) 30 | 31 | # Create a solder jumper and connect it to RST 32 | SJ1 = SJ(IC1['PA6'].x - 0.07, J1['RST'].y, 90, 'SJ1') 33 | pcb += SJ1 34 | pcb.connectV( 35 | J1['RST'], 36 | (J1['SCK'].x + 0.05, J1['SCK'].y - 0.08), 37 | (J1['MOSI'].x - 0.05, J1['MOSI'].y - 0.08), 38 | (SJ1[1].x, J1['MOSI'].y+0.08), 39 | SJ1[1]) 40 | pcb.connectH(IC1['PB3'], SJ1[0]) 41 | 42 | # Add the USB port 43 | J2 = USB_mini_B(IC1.x-0.02, pcb.height - 0.04, 180, 'J2 USB') 44 | pcb += J2 45 | 46 | R1 = R_1206(J2[5].x + 0.09, J2[5].y - 0.1, 0, "R1\n1k") 47 | pcb += R1 48 | pcb.connectH(R1[1], J2[4]) 49 | 50 | R2 = R_1206(R1[2].x, R1.y - 0.143, 90, "R2\n499") 51 | pcb += R2 52 | pcb.connectV(R1[2], R2[2]) 53 | pcb.connectH(R2[1], (R2.x+0.075, R1.y+0.06), J2['V']) 54 | 55 | SJ2 = R_1206(R2.x - 0.015, R2.y - 0.2, 90, "SJ2") 56 | pcb += SJ2 57 | pcb.connectH(J1['V'], (SJ2.x+0.02, SJ2[1].y), SJ2[1]) 58 | pcb.connectH(SJ2[2], R2[1]) 59 | pcb.connectV( 60 | (IC1['GND'].x-0.01, IC1['GND'].y), 61 | (J1['MISO'].x+0.085, SJ2.y), 62 | (J1['GND'].x, J1['GND'].y-0.11), 63 | J1['GND']) 64 | 65 | # Add a pair of diodes to limit voltages 66 | D1 = D_SOD_123(J2['+'].x, R2[2].y, 0, "D1\n3.3V") 67 | pcb += D1 68 | pcb.connectV(J2['G'], D1[1]) 69 | pcb.connectV(R1[1], D1[2]) 70 | D2 = D_SOD_123(D1[1].x, D1.y - 0.07, 0, "\nD2 3.3V") 71 | pcb += D2 72 | 73 | # Connect the diode anodes 74 | pcb.connectH(D2[1], D1[1]) 75 | pcb.connectH(D2[2], J2['+']) 76 | 77 | R3 = R_1206(D2.x+0.04, D2.y-0.15, 90, 'R3\n100') 78 | pcb += R3 79 | pcb.connectH(IC1['PB2'], R3[1]) 80 | pcb.connectH(IC1['PA7'], R3[1]) 81 | pcb.connectH(R3[2], D2[2]) 82 | 83 | R4 = R_1206(D1[2].x-0.01, R3.y, 90, 'R4\n100') 84 | pcb += R4 85 | pcb.connectH(IC1['PA0'], R4[1]) 86 | pcb.connectH(R4[2], D1[2]) 87 | 88 | # Ground the diodes 89 | pcb.connectH(D2[1], (D2[1].x, R3.y), (IC1['GND'].x-0.01, IC1['GND'].y)) 90 | 91 | R5 = R_1206(SJ1.x-0.07, J1['GND'].y, 90, 'R5\n10k') 92 | pcb += R5 93 | pcb.connectH(R2[1], (R2.x+0.075, R5[1].y-0.085), R5[1]) 94 | pcb.connectH(SJ1[2], R5[2]) 95 | 96 | C1 = C_1206(R5.x-0.1, R5.y, 90, "C1\n1uF") 97 | pcb += C1 98 | pcb.connectH((R5.x, R5[1].y-0.085), C1[1]) 99 | pcb.connectH(J1['GND'], C1[2]) 100 | 101 | # Add the clock crystal 102 | XTAL1 = XTAL_NX5032GA(IC1.x-0.27, IC1['PB0'].y-0.025, 90, '20\nMHz') 103 | pcb += XTAL1 104 | pcb.connectH(IC1['PB0'], XTAL1[2]) 105 | pcb.connectH(IC1['PB1'], XTAL1[1]) 106 | # and loading capacitors for the clock crystal 107 | C2 = C_1206(XTAL1.x-0.12, XTAL1.y+0.12, 90, 'C2\n10pF') 108 | pcb += C2 109 | pcb.connectV(XTAL1[2], C2[2]) 110 | C3 = C_1206(XTAL1.x-0.12, XTAL1.y-0.12, 90, 'C3\n10pF') 111 | pcb += C3 112 | pcb.connectV(XTAL1[1], C3[1]) 113 | pcb.connectV(C2[1], C3[2]) 114 | # Connect the loading caps to ground 115 | pcb.connectH( 116 | (C1.x, C1.y), 117 | (C2.x-0.06, XTAL1.y), 118 | (C2.x, XTAL1.y)) 119 | 120 | # Loop VCC around to connect to IC1 121 | pcb.connectH( 122 | (C1.x, C1[1].y-0.085), 123 | (C2.x-0.095, R3.y), 124 | (D2[1].x-0.05, R3[1].y), 125 | IC1['VCC']) 126 | 127 | # Add the CBA logo 128 | pcb += CBA(0.15, pcb.height-0.16) 129 | 130 | 131 | # Select one of these options: 132 | # Layout shows parts with labels and colors 133 | # Traces is simply the black and white trace cutout 134 | # Cutout is the board outline 135 | 136 | cad.shapes = pcb.layout 137 | #cad.shape = pcb.traces 138 | #cad.shape = pcb.cutout 139 | 140 | -------------------------------------------------------------------------------- /examples/gear.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/gear.ko 3 | 4 | This example creates an involute gear with user-defined 5 | pressure angle, diametral pitch, and number of teeth. 6 | 7 | It works directly with MathTree expressions to create the 8 | involute curve profile, then uses higher-level operations 9 | to create the teeth and base. 10 | 11 | The involute gear construction is based on the tutorial at 12 | http://www.cartertools.com/involute.html 13 | """ 14 | 15 | from math import sqrt, atan2, sin, cos, pi, radians 16 | import operator 17 | 18 | from koko.lib.shapes import * 19 | 20 | P = 14. # diametral pitch 21 | PA = 20 # pressure angle 22 | N = 32 # number of teeth 23 | 24 | R = N/P/2 # pitch radius 25 | RB = R*cos(radians(PA)) # base circle radius 26 | a = 1/P # addendum 27 | d = 1.157/P # dedendum 28 | RO = R+a # outer radius 29 | RR = R-d # root radius 30 | 31 | 32 | # MathTree expressions for r and r**2 33 | r_2 = MathTree.square(X) + MathTree.square(Y) 34 | r = MathTree.sqrt(r_2) 35 | 36 | # See note (1) for derivation of tooth rotation 37 | t = sqrt(R**2/RB**2 -1) 38 | rot = atan2(sin(t)-t*cos(t), cos(t)+t*sin(t)) + pi/2/N 39 | 40 | # See note (2) for derivation of involute curve 41 | tooth = ( 42 | MathTree.sqrt(r_2-RB**2) - RB*(MathTree.atan(Y/X) + 43 | MathTree.acos(RB/r)) - RB*rot 44 | ) 45 | 46 | tooth.shape = True 47 | tooth &= reflect_y(tooth) 48 | 49 | # If we have an odd number of teeth, then we can't take 50 | # advantage of bilateral tooth symmetry. 51 | if N % 2: 52 | tooth &= -X 53 | teeth = reduce(operator.add, [rotate(tooth, i*360./N) 54 | for i in range(N)]) 55 | else: 56 | teeth = reduce(operator.add, [rotate(tooth, i*360./N) 57 | for i in range(N/2)]) 58 | 59 | teeth += circle(0, 0, RR) 60 | teeth &= circle(0, 0, RO) - circle(0, 0, RR*0.9) 61 | teeth.bounds = circle(0, 0, RO).bounds 62 | teeth = extrusion(teeth, -0.1, 0.1) 63 | 64 | teeth.color = 'red' 65 | 66 | # Create a set of six ribs inside the gear 67 | ribs = rectangle(-0.002*N, 0.002*N, -RR*0.95, RR*0.95) 68 | ribs = reduce(operator.add, [rotate(ribs, i*120) for i in range(3)]) 69 | ribs += circle(0, 0, 0.4) 70 | ribs -= circle(0,0,0.25) 71 | ribs -= rectangle(-0.06, 0.06, 0, 0.3) 72 | ribs = extrusion(ribs, -0.08, 0.08) 73 | ribs.color = 'green' 74 | 75 | # Create a base for the gear 76 | base = circle(0, 0, RR*0.95) - circle(0, 0, 0.35) 77 | base -= rectangle(-0.06, 0.06, 0, 0.3) 78 | base = extrusion(base, -0.04, 0.04) 79 | base.color = 'blue' 80 | 81 | cad.shapes = teeth, ribs, base 82 | 83 | """ 84 | Notes: 85 | 86 | (1) 87 | We want to find the angle such that the involute curve 88 | intersects a circle of radius R, where the involute is being 89 | unwound from a circle of radius RB (and RB < R) 90 | 91 | The involute has coordinates 92 | x, y = RB*(cos(t)+t*sin(t)), RB*(sin(t)-t*cos(t)) 93 | where t is a parameter. 94 | 95 | Solving x**2 + y**2 = R**2 gives 96 | t = sqrt(R**2/RB**2 -1) 97 | which we can plug back in to find x(t) and y(t). 98 | 99 | We take atan2(y(t), x(t)) to find the angle, then add another 100 | (2*pi)/(4*N) so that we can mirror the tooth about the x axis. 101 | 102 | 103 | (2) 104 | [Clever math & explanation by Peter Fedak, HMC 2013] 105 | 106 | Assuming that restricting to x and y greater than 107 | R is not interesting/challenging, an expression 108 | separating one side of a portion of a circle involute 109 | from another is 110 | 111 | R(atan(y/x) + acos( R/sqrt(x^2+y^2) )) - sqrt(x^2+y^2-R^2) 112 | 113 | which is 0 on the curve and negative for points to the northeast. 114 | This assumes the involute starts at (1,0), if you want to rotate it, 115 | or deal with a different turn of the involute, subtract R*rotation 116 | angle. 117 | 118 | For points P=(x,y) in the first quadrant, atan(y/x) accurately gives 119 | the angle between the x-axis and the ray from O=(0,0) to P. Assuming 120 | we are "unwinding" the involute counterclockwise, the place where the 121 | "string" meets the circle, Q, will be the more-counterclockwise 122 | tangent from P to the circle. Then O,P,Q is a right triangle, and the 123 | length of the string, PQ, is sqrt(x^2+y^2-R^2) (right angle between 124 | the tangent and the radius OQ). 125 | 126 | The angle of OQ from the x-axis is, again restricted to P in the first 127 | quadrant, the sum of the angle of OP from the x-axis and one of the 128 | angles in teh triangle. the cosine of the relevant angle is 129 | R/sqrt(x^2+y^2), and as this angle will always be smaller than a right 130 | angle, can by given by the inverse cosine of this ratio directly. The 131 | condition for a point to lie on the involute is for the length of the 132 | "string" to be equal to the amount unwound. The amount unwound is the 133 | angle from OQ to the x-axis times R, which is the first term in the 134 | expression. The length of the string is the expression after the minus 135 | sign. Thus the involute is where this F=0, whereas if the point is too 136 | close to the origin, the expression will be positive. 137 | """ 138 | -------------------------------------------------------------------------------- /examples/gik.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/gik.ko 3 | 4 | This example creates the Gershenfeld Invention Kit, 5 | a press-fit kit of parts intended to be made out of 6 | cardboard on a laser cutter. 7 | 8 | Based on the original gik.cad: 9 | http://academy.cba.mit.edu/classes/computer_cutting/gik.cad 10 | [Neil Gershenfeld, 8/1/07] 11 | 12 | Major modifications include moving from functions to overloaded 13 | operators, switching to new libraries, and using loops to duplicate 14 | parts (rather than doing so by hand). 15 | """ 16 | 17 | from koko.lib.shapes import * 18 | from math import sqrt 19 | 20 | ################################################################################ 21 | 22 | # Design parameters 23 | 24 | w = 0.13 # slot width 25 | l = 0.3 # slot length 26 | c = 0.03 # champher length 27 | s = .1 # part space 28 | 29 | ################################################################################ 30 | 31 | # We'll accumulate shapes in the 'gik' variable 32 | gik = None 33 | 34 | ################################################################################ 35 | 36 | x0 = 0 37 | 38 | slot = rectangle(0, l/2, -w/2, w/2) 39 | slot += right_triangle(0, w/2, c) 40 | slot += reflect_y(right_triangle(0, w/2, c)) 41 | 42 | double_slot = rectangle(0, l+w/2, -w/2, w/2) 43 | double_slot += right_triangle(0, w/2, c) 44 | double_slot += reflect_y(right_triangle(0, w/2, c), 0) 45 | 46 | ################################################################################ 47 | 48 | square_gik = rectangle(-l-w/2, l+w/2, -l-w/2, l+w/2) 49 | square_gik -= move(slot, -l-w/2, 0) 50 | square_gik -= move(reflect_x(slot, 0), l+w/2, 0) 51 | square_gik -= move(reflect_xy(slot), 0, -l-w/2) 52 | square_gik -= move(reflect_xy(reflect_x(slot, 0)), 0, l+w/2) 53 | 54 | dxy = 2*l+w+s 55 | for i in range(2): 56 | for j in range(4): 57 | gik += move(square_gik, i*dxy, j*dxy) 58 | 59 | ################################################################################ 60 | 61 | pi_gik = rectangle(-l-w/2, l+w/2, -l-w/2, l+w/2) 62 | pi_gik -= move(slot, -l-w/2, 0) 63 | pi_gik -= move(reflect_x(slot, 0), l+w/2, 0) 64 | pi_gik -= move(reflect_xy(double_slot), 0, -l-w/2) 65 | 66 | dxy = 2*l + w + s 67 | x0 += 2*dxy 68 | 69 | for j in range(4): 70 | gik += move(pi_gik, x0, j*dxy) 71 | 72 | ################################################################################ 73 | 74 | tee_gik = rectangle(-l-w/2, l+w/2, -l-w/2, 3*w/2) 75 | tee_gik -= move(slot, -l-w/2, 0) 76 | tee_gik -= move(reflect_x(slot, 0), l+w/2, 0) 77 | tee_gik -= move(reflect_xy(slot), 0, -l-w/2) 78 | 79 | dx = 2*l+w+s 80 | dy = l + s + 2*w 81 | 82 | x0 += dx 83 | for i in range(5): 84 | gik += move(tee_gik, 3*dxy, i*dy) 85 | x0 += dx 86 | 87 | ################################################################################ 88 | 89 | corner_gik = rectangle(-l-w/2, 3*w/2, -l-w/2, 3*w/2) 90 | corner_gik -= move(slot, -l-w/2, 0) 91 | corner_gik -= move(reflect_xy(slot), 0, -l-w/2) 92 | 93 | dx = l + 2*w + s 94 | dy = l + s + 2*w 95 | 96 | for i in range(2): 97 | for j in range(5): 98 | gik += move(corner_gik, x0 + dx*i, j*dy) 99 | x0 += 2*dx 100 | 101 | ################################################################################ 102 | 103 | connector_gik = rectangle(-l, l, -3*w/2, 3*w/2) 104 | connector_gik -= move(slot, -l, 0) 105 | connector_gik -= move(reflect_x(slot, 0), l, 0) 106 | 107 | dy = 3*w+s 108 | for j in range(6): 109 | gik += move(connector_gik, x0, j*dy) 110 | x0 += 2*l + s 111 | 112 | ################################################################################ 113 | 114 | r = l + w/(2*sqrt(3)) 115 | round_gik = circle(0, 0, r) 116 | round_gik = round_gik - move(slot, -r, 0) 117 | round_gik = rotate(round_gik, 120) - move(slot, -r, 0) 118 | round_gik = rotate(round_gik, 120) - move(slot, -r, 0) 119 | 120 | round_gik_row = None 121 | x0 += s 122 | for j in range(3): 123 | round_gik_row += move(round_gik, x0, j*(2*r+4*s)) 124 | 125 | gik += round_gik_row 126 | 127 | ################################################################################ 128 | 129 | d = sqrt(3)*r 130 | h = sqrt(3)*d 131 | triangle_gik = triangle(-d, -r, 0, h-r, d, -r) 132 | triangle_gik -= move(reflect_xy(slot), 0, -r) 133 | triangle_gik = rotate(triangle_gik, 120) - move(reflect_xy(slot), 0, -r) 134 | triangle_gik = rotate(triangle_gik, 120) - move(reflect_xy(slot), 0, -r) 135 | inverted_triangle_gik = move(reflect_y(triangle_gik, 0), 0, h-2*r) 136 | 137 | x0 += r/2 + d + 4*s 138 | 139 | dx = d + s 140 | dy = h + s 141 | gik += move(triangle_gik, x0, 0) 142 | gik += move(inverted_triangle_gik, x0, dy) 143 | gik += move(triangle_gik, x0, 2*dy) 144 | gik += move(inverted_triangle_gik, x0+dx, 0) 145 | gik += move(triangle_gik, x0+dx, dy) 146 | gik += move(inverted_triangle_gik, x0+dx, 2*dy) 147 | 148 | ################################################################################ 149 | 150 | gik += move(round_gik_row, 4*dx + 2*s, 0) 151 | 152 | ################################################################################ 153 | 154 | cad.shape = gik 155 | -------------------------------------------------------------------------------- /examples/hinge.ko: -------------------------------------------------------------------------------- 1 | ## Geometry header ## 2 | [(koko.prims.points.Point, {'y': '3.68116250962', 'x': '6.6241576341', 'name': 'corner'}),(koko.prims.points.Point, {'y': 'corner.y', 'x': '3.96428776181', 'name': 'border'}),(koko.prims.utils.Slider, {'name': 'esw_slider', 'min': '0', 'max': '0.5', 'value': '0.12974617506', 'y': '2.36389504454', 'x': '10.2595197251', 'size': '5'}),(koko.prims.utils.Slider, {'name': 'esl_slider', 'min': '1', 'max': '5', 'value': '1.4', 'y': '0.128180535766', 'x': '10.379580192', 'size': '5'}),(koko.prims.utils.Slider, {'name': 'wt_slider', 'min': '0', 'max': '0.5', 'value': '0.207806851754', 'y': '-2.07664232583', 'x': '10.4804981458', 'size': '5'})] 3 | ## End of geometry header ## 4 | """ 5 | examples/hinge.ko 6 | 7 | A response living hinge. Drag the corners or adjust the sliders 8 | to change its size and other parameters. 9 | 10 | Based on a design by Terrence J. Fagan 11 | (http://terencefagan.wordpress.com/) posted at 12 | http://fabacademy.org/archives/2014/tutorials/LivingHingeinKokopelli.html 13 | 14 | """ 15 | from koko.lib.shapes import * 16 | import operator 17 | import math 18 | 19 | # ############################################################## 20 | # Driving parameters: 21 | # These values are taken from UI handles; click and drag the 22 | # sliders and points to modify the hinge. 23 | esw = esw_slider.value # Empty space thickness 24 | esl = esl_slider.value # Empty space length 25 | wt = wt_slider.value # Web thickness 26 | w = corner.x # Half of sheet width 27 | h = corner.y # Half of sheet height 28 | # ############################################################## 29 | 30 | # Calculate how many cutouts we need in the x and y direction, 31 | # based on living hinge parameters and the sheet size 32 | x_count = int(math.ceil((border.x - esw/2 - wt) / (esw + wt))) 33 | y_count = int(math.ceil(h / (esl + 2*esw + wt))) 34 | 35 | # This is the basic shape for the cutout: 36 | # It's a rectangle with circular caps at the top and bottom 37 | es = (rectangle(-esw/2, esw/2, -esl/2, esl/2) + 38 | circle(0,esl/2,esw/2) + circle(0,-esl/2,esw/2)) 39 | 40 | # Tile the cutout object in the vertical direction 41 | es = reduce(operator.add, 42 | [move(es, 0, i*(esl + esw + wt)) 43 | for i in range(-y_count, y_count + 1)]) 44 | 45 | # Tile horizontally with interleaved offset cutouts. 46 | print x_count 47 | cutout = reduce(operator.add, 48 | [move(es, i * (esw + wt), 0) 49 | for i in range(-x_count, x_count + 1, 2)] + 50 | [move(es, i * (esw + wt), esl/2 + esw) 51 | for i in range(-x_count + 1, x_count, 2)]) 52 | 53 | # Finally, subtract the cutouts from the hinge shape 54 | cad.shape = rectangle(-w,w,-h,h) - cutout -------------------------------------------------------------------------------- /examples/mandala.ko: -------------------------------------------------------------------------------- 1 | ## Geometry header ## 2 | [(koko.prims.utils.Slider, {'name': 'N', 'min': '12', 'max': '24', 'value': '17', 'y': '3.37806648161', 'x': '-0.0986350465531', 'size': '3.0'}),(koko.prims.utils.Slider, {'name': 'T', 'min': '0.05', 'max': '0.2', 'value': '0.075', 'y': '3.05176854553', 'x': '-0.0872853625212', 'size': '3'})] 3 | ## End of geometry header ## 4 | """ 5 | examples/mandala.ko 6 | 7 | Demonstrates an intricate pattern parameterized by a pair of 8 | interactive sliders. 9 | 10 | Zoom out to see the sliders, then drag the handles to change system 11 | parameters. 12 | 13 | """ 14 | 15 | from koko.lib.shapes import * 16 | from math import sqrt 17 | import operator 18 | 19 | # Take the values from the sliders and store them as local variables 20 | N = int(N.value) 21 | T = T.value 22 | 23 | r = rectangle(-1, 1, -1, 1) - rectangle(-1+T, 1-T, -1+T, 1-T) 24 | r = move(rotate(r, 45), 0, sqrt(2)) 25 | r = scale_x(r, 0, 0.5) 26 | r += circle(0, 1.5*sqrt(2), 0.1) 27 | r += rotate( 28 | move( 29 | rotate(rectangle(-0.1, 0.1, -0.1, 0.1), 45), 30 | 0, 1.8*sqrt(2)), 31 | 360./N/2) 32 | 33 | r = reduce( 34 | operator.add, 35 | [rotate(r, i*360./N) for i in range(N)] 36 | ) 37 | 38 | r += circle(0, 0, 1.1+T/2) 39 | r -= circle(0, 0, 1.1) 40 | r += circle(0, 0, 2*sqrt(2)) - circle(0, 0, 2*sqrt(2)-T/2) 41 | 42 | c = circle(0, 0, 0.55) - circle(0, 0, 0.55-T) 43 | c += rectangle(-0.55, 0.55, -T/2, T/2) 44 | c = scale_x(move(c, 0, 0.6), 0, 0.5) 45 | c = reduce( 46 | operator.add, 47 | [rotate(c, i*360./(N/2)) for i in range(N/2)] 48 | ) 49 | c += circle(0, 0, 0.4) 50 | c -= circle(0, 0, 0.4-T/2) 51 | 52 | cad.shape = r + c 53 | -------------------------------------------------------------------------------- /examples/text.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/text.ko 3 | 4 | This example shows the use of the koko.lib.text library. 5 | 6 | It also indirectly shows a few data attributes of MathTree 7 | shapes: the staff is position with reference to x and y bounds 8 | of the text block. 9 | """ 10 | 11 | 12 | from koko.lib.text import * 13 | from koko.lib.shapes2d import * 14 | 15 | txt = ''' But this rough magic 16 | I here abjure, and, when I have required 17 | Some heavenly music, which even now I do, 18 | To work mine end upon their senses that 19 | This airy charm is for, I'll break my staff, 20 | Bury it certain fathoms in the earth, 21 | And deeper than did ever plummet sound 22 | I'll drown my book. 23 | ''' # -- Prospero (The Tempest, 5.I) 24 | 25 | 26 | # The first character in 'align' determines horizontal alignment. 27 | # Valid values are and can be L, R, or C (left, right, or centered). 28 | # The second character determines vertical alignment. 29 | # Valid values are T, C, or B (top, center, or bottom) 30 | txt = text(txt, 0, 0, align='LC') 31 | 32 | # Make a decorative wizard's staff 33 | edge = triangle(-0.1, 0, 0, 0, -0.05, -0.1) 34 | edge += move(edge, 0.1, 0) 35 | top = ( 36 | rectangle(-0.1, 0.1, 0, 2) + edge + 37 | circle(0, 2, 0.2) - rectangle(-1, 1, 2, 2.5) - 38 | circle(0, 2, 0.15) 39 | ) 40 | bottom = rectangle(-0.1, 0.1, -2, 0) - edge 41 | top, bottom = map( 42 | lambda t: taper_x_y(t, 0, -2, 2, 0.3, 1), [top, bottom] 43 | ) 44 | crystal = circle(0, 0, 0.12) 45 | r = scale_y(rotate(rectangle(-0.02, 0.02, -0.02, 0.02), 45), 0, 3) 46 | for i in range(8): 47 | crystal -= rotate(r, i*360/8) 48 | top += move(crystal, 0, 2) 49 | 50 | # Position and scale the wizard's staff 51 | staff = scale_xy( 52 | rotate(top + rotate(move(bottom, 0, -0.1), -10), 95), 53 | 0, 0, 5) 54 | staff = move(staff, (txt.xmin+txt.xmax)/2, txt.ymin - 1) 55 | 56 | cad.shapes = txt, staff 57 | -------------------------------------------------------------------------------- /examples/tippy.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/tippy.ko 3 | 4 | Tippy-top molding and casting example 5 | 6 | Based on the original tippy.cad: 7 | http://academy.cba.mit.edu/classes//molding_casting/tippy.cad 8 | [Neil Gershenfeld, 10/19/10] 9 | 10 | We use the "MathTree(-1)" to create a filled world (since negative 11 | values are considered part of the object in our distance metric 12 | representation). This means that the object's bounds have to be 13 | specified manually (rather than being automatically generated). 14 | 15 | """ 16 | 17 | from koko.lib.shapes import * 18 | 19 | # 20 | # dimensions 21 | # 22 | top_radius = 0.5 23 | top_wall = top_radius/4.0 24 | stem_radius = top_radius/5.0 25 | stem_height = top_radius/1.5 26 | stem_depth = stem_height 27 | mold_wall = 0.2 28 | x0 = 1.0 29 | y0 = 1.0 30 | 31 | ################################################################################ 32 | 33 | # define bottom 34 | bottom = MathTree(-1) 35 | bottom -= cylinder(0,0,-top_radius-2*mold_wall,10,top_radius+2*mold_wall) 36 | bottom += cylinder(0,0,-top_radius-2*mold_wall,-top_radius-mold_wall,top_radius+mold_wall) 37 | bottom += sphere(0,0,-top_radius-mold_wall,top_radius) 38 | 39 | # cutout to see interior (comment out for machining) 40 | bottom = subtract(bottom,cube(0,10,-10,0,-10,10)) 41 | 42 | 43 | # move to location to machine 44 | bottom = move(bottom, x0+top_radius+2*mold_wall, y0+top_radius+2*mold_wall) 45 | 46 | ################################################################################ 47 | 48 | # define top 49 | top = MathTree(-1) 50 | top -= cylinder(0,0,-stem_height,10,top_radius+mold_wall) 51 | top -= sphere(0,0,-stem_height,top_radius-top_wall) 52 | top += cylinder(0,0,-10,10,stem_radius) 53 | top += cylinder(top_radius-top_wall/2.0,0,-10,10,top_wall/2.0) 54 | 55 | # cutout to see interior (comment out for machining) 56 | top -= cube(0,10,-10,0,-10,10) 57 | 58 | # move to location to machine 59 | top = move(top,x0+top_radius+mold_wall,y0+top_radius+mold_wall) 60 | 61 | ################################################################################ 62 | 63 | # select mold component to make 64 | part = bottom 65 | #part = top 66 | 67 | ################################################################################ 68 | 69 | # part bounds 70 | part.xmin = x0-mold_wall 71 | part.xmax = x0+2*top_radius+5*mold_wall 72 | part.ymin = y0-mold_wall 73 | part.ymax = y0+2*top_radius+5*mold_wall 74 | part.zmin = -top_radius-3*mold_wall 75 | part.zmax = 0 76 | 77 | cad.shape = part 78 | -------------------------------------------------------------------------------- /examples/trinket.ko: -------------------------------------------------------------------------------- 1 | """ 2 | examples/trinket.ko 3 | 4 | This is a model for a four-piece press-fit trinket. 5 | It is designed for fabrication on a water-jet cutter; 6 | the kerf of the tool is parameterized below. 7 | 8 | When the variable 'mode' is changed, it switches between 9 | a cut sheet and a solid model. 10 | 11 | Notice that the design sets cad.render_mode, overriding 12 | the display settings. 13 | """ 14 | 15 | from koko.lib.shapes import * 16 | 17 | ## Comment out one or the other to switch mode 18 | #mode = 'cut sheet' 19 | mode = 'model' 20 | 21 | # System parameters 22 | R = 0.25 # sphere radius 23 | T = 0.125 # material thickness 24 | kerf = 0.015 # cutting tool's kerf (radius) 25 | 26 | 27 | A = circle(0, 0, R) - circle(0, 0, R/2) 28 | B = C = A 29 | 30 | inner_slot = (rectangle(-T/2, T/2, 0, 0.75*R) + 31 | circle(-T/2, 0.75*R-kerf, kerf) + 32 | circle(T/2, 0.75*R-kerf, kerf)) 33 | outer_slot = (rectangle(-T/2, T/2, 0.75*R, R) + 34 | circle(-T/2, 0.75*R+kerf, kerf) + 35 | circle(T/2, 0.75*R+kerf, kerf)) 36 | 37 | A -= inner_slot 38 | A -= rectangle(-T/2, T/2, -R, 0) 39 | A.color = 'red' 40 | 41 | B -= rotate(inner_slot, 90) + rotate(inner_slot, -90) 42 | B -= outer_slot 43 | B.color = 'blue' 44 | 45 | C -= rectangle(-T/2, T/2, -R, R) 46 | C -= rotate(outer_slot, 90) + rotate(outer_slot, -90) 47 | C.color = 'green' 48 | 49 | if mode == 'model': 50 | A = extrusion(A, -T/2, T/2) 51 | B = rotate_y(extrusion(B, -T/2, T/2), 90) 52 | C = rotate_z(rotate_y(extrusion(C, -T/2, T/2), 90), 90) 53 | 54 | cad.render_mode = '3D' 55 | cad.shapes = A, B, C 56 | elif mode == 'cut sheet': 57 | cad.render_mode = '2D' 58 | cad.shapes = A, move(B, 2.5*R, 0), move(C, 5*R, 0) 59 | else: 60 | print "Invalid mode." 61 | -------------------------------------------------------------------------------- /koko/__init__.py: -------------------------------------------------------------------------------- 1 | """ Top-level module for kokopelli. """ 2 | 3 | NAME = 'kokopelli' 4 | VERSION = '0.2' 5 | HASH = None 6 | 7 | """ 8 | @namespace koko 9 | @brief Top-level module 10 | """ 11 | -------------------------------------------------------------------------------- /koko/about.py: -------------------------------------------------------------------------------- 1 | from koko import NAME, VERSION, HASH 2 | 3 | import wx 4 | 5 | def show_about_box(event=None): 6 | '''Displays an About box with information about this program.''' 7 | 8 | info = wx.AboutDialogInfo() 9 | info.SetName(NAME) 10 | info.SetVersion(VERSION) 11 | 12 | if HASH is None: 13 | info.SetDescription('An interactive design tool for .cad files.') 14 | else: 15 | info.SetDescription( 16 | 'An interactive design tool for .cad files.\ngit commit: ' + 17 | HASH 18 | ) 19 | 20 | info.SetWebSite('https://github.com/mkeeter/kokopelli') 21 | info.SetCopyright( 22 | '''(C) 2012-13 MIT Center for Bits and Atoms 23 | (C) 2013 Matt Keeter''') 24 | 25 | wx.AboutBox(info) 26 | -------------------------------------------------------------------------------- /koko/c/__init__.py: -------------------------------------------------------------------------------- 1 | """ Module defining C data structures from the libfab library. """ 2 | -------------------------------------------------------------------------------- /koko/c/asdf.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | from koko.c.interval import Interval 4 | 5 | class ASDF(ctypes.Structure): 6 | """ @class ASDF 7 | @brief C data structure describing an ASDF. 8 | """ 9 | pass 10 | ASDF._fields_ = [('state', ctypes.c_int), 11 | ('X', Interval), ('Y', Interval), ('Z', Interval), 12 | ('branches', ctypes.POINTER(ASDF)*8), 13 | ('d', ctypes.c_float*8), 14 | ('data', ctypes.c_void_p)] 15 | -------------------------------------------------------------------------------- /koko/c/interval.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | class Interval(ctypes.Structure): 4 | """ @class Interval 5 | @brief Interval containing upper and lower bounds with overloaded arithmetic operators 6 | """ 7 | _fields_ = [('lower', ctypes.c_float), 8 | ('upper', ctypes.c_float)] 9 | 10 | def __init__(self, lower=0, upper=None): 11 | """ @brief Constructor for Interval 12 | @param lower Lower bound 13 | @param upper Upper bound (if None, lower is used) 14 | """ 15 | if upper is None: upper = lower 16 | if isinstance(lower, Interval): 17 | lower, upper = lower.lower, lower.upper 18 | ctypes.Structure.__init__(self, lower, upper) 19 | 20 | def __str__(self): 21 | return "[%g, %g]" % (self.lower, self.upper) 22 | def __repr__(self): 23 | return "Interval(%g, %g)" % (self.lower, self.upper) 24 | def __add__(self, rhs): return libfab.add_i(self, Interval(rhs)) 25 | def __radd__(self, lhs): return libfab.add_i(Interval(lhs), self) 26 | def __sub__(self, rhs): return libfab.sub_i(self, Interval(rhs)) 27 | def __rsub__(self, lhs): return libfab.sub_i(Interval(lhs), self) 28 | def __mul__(self, rhs): return libfab.mul_i(self, Interval(rhs)) 29 | def __rmul__(self, lhs): return libfab.mul_i(Interval(lhs), self) 30 | def __div__(self, rhs): return libfab.div_i(self, Interval(rhs)) 31 | def __rdiv__(self, lhs): return libfab.div_i(Interval(lhs), self) 32 | def __neg__(self): return libfab.neg_i(self) 33 | 34 | @staticmethod 35 | def sqrt(i): return libfab.sqrt_i(i) 36 | @staticmethod 37 | def pow(i, e): return libfab.pow_i(i, e) 38 | @staticmethod 39 | def sin(i): return libfab.sin_i(i) 40 | @staticmethod 41 | def cos(i): return libfab.cos_i(i) 42 | @staticmethod 43 | def tan(i): return libfab.tan_i(i) 44 | 45 | def copy(self): 46 | return Interval(self.lower, self.upper) 47 | 48 | from koko.c.libfab import libfab 49 | -------------------------------------------------------------------------------- /koko/c/mesh.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | from koko.c.interval import Interval 4 | 5 | class Mesh(ctypes.Structure): 6 | """ @class Mesh 7 | @brief C data structure describing a Mesh 8 | """ 9 | _fields_ = [ 10 | ('vdata', ctypes.POINTER(ctypes.c_float)), 11 | ('vcount', ctypes.c_uint32), 12 | ('valloc', ctypes.c_uint32), 13 | ('tdata', ctypes.POINTER(ctypes.c_uint32)), 14 | ('tcount', ctypes.c_uint32), 15 | ('talloc', ctypes.c_uint32), 16 | ('X', Interval), ('Y', Interval), ('Z', Interval) 17 | ] 18 | 19 | -------------------------------------------------------------------------------- /koko/c/multithread.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from thread import LockType 3 | 4 | def __monitor(interrupt, halt): 5 | """ @brief Waits for interrupt, then sets halt to 1 6 | @param interrupt threading.Event on which we wait 7 | @param halt ctypes.c_int used as a flag elsewhere 8 | """ 9 | interrupt.wait() 10 | halt.value = 1 11 | 12 | 13 | def multithread(target, args, interrupt=None, halt=None): 14 | """ @brief Runs a process on multiple threads. 15 | @details Must be called with both interrupt and halt or neither. Interrupt is cleared before returning. 16 | @param target Callable function 17 | @param args List of argument tuples (one tuple per thread) 18 | @param interrupt threading.Event to halt thread or None 19 | @param halt ctypes.c_int used as an interrupt flag by target 20 | """ 21 | 22 | if (halt is None) ^ (interrupt is None): 23 | raise ValueError('multithread must be invoked with both halt and interrupt (or neither)') 24 | 25 | threads = [threading.Thread(target=target, args=a) for a in args] 26 | 27 | if interrupt: 28 | m = threading.Thread(target=__monitor, args=(interrupt, halt)) 29 | m.daemon = True 30 | m.start() 31 | 32 | for t in threads: t.daemon = True 33 | for t in threads: t.start() 34 | for t in threads: t.join() 35 | 36 | if interrupt: 37 | interrupt.set() 38 | m.join() 39 | interrupt.clear() 40 | 41 | 42 | def monothread(target, args, interrupt=None, halt=None): 43 | """ @brief Runs a process on a single thread 44 | @details Must be called with both interrupt and halt or neither. Interrupt is cleared before returning. 45 | @param target Callable function 46 | @param args Argument tuples 47 | @param interrupt threading.Event to halt thread or None 48 | @param halt ctypes.c_int used as an interrupt flag by target 49 | """ 50 | if (halt is None) ^ (interrupt is None): 51 | raise ValueError('monothread must be invoked with both halt and interrupt (or neither)') 52 | 53 | if interrupt: 54 | m = threading.Thread(target=__monitor, args=(interrupt, halt)) 55 | m.daemon = True 56 | m.start() 57 | 58 | result = target(*args) 59 | 60 | if interrupt: 61 | interrupt.set() 62 | m.join() 63 | interrupt.clear() 64 | 65 | return result 66 | 67 | def threadsafe(f): 68 | ''' A decorator that locks the arguments to a function, 69 | invokes the function, then unlocks the arguments and 70 | returns.''' 71 | def wrapped(*args, **kwargs): 72 | for a in set(list(args) + kwargs.values()): 73 | if hasattr(a, 'lock') and LockType and isinstance(a.lock, LockType): 74 | a.lock.acquire() 75 | result = f(*args, **kwargs) 76 | for a in set(list(args) + kwargs.values()): 77 | if hasattr(a, 'lock') and LockType and isinstance(a.lock, LockType): 78 | a.lock.release() 79 | return result 80 | return wrapped 81 | -------------------------------------------------------------------------------- /koko/c/path.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | class Path(ctypes.Structure): 4 | """ @class Path 5 | @brief C data structure containing a doubly linked list. 6 | """ 7 | def __repr__(self): 8 | return 'pt(%g, %g) at %s' % \ 9 | (self.x, self.y, hex(ctypes.addressof(self))) 10 | def __eq__(self, other): 11 | if other is None: return False 12 | return ctypes.addressof(self) == ctypes.addressof(other) 13 | def __ne__(self, other): 14 | if other is None: return False 15 | return not (self == other) 16 | 17 | def p(t): return ctypes.POINTER(t) 18 | def pp(t): return p(p(t)) 19 | 20 | Path._fields_ = [ 21 | ('prev', p(Path)), ('next', p(Path)), 22 | ('x', ctypes.c_float), ('y', ctypes.c_float), ('z', ctypes.c_float), 23 | ('ptr', pp(Path*2)) 24 | ] 25 | 26 | del p, pp 27 | -------------------------------------------------------------------------------- /koko/c/region.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | class Region(ctypes.Structure): 4 | ''' @class Region 5 | @brief Class containing lattice bounds and ticks. 6 | @details Replicates C Region class. 7 | ''' 8 | _fields_ = [('imin', ctypes.c_uint32), 9 | ('jmin', ctypes.c_uint32), 10 | ('kmin', ctypes.c_uint32), 11 | ('ni', ctypes.c_uint32), 12 | ('nj', ctypes.c_uint32), 13 | ('nk', ctypes.c_uint32), 14 | ('voxels', ctypes.c_uint64), 15 | ('X', ctypes.POINTER(ctypes.c_float)), 16 | ('Y', ctypes.POINTER(ctypes.c_float)), 17 | ('Z', ctypes.POINTER(ctypes.c_float)), 18 | ('L', ctypes.POINTER(ctypes.c_uint16))] 19 | 20 | def __init__(self, (xmin, ymin, zmin)=(0.,0.,0.), 21 | (xmax, ymax, zmax)=(0.,0.,0.), 22 | scale=100., dummy=False, depth=None): 23 | """ @brief Creates an array. 24 | """ 25 | 26 | dx = float(xmax - xmin) 27 | dy = float(ymax - ymin) 28 | dz = float(zmax - zmin) 29 | 30 | if depth is not None: 31 | scale = 3*(2**6)* 2**(depth/3.) / (dx+dy+dz) 32 | 33 | ni = max(int(round(dx*scale)), 1) 34 | nj = max(int(round(dy*scale)), 1) 35 | nk = max(int(round(dz*scale)), 1) 36 | 37 | # Dummy assignments so that Doxygen recognizes these instance variables 38 | self.ni = self.nj = self.nk = 0 39 | self.imin = self.jmin = self.kmin = 0 40 | self.voxels = 0 41 | self.X = self.Y = self.Z = self.L = None 42 | 43 | ## @var ni 44 | # Number of ticks along x axis 45 | ## @var nj 46 | #Number of points along y axis 47 | ## @var nk 48 | # Number of points along z axis 49 | 50 | ## @var imin 51 | # Minimum i coordinate in global lattice space 52 | ## @var jmin 53 | # Minimum j coordinate in global lattice space 54 | ## @var kmin 55 | # Minimum k coordinate in global lattice space 56 | 57 | ## @var voxels 58 | # Voxel count in this section of the lattice 59 | 60 | ## @var X 61 | # Array of ni+1 X coordinates as floating-point values 62 | ## @var Y 63 | # Array of nj+1 Y coordinates as floating-point values 64 | ## @var Z 65 | # Array of nk+1 Z coordinates as floating-point values 66 | ## @var L 67 | # Array of nk+1 luminosity values as 16-bit integers 68 | 69 | ## @var free_arrays 70 | # Boolean indicating whether this region dynamically allocated 71 | # the X, Y, Z, and L arrays. 72 | # 73 | # Determines whether these arrays are 74 | # freed when the structure is deleted. 75 | 76 | ctypes.Structure.__init__(self, 77 | 0, 0, 0, 78 | ni, nj, nk, 79 | ni*nj*nk, 80 | None, None, None, None) 81 | 82 | if dummy is False: 83 | libfab.build_arrays(ctypes.byref(self), 84 | xmin, ymin, zmin, 85 | xmax, ymax, zmax) 86 | self.free_arrays = True 87 | else: 88 | self.free_arrays = False 89 | 90 | def __del__(self): 91 | """ @brief Destructor for Region 92 | @details Frees allocated arrays if free_arrays is True 93 | """ 94 | if hasattr(self, 'free_arrays') and self.free_arrays and libfab is not None: 95 | libfab.free_arrays(self) 96 | 97 | def __repr__(self): 98 | return ('[(%g, %g), (%g, %g), (%g, %g)]' % 99 | (self.imin, self.imin + self.ni, 100 | self.jmin, self.jmin + self.nj, 101 | self.kmin, self.kmin + self.nk)) 102 | 103 | def split(self, count=2): 104 | """ @brief Repeatedly splits the region along its longest axis 105 | @param count Number of subregions to generate 106 | @returns List of regions (could be fewer than requested if the region is indivisible) 107 | """ 108 | L = (Region*count)() 109 | count = libfab.split(self, L, count) 110 | 111 | return L[:count] 112 | 113 | def split_xy(self, count=2): 114 | """ @brief Repeatedly splits the region on the X and Y axes 115 | @param count Number of subregions to generate 116 | @returns List of regions (could be fewer than requested if the region is indivisible) 117 | """ 118 | L = (Region*count)() 119 | count = libfab.split_xy(self, L, count) 120 | 121 | return L[:count] 122 | 123 | def octsect(self, all=False, overlap=False): 124 | """ @brief Splits the region into eight subregions 125 | @param all If true, returns an 8-item array with None in the place of missing subregions. Otherwise, the output array is culled to only include valid subregions. 126 | @returns An array of containing regions (and Nones if all is True and the region was indivisible along some axis) 127 | """ 128 | L = (Region*8)() 129 | if overlap: 130 | bits = libfab.octsect_overlap(self, L) 131 | else: 132 | bits = libfab.octsect(self, L) 133 | 134 | if all: 135 | return [L[i] if (bits & (1 << i)) else None for i in range(8)] 136 | else: 137 | return [L[i] for i in range(8) if (bits & (1 << i))] 138 | 139 | 140 | from koko.c.libfab import libfab 141 | from koko.c.vec3f import Vec3f 142 | -------------------------------------------------------------------------------- /koko/c/vec3f.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from math import sin, cos, radians, sqrt 3 | 4 | class Vec3f(ctypes.Structure): 5 | """ @class Vec3f 6 | @brief Three-element vector with overloaded arithmetic operators. 7 | """ 8 | _fields_ = [('x', ctypes.c_float), 9 | ('y', ctypes.c_float), 10 | ('z', ctypes.c_float)] 11 | def __init__(self, x=0., y=0., z=0.): 12 | try: x = list(x) 13 | except TypeError: ctypes.Structure.__init__(self, x, y, z) 14 | else: ctypes.Structure.__init__(self, x[0], x[1], x[2]) 15 | 16 | def __str__(self): 17 | return "(%g, %g, %g)" % (self.x, self.y, self.z) 18 | def __repr__(self): 19 | return "Vec3f(%g, %g, %g)" % (self.x, self.y, self.z) 20 | def __add__(self, rhs): 21 | return Vec3f(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) 22 | def __sub__(self, rhs): 23 | return Vec3f(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) 24 | def __div__(self, rhs): 25 | return Vec3f(self.x/rhs, self.y/rhs, self.z/rhs) 26 | def __neg__(self): 27 | return Vec3f(-self.x, -self.y, -self.z) 28 | def length(self): 29 | return sqrt(self.x**2 + self.y**2 + self.z**2) 30 | def copy(self): 31 | return Vec3f(self.x, self.y, self.z) 32 | 33 | @staticmethod 34 | def M(alpha, beta): 35 | """ @brief Generates M matrix for libfab's project and deproject functions. 36 | @param alpha Rotation about z axis 37 | @param beta Rotation about x axis. 38 | @returns (cos(a), sin(a), cos(b), sin(b)) as float array 39 | """ 40 | return (ctypes.c_float*4)( 41 | cos(radians(alpha)), sin(radians(alpha)), 42 | cos(radians(beta)), sin(radians(beta)) 43 | ) 44 | 45 | def project(self, alpha, beta): 46 | """ @brief Transforms from cell frame to view frame. 47 | @param alpha Rotation about z axis 48 | @param beta Rotation about x axis. 49 | @returns Projected Vec3f object 50 | """ 51 | return libfab.project(self, self.M(alpha, beta)) 52 | 53 | def deproject(self, alpha, beta): 54 | """ @brief Transforms from view frame to cell frame. 55 | @param alpha Rotation about z axis 56 | @param beta Rotation about x axis. 57 | @returns Deprojected Vec3f object 58 | """ 59 | return libfab.deproject(self, self.M(alpha, beta)) 60 | 61 | def __iter__(self): 62 | """ @brief Iterates over (x, y, z) list 63 | """ 64 | return [self.x, self.y, self.z].__iter__() 65 | 66 | from koko.c.libfab import libfab 67 | -------------------------------------------------------------------------------- /koko/cam/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | @namespace cam 3 | @brief Module containing CAM-related functionality 4 | """ 5 | -------------------------------------------------------------------------------- /koko/cam/inputs/__init__.py: -------------------------------------------------------------------------------- 1 | """ Module containing input modules. 2 | 3 | Each input module must define TYPE (the python type of the desired input) 4 | and WORKFLOWS (a dictionary mapping different panel types to workflows 5 | that lead to that type of panel). 6 | """ 7 | import asdf, cad, image 8 | 9 | INPUTS = [asdf, cad, image] 10 | -------------------------------------------------------------------------------- /koko/cam/inputs/asdf.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import wx.lib.stattext 3 | 4 | import os 5 | 6 | import koko 7 | from koko.struct import Struct 8 | from koko.cam.panel import FabPanel 9 | from koko.dialogs import RescaleDialog 10 | 11 | from koko.fab.image import Image 12 | 13 | class ASDFInputPanel(FabPanel): 14 | """ @class ASDFInputPanel UI Panel for ASDF loaded from a file 15 | """ 16 | 17 | def __init__(self, parent): 18 | FabPanel.__init__(self, parent) 19 | 20 | sizer = wx.BoxSizer(wx.VERTICAL) 21 | title = wx.lib.stattext.GenStaticText(self, wx.ID_ANY, label='Input', 22 | style=wx.ALIGN_CENTRE) 23 | title.header = True 24 | sizer.Add(title, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=5) 25 | 26 | text = wx.GridSizer(2, 2) 27 | 28 | self.file = wx.StaticText(self, label='.asdf file') 29 | self.pix = wx.StaticText(self) 30 | self.mms = wx.StaticText(self) 31 | self.ins = wx.StaticText(self) 32 | 33 | text.Add(self.file) 34 | text.Add(self.mms, flag=wx.ALIGN_LEFT|wx.EXPAND) 35 | text.Add(self.pix, flag=wx.ALIGN_LEFT|wx.EXPAND) 36 | text.Add(self.ins, flag=wx.ALIGN_LEFT|wx.EXPAND) 37 | 38 | sizer.Add(text, flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5) 39 | resize = wx.Button(self, label='Rescale') 40 | sizer.Add(resize, flag=wx.CENTER|wx.ALL, border=5) 41 | resize.Bind(wx.EVT_BUTTON, self.resize) 42 | 43 | self.SetSizerAndFit(sizer) 44 | 45 | 46 | @property 47 | def input(self): 48 | """ @brief Property returning self.asdf """ 49 | return self.asdf 50 | 51 | 52 | def resize(self, event=None): 53 | """ @brief Allows user to resize asdf 54 | @details Opens a RescaleDialog and rescales the stored asdf, then updates the UI, retriangulates the displayed mesh. 55 | """ 56 | dlg = RescaleDialog('Rescale ASDF', self.asdf) 57 | if dlg.ShowModal() == wx.ID_OK: 58 | self.asdf.rescale(float(dlg.result)) 59 | 60 | mesh = self.asdf.triangulate() 61 | mesh.source = Struct(type=ASDF, file=self.asdf.filename, depth=0) 62 | koko.GLCANVAS.load_mesh(mesh) 63 | 64 | self.parent.invalidate() 65 | self.parent.update() 66 | dlg.Destroy() 67 | 68 | 69 | def update(self, input): 70 | """ @brief Updates this panel 71 | @param input Input ASDF 72 | @returns Dictionary with dx, dy, and dz values 73 | """ 74 | 75 | ## @var asdf 76 | # ASDF data structure 77 | self.asdf = input 78 | 79 | if self.asdf.filename: file = os.path.split(self.asdf.filename)[1] 80 | else: file = 'Unknown .asdf file' 81 | self.file.SetLabel(file) 82 | 83 | self.pix.SetLabel('%i x %i x %i voxels' % self.asdf.dimensions) 84 | 85 | self.mms.SetLabel( 86 | '%.1f x %.1f x %.1f mm' % 87 | (self.asdf.dx, self.asdf.dy, self.asdf.dz) 88 | ) 89 | 90 | self.ins.SetLabel( 91 | '%.2f x %.2f x %.2f"' % 92 | (self.asdf.dx/25.4, self.asdf.dy/25.4, self.asdf.dz/25.4) 93 | ) 94 | return {'dx': self.asdf.dx, 'dy': self.asdf.dy, 'dz': self.asdf.dz} 95 | 96 | 97 | def run(self): 98 | """ @brief Returns a dictionary with the stored asdf 99 | """ 100 | return {'asdf': self.asdf} 101 | 102 | ################################################################################ 103 | 104 | class ASDFImagePanel(FabPanel): 105 | ''' @class ASDFImagePanel Panel to convert an ASDF data structure into a png. 106 | ''' 107 | def __init__(self, parent): 108 | FabPanel.__init__(self, parent) 109 | self.construct('Lattice', [ 110 | ('Resolution (pixels/mm)\n', 'res', float, lambda f: f > 0)]) 111 | 112 | self.res.Bind(wx.EVT_TEXT, self.parent.update) 113 | self.img = Image(0,0) 114 | 115 | def update(self, dx, dy, dz): 116 | """ @brief Updates UI panel with dimensions 117 | @param dx x dimension (mm) 118 | @param dy y dimension (mm) 119 | @param dz z dimension (mm) 120 | """ 121 | try: 122 | scale = float(self.res.GetValue()) 123 | except ValueError: 124 | self.labels[0].SetLabel('Resolution (pixels/mm)\n? x ? x ?') 125 | else: 126 | self.labels[0].SetLabel( 127 | 'Resolution (pixels/mm)\n%i x %i x %i' % 128 | (max(1, dx*scale), 129 | max(1, dy*scale), 130 | max(1, dz*scale)) 131 | ) 132 | return {'threeD': True} 133 | 134 | def run(self, asdf): 135 | """ @brief Renders an ASDF to an image 136 | @details Image is saved to self.img and appended to dictionary 137 | @param args Dictionary with key 'asdf' 138 | @returns Dictionary updated with key 'img' 139 | """ 140 | koko.FRAME.status = 'Generating image' 141 | 142 | values = self.get_values() 143 | if not values: return False 144 | 145 | # Render the asdf into an image 146 | self.img = asdf.render(resolution=values['res']) 147 | koko.FRAME.status = '' 148 | 149 | return {'img': self.img} 150 | 151 | ################################################################################ 152 | 153 | from koko.fab.asdf import ASDF 154 | from koko.cam.path_panels import PathPanel, ContourPanel, MultiPathPanel 155 | 156 | TYPE = ASDF 157 | WORKFLOWS = { 158 | None: (ASDFInputPanel,), 159 | PathPanel: (ASDFInputPanel, ASDFImagePanel,), 160 | MultiPathPanel: (ASDFInputPanel,), 161 | } 162 | 163 | ################################################################################ 164 | -------------------------------------------------------------------------------- /koko/cam/inputs/image.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import wx.lib.stattext 3 | 4 | import os 5 | 6 | import koko 7 | from koko.cam.panel import FabPanel 8 | 9 | class ImageInputPanel(FabPanel): 10 | """ @class ImageInputPanel Input FabPanel for Image-based workflow 11 | """ 12 | 13 | def __init__(self, parent): 14 | """ @brief Initializes the panel 15 | @param Parent UI panel 16 | """ 17 | 18 | FabPanel.__init__(self, parent) 19 | 20 | sizer = wx.BoxSizer(wx.VERTICAL) 21 | title = wx.lib.stattext.GenStaticText(self, wx.ID_ANY, label='Input', 22 | style=wx.ALIGN_CENTRE) 23 | title.header = True 24 | sizer.Add(title, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=5) 25 | 26 | text = wx.GridSizer(2, 2) 27 | 28 | ## @var file 29 | # UI label with filename 30 | self.file = wx.StaticText(self, label='.png file') 31 | 32 | ## @var pix 33 | # UI label with size in pixels 34 | self.pix = wx.StaticText(self) 35 | 36 | ## @var file 37 | # UI label with size in mms 38 | self.mms = wx.StaticText(self) 39 | 40 | ## @var file 41 | # UI label with size in inches 42 | self.ins = wx.StaticText(self) 43 | 44 | text.Add(self.file) 45 | text.Add(self.mms, flag=wx.ALIGN_LEFT|wx.EXPAND) 46 | text.Add(self.pix, flag=wx.ALIGN_LEFT|wx.EXPAND) 47 | text.Add(self.ins, flag=wx.ALIGN_LEFT|wx.EXPAND) 48 | 49 | sizer.Add(text, flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5) 50 | self.SetSizerAndFit(sizer) 51 | 52 | @property 53 | def input(self): 54 | """ @brief Property returning self.img 55 | """ 56 | return self.img 57 | 58 | def update(self, input): 59 | """ @brief Updates the current image 60 | @details Reloads size values 61 | @param input Input image 62 | @returns Dictionary with 'threeD' value populated 63 | """ 64 | 65 | ## @var img 66 | # Image data structure 67 | self.img = input 68 | 69 | if self.img.filename: file = os.path.split(self.img.filename)[1] 70 | else: file = 'Unknown .png file' 71 | self.file.SetLabel(file) 72 | 73 | self.pix.SetLabel('%i x %i pixels' % (self.img.width, self.img.height)) 74 | 75 | if self.img.zmin is not None and self.img.zmax is not None: 76 | self.mms.SetLabel('%.2g x %.2g x %.2g mm' % 77 | (self.img.dx, self.img.dy, self.img.dz) 78 | ) 79 | self.ins.SetLabel('%.2g x %.2g x %.2g"' % 80 | (self.img.dx/25.4, 81 | self.img.dy/25.4, 82 | self.img.dz/25.4) 83 | ) 84 | threeD = bool(self.img.dz) 85 | else: 86 | self.mms.SetLabel('%.2g x %.2g mm' % 87 | (self.img.dx, self.img.dy) 88 | ) 89 | self.ins.SetLabel('%.2g x %.2g"' % 90 | (self.img.dx/25.4,self.img.dy/25.4) 91 | ) 92 | threeD = False 93 | 94 | return {'threeD': threeD} 95 | 96 | def run(self): 97 | """ @brief Returns a dictionary with the stored Image 98 | """ 99 | return {'img': self.img} 100 | 101 | ################################################################################ 102 | 103 | from koko.fab.image import Image 104 | from koko.cam.path_panels import PathPanel, ContourPanel 105 | 106 | TYPE = Image 107 | 108 | WORKFLOWS = { 109 | None: (ImageInputPanel,), 110 | PathPanel: (ImageInputPanel,), 111 | ContourPanel: (ImageInputPanel,), 112 | } 113 | 114 | ################################################################################ 115 | -------------------------------------------------------------------------------- /koko/cam/machines/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | @namespace machines 3 | @brief Module containing output machines 4 | 5 | @details Each input module must define NAME, INPUT, PANEL, and DEFAULTS (a list of tuples, each containing a default name and a dictionary which defines defaults for each potential panel). 6 | """ 7 | 8 | import modela, epilog, universal, null, shopbot, gcode, shopbot5 9 | 10 | MACHINES = [null, modela, epilog, universal, shopbot, gcode, shopbot5] 11 | -------------------------------------------------------------------------------- /koko/cam/machines/epilog.py: -------------------------------------------------------------------------------- 1 | """ 2 | @namespace epilog 3 | @brief Output details and UI panel for an Epilog laser cutter. 4 | """ 5 | 6 | 7 | NAME = 'Epilog' 8 | 9 | import tempfile 10 | import subprocess 11 | 12 | import koko 13 | from koko.cam.panel import OutputPanel 14 | 15 | class EpilogOutput(OutputPanel): 16 | """ @class EpilogOutput UI Panel for Epilog laser 17 | """ 18 | 19 | """ @var extension File extension for Epilog laser file 20 | """ 21 | extension = '.epi' 22 | 23 | def __init__(self, parent): 24 | OutputPanel.__init__(self, parent) 25 | 26 | self.construct('Epilog laser cutter', [ 27 | ('2D power (%)', 'power', int, lambda f: 0 <= f <= 100), 28 | ('Speed (%)', 'speed', int, lambda f: 0 <= f <= 100), 29 | ('Rate','rate', int, lambda f: f > 0), 30 | ('xmin (mm)', 'xmin', float, lambda f: f >= 0), 31 | ('ymin (mm)', 'ymin', float, lambda f: f >= 0), 32 | ('autofocus', 'autofocus', bool) 33 | ], start=True) 34 | 35 | 36 | def run(self, paths): 37 | ''' Convert the path from the previous panel into an epilog 38 | laser file (with .epi suffix). 39 | ''' 40 | values = self.get_values() 41 | if not values: return False 42 | 43 | koko.FRAME.status = 'Converting to .epi file' 44 | 45 | self.file = tempfile.NamedTemporaryFile(suffix=self.extension) 46 | job_name = koko.APP.filename if koko.APP.filename else 'untitled' 47 | 48 | self.file.write("%%-12345X@PJL JOB NAME=%s\r\nE@PJL ENTER LANGUAGE=PCL\r\n&y%iA&l0U&l0Z&u600D*p0X*p0Y*t600R*r0F&y50P&z50S*r6600T*r5100S*r1A*rC%%1BIN;XR%d;YP%d;ZS%d;\n" % 49 | (job_name, 1 if values['autofocus'] else 0, 50 | values['rate'], values['power'], values['speed'])) 51 | 52 | scale = 600/25.4 # The laser's tick is 600 DPI 53 | xoffset = values['xmin']*scale 54 | yoffset = values['ymin']*scale 55 | xy = lambda x,y: (xoffset + scale*x, yoffset + scale*y) 56 | 57 | for path in paths: 58 | self.file.write("PU%d,%d;" % xy(*path.points[0][0:2])) 59 | for pt in path.points[1:]: 60 | self.file.write("PD%d,%d;" % xy(*pt[0:2])) 61 | self.file.write("\n") 62 | 63 | self.file.write("%%0B%%1BPUtE%%-12345X@PJL EOJ \r\n") 64 | self.file.flush() 65 | 66 | koko.FRAME.status = '' 67 | 68 | return True 69 | 70 | 71 | def send(self): 72 | subprocess.call('printer=laser; lpr -P$printer "%s"' 73 | % self.file.name, shell=True) 74 | 75 | ################################################################################ 76 | 77 | from koko.cam.path_panels import ContourPanel 78 | 79 | INPUT = ContourPanel 80 | PANEL = EpilogOutput 81 | 82 | ################################################################################ 83 | 84 | from koko.cam.inputs.cad import CadImgPanel 85 | 86 | DEFAULTS = [ 87 | ('', {}), 88 | 89 | ('Cardboard', 90 | {CadImgPanel: [('res',5)], 91 | ContourPanel: [('diameter', 0.25)], 92 | EpilogOutput: [('power', 25), ('speed', 75), 93 | ('rate', 500), ('xmin', 0), ('ymin', 0)] 94 | } 95 | ) 96 | ] 97 | -------------------------------------------------------------------------------- /koko/cam/machines/null.py: -------------------------------------------------------------------------------- 1 | # Empty machine description file 2 | 3 | NAME = "" 4 | INPUT = None 5 | PANEL = None 6 | DEFAULTS = {} 7 | -------------------------------------------------------------------------------- /koko/cam/machines/shopbot.py: -------------------------------------------------------------------------------- 1 | NAME = 'Shopbot' 2 | 3 | import os 4 | import subprocess 5 | import tempfile 6 | 7 | import wx 8 | 9 | import koko 10 | from koko.fab.path import Path 11 | from koko.cam.panel import FabPanel, OutputPanel 12 | 13 | class ShopbotOutput(OutputPanel): 14 | """ @class ShopbotOutput 15 | @brief Panel for a three-axis Shopbot machine. 16 | """ 17 | extension = '.sbp' 18 | 19 | def __init__(self, parent): 20 | """ @brief Panel constructor 21 | @param parent Parent UI panel 22 | """ 23 | OutputPanel.__init__(self, parent) 24 | 25 | FabPanel.construct(self, 'Shopbot', [ 26 | ('Cut speed (mm/s)', 'cut_speed', float, lambda f: f > 0), 27 | ('Jog speed (mm/s)', 'jog_speed', float, lambda f: f > 0), 28 | ('Spindle speed (RPM)', 'spindle', float, lambda f: f > 0), 29 | ('Jog height (mm)', 'jog', float, lambda f: f > 0), 30 | ('Cut type', 'type', ['Conventional', 'Climb']), 31 | ('File units', 'units', ['inches', 'mm']) 32 | ]) 33 | 34 | self.construct() 35 | 36 | 37 | def run(self, paths): 38 | """ @brief Convert the path from the previous panel into a shopbot file. 39 | @param paths List of Paths 40 | """ 41 | 42 | koko.FRAME.status = 'Converting to .sbp file' 43 | 44 | values = self.get_values() 45 | if not values: return False 46 | 47 | # Reverse direction for climb cutting 48 | if values['type']: 49 | paths = Path.sort([p.reverse() for p in paths]) 50 | 51 | # Check to see if all of the z values are the same. If so, 52 | # we can use 2D cutting commands; if not, we'll need 53 | # to do full three-axis motion control 54 | zmin = paths[0].points[0][2] 55 | flat = True 56 | for p in paths: 57 | if not all(pt[2] == zmin for pt in p.points): 58 | flat = False 59 | 60 | ## @var file 61 | # tempfile.NamedTemporaryFile to store OpenSBP commands 62 | self.file = tempfile.NamedTemporaryFile(suffix=self.extension) 63 | 64 | self.file.write("SA\r\n") # plot absolute 65 | self.file.write("TR,%s,1,\r\n" % values['spindle']) # spindle speed 66 | self.file.write("SO,1,1\r\n") # set output number 1 to on 67 | self.file.write("pause,2,\r\n") # pause for spindle to spin up 68 | 69 | scale = 1 if values['units'] else 1/25.4 # mm vs inch units 70 | 71 | # Cut and jog speeds 72 | self.file.write("MS,%f,%f\r\n" % 73 | (values['cut_speed']*scale, values['cut_speed']*scale)) 74 | self.file.write("JS,%f,%f\r\n" % 75 | (values['jog_speed']*scale, values['jog_speed']*scale)) 76 | 77 | self.file.write("JZ,%f\r\n" % (values['jog']*scale)) # Move up 78 | 79 | xy = lambda x,y: (scale*x, scale*y) 80 | xyz = lambda x,y,z: (scale*x, scale*y, scale*z) 81 | 82 | 83 | for p in paths: 84 | 85 | # Move to the start of this path with the pen up 86 | self.file.write("J2,%f,%f\r\n" % xy(*p.points[0][0:2])) 87 | 88 | if flat: self.file.write("MZ,%f\r\n" % (zmin*scale)) 89 | else: self.file.write("M3,%f,%f,%f\r\n" % xyz(*p.points[0])) 90 | 91 | # Cut each point in the segment 92 | for pt in p.points: 93 | if flat: self.file.write("M2,%f,%f\r\n" % xy(*pt[0:2])) 94 | else: self.file.write("M3,%f,%f,%f\r\n" % xyz(*pt)) 95 | 96 | # Lift then pen up at the end of the segment 97 | self.file.write("MZ,%f\r\n" % (values['jog']*scale)) 98 | 99 | self.file.flush() 100 | 101 | koko.FRAME.status = '' 102 | return True 103 | 104 | 105 | ################################################################################ 106 | 107 | from koko.cam.path_panels import PathPanel 108 | 109 | INPUT = PathPanel 110 | PANEL = ShopbotOutput 111 | 112 | ################################################################################ 113 | 114 | from koko.cam.inputs.cad import CadImgPanel 115 | 116 | DEFAULTS = [ 117 | ('', {}), 118 | 119 | ('Flat cutout (1/8")', { 120 | PathPanel: [ 121 | ('diameter', 3.175), 122 | ('offsets', 1), 123 | ('overlap', ''), 124 | ('threeD', True), 125 | ('type', 'XY'), 126 | ('step', 1.5), 127 | ('depth', ''), 128 | ], 129 | CadImgPanel: 130 | [('res', 5)], 131 | ShopbotOutput: 132 | [('cut_speed', 20), 133 | ('jog_speed', 5.0), 134 | ('spindle', 10000), 135 | ('jog', 5)] 136 | } 137 | ), 138 | 139 | ('Wax rough cut (1/8")', { 140 | PathPanel: [ 141 | ('diameter', 3.175), 142 | ('offsets', -1), 143 | ('overlap', 0.25), 144 | ('threeD', True), 145 | ('type', 'XY'), 146 | ('step', 1.5), 147 | ('depth', ''), 148 | ], 149 | CadImgPanel: 150 | [('res', 5)], 151 | ShopbotOutput: 152 | [('cut_speed', 20), 153 | ('jog_speed', 5.0), 154 | ('spindle', 10000), 155 | ('jog', 5)] 156 | } 157 | ), 158 | 159 | ('Wax finish cut (1/8")', { 160 | PathPanel: 161 | [('diameter', 3.175), 162 | ('offsets', -1), 163 | ('overlap', 0.5), 164 | ('threeD', True), 165 | ('type', 'XZ + YZ'), 166 | ('step', 1.5), 167 | ('depth', ''), 168 | ], 169 | CadImgPanel: 170 | [('res', 5)], 171 | ShopbotOutput: 172 | [('cut_speed', 20), 173 | ('jog_speed', 5.0), 174 | ('spindle', 10000), 175 | ('jog', 5)] 176 | } 177 | ) 178 | ] 179 | -------------------------------------------------------------------------------- /koko/cam/machines/universal.py: -------------------------------------------------------------------------------- 1 | NAME = 'Universal laser cutter' 2 | 3 | import tempfile 4 | import subprocess 5 | 6 | import koko 7 | from koko.cam.panel import OutputPanel 8 | 9 | class UniversalOutput(OutputPanel): 10 | 11 | extension = '.uni' 12 | 13 | def __init__(self, parent): 14 | OutputPanel.__init__(self, parent) 15 | 16 | self.construct('Universal laser cutter', [ 17 | ('2D power (%)', 'power', int, lambda f: 0 <= f <= 100), 18 | ('Speed (%)', 'speed', int, lambda f: 0 <= f <= 100), 19 | ('Rate','rate', int, lambda f: f > 0), 20 | ('xmin (mm)', 'xmin', float, lambda f: f >= 0), 21 | ('ymin (mm)', 'ymin', float, lambda f: f >= 0) 22 | ], start=True) 23 | 24 | 25 | def run(self, paths): 26 | ''' Convert the path from the previous panel into an epilog 27 | laser file (with .epi suffix). 28 | ''' 29 | values = self.get_values() 30 | if not values: return False 31 | 32 | koko.FRAME.status = 'Converting to .uni file' 33 | 34 | self.file = tempfile.NamedTemporaryFile(suffix=self.extension) 35 | job_name = koko.APP.filename if koko.APP.filename else 'untitled' 36 | 37 | self.file.write("Z") # initialize 38 | self.file.write("t%s~;" % job_name) # job name 39 | self.file.write("IN;DF;PS0;DT~") # initialize 40 | self.file.write("s%c" % (values['rate']/10)) # ppi 41 | 42 | speed_hi = chr((648*values['speed']) / 256) 43 | speed_lo = chr((648*values['speed']) % 256) 44 | self.file.write("v%c%c" % (speed_hi, speed_lo)) 45 | 46 | power_hi = chr((320*values['power']) / 256) 47 | power_lo = chr((320*values['power']) % 256) 48 | self.file.write("p%c%c" % (power_hi, power_lo)) 49 | 50 | self.file.write("a%c" % 2) # air assist on high 51 | 52 | scale = 1000/25.4 # The laser's tick is 1000 DPI 53 | xoffset = values['xmin']*scale 54 | yoffset = values['ymin']*scale 55 | xy = lambda x,y: (xoffset + scale*x, yoffset + scale*y) 56 | 57 | for path in paths: 58 | self.file.write("PU;PA%d,%d;PD;" % xy(*path.points[0][0:2])) 59 | for pt in path.points[1:]: 60 | self.file.write("PA%d,%d;" % xy(*pt[0:2])) 61 | self.file.write("\n") 62 | 63 | self.file.write("e") # end of file 64 | self.file.flush() 65 | 66 | koko.FRAME.status = '' 67 | 68 | return True 69 | 70 | 71 | def send(self): 72 | subprocess.call('printer=laser; lpr -P$printer "%s"' 73 | % self.file.name, shell=True) 74 | 75 | ################################################################################ 76 | 77 | from koko.cam.path_panels import ContourPanel 78 | 79 | INPUT = ContourPanel 80 | PANEL = UniversalOutput 81 | 82 | ################################################################################ 83 | 84 | from koko.cam.inputs.cad import CadImgPanel 85 | 86 | DEFAULTS = [ 87 | ('', {}), 88 | ('Cardboard', 89 | {CadImgPanel: [('res',5)], 90 | ContourPanel: [('diameter', 0.25)], 91 | UniversalOutput: [('power', 25), ('speed', 75), 92 | ('rate', 500), ('xmin', 0), ('ymin', 0)] 93 | } 94 | ) 95 | ] 96 | -------------------------------------------------------------------------------- /koko/fab/__init__.py: -------------------------------------------------------------------------------- 1 | """ Module defining classes used in design and fabrication workflow. """ 2 | -------------------------------------------------------------------------------- /koko/fab/fabvars.py: -------------------------------------------------------------------------------- 1 | """ Module defining a container class for CAD state and settings """ 2 | 3 | import operator 4 | 5 | from koko.lib.shapes2d import color 6 | from koko.fab.tree import MathTree 7 | 8 | class FabVars(object): 9 | ''' Container class to hold CAD state and settings.''' 10 | def __init__(self): 11 | self._shapes = [] 12 | self._shape = None 13 | self.render_mode = None 14 | self.mm_per_unit = 25.4 15 | self.border = 0.05 16 | 17 | @property 18 | def shapes(self): return self._shapes 19 | @shapes.setter 20 | def shapes(self, value): 21 | if type(value) not in (list, tuple): 22 | raise TypeError('cad.shapes must be a list or tuple of MathTree objects') 23 | value = map(MathTree.wrap, value) 24 | self._shapes = list(value) 25 | self._shape = reduce(operator.add, 26 | [color(s, None) for s in self.shapes] 27 | ) 28 | 29 | @property 30 | def shape(self): return self._shape 31 | @shape.setter 32 | def shape(self, value): self.shapes = [MathTree.wrap(value)] 33 | @property 34 | def function(self): return self.shape 35 | @function.setter 36 | def function(self, value): self.shape = value 37 | 38 | @property 39 | def render_mode(self): 40 | return self._render_mode 41 | @render_mode.setter 42 | def render_mode(self, value): 43 | if value not in ['2D','3D',None]: 44 | raise TypeError("render_mode must be '2D' or '3D'") 45 | self._render_mode = value 46 | 47 | @property 48 | def mm_per_unit(self): 49 | return self._mm_per_unit 50 | @mm_per_unit.setter 51 | def mm_per_unit(self, value): 52 | try: 53 | self._mm_per_unit = float(value) 54 | except TypeError: 55 | raise TypeError("mm_per_unit should be a number.") 56 | 57 | 58 | 59 | @property 60 | def xmin(self): 61 | try: 62 | dx = (max(s.xmax for s in self.shapes) - 63 | min(s.xmin for s in self.shapes)) 64 | return min(s.xmin for s in self.shapes) - dx*self.border/2. 65 | except (TypeError, ValueError, AttributeError): return None 66 | @property 67 | def xmax(self): 68 | try: 69 | dx = (max(s.xmax for s in self.shapes) - 70 | min(s.xmin for s in self.shapes)) 71 | return max(s.xmax for s in self.shapes) + dx*self.border/2. 72 | except (TypeError, ValueError, AttributeError): return None 73 | @property 74 | def dx(self): 75 | try: return self.xmax - self.xmin 76 | except TypeError: return None 77 | 78 | @property 79 | def ymin(self): 80 | try: 81 | dy = (max(s.ymax for s in self.shapes) - 82 | min(s.ymin for s in self.shapes)) 83 | return min(s.ymin for s in self.shapes) - dy*self.border/2. 84 | except (TypeError, ValueError, AttributeError): return None 85 | @property 86 | def ymax(self): 87 | try: 88 | dy = (max(s.ymax for s in self.shapes) - 89 | min(s.ymin for s in self.shapes)) 90 | return max(s.ymax for s in self.shapes) + dy*self.border/2. 91 | except (TypeError, ValueError, AttributeError): return None 92 | @property 93 | def dy(self): 94 | try: return self.ymax - self.ymin 95 | except TypeError: return None 96 | 97 | @property 98 | def zmin(self): 99 | try: 100 | dz = (max(s.zmax for s in self.shapes) - 101 | min(s.zmin for s in self.shapes)) 102 | return min(s.zmin for s in self.shapes) - dz*self.border/2. 103 | except (TypeError, ValueError, AttributeError): return None 104 | @property 105 | def zmax(self): 106 | try: 107 | dz = (max(s.zmax for s in self.shapes) - 108 | min(s.zmin for s in self.shapes)) 109 | return max(s.zmax for s in self.shapes) + dz*self.border/2. 110 | except (TypeError, ValueError, AttributeError): return None 111 | @property 112 | def dz(self): 113 | try: return self.zmax - self.zmin 114 | except TypeError: return None 115 | 116 | @property 117 | def bounded(self): 118 | return all(getattr(self, b) is not None for b in 119 | ['xmin','xmax','ymin','ymax','zmin','zmax']) 120 | 121 | -------------------------------------------------------------------------------- /koko/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkeeter/kokopelli/c99b7909e138c42c7d5c99927f5031f021bffd77/koko/lib/__init__.py -------------------------------------------------------------------------------- /koko/lib/shapes.py: -------------------------------------------------------------------------------- 1 | from koko.lib.shapes2d import * 2 | from koko.lib.shapes3d import * 3 | -------------------------------------------------------------------------------- /koko/prims/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkeeter/kokopelli/c99b7909e138c42c7d5c99927f5031f021bffd77/koko/prims/__init__.py -------------------------------------------------------------------------------- /koko/prims/editpanel.py: -------------------------------------------------------------------------------- 1 | import wx 2 | 3 | import koko 4 | from koko.themes import APP_THEME 5 | from koko.prims.evaluator import Evaluator 6 | 7 | class EditPanel(wx.Panel): 8 | ''' Panel that allows us to edit parameters of a Primitive. 9 | Child of the global canvas instance.''' 10 | 11 | def __init__(self, target): 12 | wx.Panel.__init__(self, koko.CANVAS) 13 | 14 | self.target = target 15 | 16 | sizer = wx.FlexGridSizer( 17 | rows=len(target.PARAMETERS)+2, 18 | cols = 2) 19 | 20 | txt = wx.StaticText(self, label='type', size=(-1, 25), 21 | style=wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE) 22 | sizer.Add(txt, border=3, flag=wx.BOTTOM|wx.TOP|wx.RIGHT|wx.EXPAND) 23 | 24 | # Add this panel's class 25 | classTxt = wx.StaticText(self, size=(-1, 25), 26 | label=target.__class__.__name__) 27 | classTxt.SetFont(wx.Font(14, family=wx.FONTFAMILY_DEFAULT, 28 | style=wx.ITALIC, weight=wx.BOLD)) 29 | sizer.Add(classTxt, border=1, flag=wx.BOTTOM|wx.TOP|wx.LEFT|wx.EXPAND) 30 | 31 | boxes = [] 32 | for p in target.PARAMETERS: 33 | boxes.append(self.add_row(sizer, p)) 34 | self.update = lambda: [b.pull() for b in boxes] 35 | 36 | outer = wx.BoxSizer() 37 | outer.Add(sizer, border=10, flag=wx.ALL) 38 | self.SetSizerAndFit(outer) 39 | APP_THEME.apply(self) 40 | 41 | koko.CANVAS.Refresh() 42 | 43 | ######################################## 44 | 45 | def add_row(self, sizer, label): 46 | ''' Helper function to add a row to a sizer. 47 | 48 | Returns a TextCtrl with extra field 'label'. 49 | ''' 50 | 51 | # Create label 52 | labelTxt = wx.StaticText(self, label=label, 53 | style=wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE, 54 | size=(-1, 25)) 55 | sizer.Add(labelTxt, border=3, 56 | flag=wx.BOTTOM|wx.TOP|wx.RIGHT|wx.EXPAND) 57 | 58 | # Create input box 59 | inputBox = wx.TextCtrl(self, size=(150, 25), 60 | style=wx.NO_BORDER|wx.TE_PROCESS_ENTER) 61 | sizer.Add(inputBox, border=3, 62 | flag=wx.BOTTOM|wx.TOP|wx.LEFT|wx.EXPAND) 63 | 64 | # Add extra field to input box 65 | inputBox.label = label 66 | 67 | # Silly hack to avoid selecting all of the text when 68 | # this row gets focus. 69 | def focus(event): 70 | txt = event.GetEventObject() 71 | txt.SetSelection(0,0) 72 | if hasattr(txt, 'lastInsertionPoint'): 73 | txt.SetInsertionPoint(txt.lastInsertionPoint) 74 | del txt.lastInsertionPoint 75 | def lost_focus(event): 76 | txt = event.GetEventObject() 77 | txt.lastInsertionPoint = txt.GetInsertionPoint() 78 | 79 | inputBox.Bind(wx.EVT_SET_FOCUS, focus) 80 | inputBox.Bind(wx.EVT_KILL_FOCUS, lost_focus) 81 | 82 | # pull() synchronizes the text in the box with the 83 | # parameter or property referred to in the target object 84 | def pull(): 85 | try: 86 | a = self.target.parameters[label] 87 | if a.expr != inputBox.GetValue(): 88 | ip = inputBox.GetInsertionPoint() 89 | inputBox.SetValue(a.expr) 90 | inputBox.SetInsertionPoint(ip) 91 | except KeyError: 92 | a = getattr(self.target, label) 93 | if str(a) != inputBox.GetValue(): 94 | inputBox.SetValue(str(a)) 95 | inputBox.pull = pull 96 | 97 | # push() synchronizes the parameter expression in the 98 | # target object with the text in the input box. 99 | def push(): 100 | a = self.target.parameters[label] 101 | a.set_expr(inputBox.GetValue()) 102 | a.eval() 103 | inputBox.SetForegroundColour(APP_THEME.foreground 104 | if a.valid else 105 | wx.Colour(255, 80, 60)) 106 | inputBox.push = push 107 | 108 | inputBox.pull() 109 | 110 | # inputBox.Bind(wx.EVT_CHAR, self.char) 111 | inputBox.Bind(wx.EVT_TEXT, self.changed) 112 | inputBox.Bind(wx.EVT_TEXT_ENTER, self.target.close_panel) 113 | 114 | return inputBox 115 | 116 | ######################################## 117 | 118 | def changed(self, event): 119 | 120 | event.GetEventObject().push() 121 | koko.CANVAS.Refresh() 122 | 123 | ######################################## 124 | 125 | def slide(self): 126 | pt = (wx.Point(*koko.CANVAS.pos_to_pixel(self.target.x, self.target.y)) + 127 | wx.Point(4,4)) 128 | self.Move(pt) 129 | 130 | -------------------------------------------------------------------------------- /koko/prims/evaluator.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import koko 4 | 5 | class Evaluator(object): 6 | '''Class to do lazy evaluation of expressions.''' 7 | 8 | def __init__(self, expr, out): 9 | self.type = out 10 | self._expr = str(expr) 11 | 12 | # Add a default value for self.result 13 | self.recursing = False 14 | self.cached = False 15 | 16 | self.result = out() if out is not None else None 17 | self.eval() 18 | 19 | def eval(self): 20 | '''Evaluate the given expression. 21 | 22 | Sets self.valid to True or False depending on whether the 23 | evaluation succeeded.''' 24 | 25 | if self.cached: return self.result 26 | 27 | # Prevent recursive loops (e.g. defining pt0.x = pt0.x) 28 | if self.recursing: 29 | self.valid = False 30 | raise RuntimeError('Bad recursion') 31 | 32 | # Set a few local variables 33 | self.recursing = True 34 | self.valid = True 35 | 36 | try: 37 | c = eval(self._expr, {}, koko.PRIMS.map) 38 | except: 39 | self.valid = False 40 | else: 41 | # If we have a desired type and we got something else, 42 | # try to coerce the returned value into the desired type 43 | if self.type is not None and not isinstance(c, self.type): 44 | try: c = self.type(c) 45 | except: self.valid = False 46 | 47 | # Make sure that we haven't ended up invalid 48 | # due to bad recursion somewhere down the line 49 | if self.valid: self.result = c 50 | 51 | # We're no longer recursing, so we can unflag the variable 52 | self.recursing = False 53 | 54 | return self.result 55 | 56 | @property 57 | def expr(self): 58 | return self._expr 59 | @expr.setter 60 | def expr(self, value): 61 | self.set_expr(value) 62 | def set_expr(self, value): 63 | new = str(value) 64 | old = self._expr 65 | self._expr = new 66 | if new != old: 67 | self.cached = False 68 | koko.APP.mark_changed_design() 69 | koko.PRIMS.update_panels() 70 | 71 | ################################################################################ 72 | 73 | class NameEvaluator(Evaluator): 74 | '''Class to store valid variable names.''' 75 | def __init__(self, expr): 76 | Evaluator.__init__(self, expr, str) 77 | 78 | def eval(self): 79 | ''' Check to see that the expression is a valid variable name 80 | and return it.''' 81 | if self.cached: return self.result 82 | 83 | if self.valid_regex.match(self.expr): 84 | self.valid = True 85 | self.result = self.expr 86 | else: 87 | self.valid = False 88 | self.cached = True 89 | return self.result 90 | 91 | 92 | valid_regex = re.compile('[a-zA-Z_][0-9a-zA-Z_]*$') 93 | -------------------------------------------------------------------------------- /koko/prims/lines.py: -------------------------------------------------------------------------------- 1 | import math 2 | import wx 3 | 4 | import koko 5 | from koko.prims.core import Primitive 6 | from koko.prims.points import Point 7 | 8 | MENU_NAME = 'Lines' 9 | 10 | class Line(Primitive): 11 | ''' Defines a line terminated at two points.''' 12 | 13 | MENU_NAME = 'Line' 14 | PARAMETERS = ['name','A','B'] 15 | 16 | def __init__(self, name='line', A='pt0', B='pt1'): 17 | Primitive.__init__(self, name) 18 | self.priority = 1 19 | self.create_evaluators(A=(A, Primitive), B=(B, Primitive)) 20 | 21 | @property 22 | def x(self): return (self.A.x + self.B.x)/2. 23 | @property 24 | def y(self): return (self.A.y + self.B.y)/2. 25 | 26 | @property 27 | def hover(self): 28 | if self.A.dragging or self.B.dragging: return False 29 | x, y = koko.CANVAS.pixel_to_pos(*(wx.GetMousePosition() - 30 | koko.CANVAS.GetScreenPosition())) 31 | r = 5 / koko.CANVAS.scale 32 | return self.intersects(x, y, r) == self 33 | 34 | @classmethod 35 | def new(cls, x, y, scale): 36 | names = koko.PRIMS.get_name('pt',2) 37 | A = Point(names[0], x-scale, y) 38 | B = Point(names[1], x+scale, y) 39 | return A, B, cls(koko.PRIMS.get_name('line'), *names) 40 | 41 | def draw(self, canvas): 42 | canvas.dc.SetPen(wx.Pen((100, 150, 255), 4)) 43 | x0, y0 = canvas.pos_to_pixel(self.A.x, self.A.y) 44 | x1, y1 = canvas.pos_to_pixel(self.B.x, self.B.y) 45 | canvas.dc.DrawLine(x0, y0, x1, y1) 46 | 47 | def intersects(self, x, y, r): 48 | 49 | x0, y0 = self.A.x, self.A.y 50 | x1, y1 = self.B.x, self.B.y 51 | L = math.sqrt((x1 - x0)**2 + (y1 - y0)**2) 52 | 53 | # Find unit vectors running parallel and perpendicular to the line 54 | try: perp = ((y1 - y0)/L, -(x1 - x0)/L) 55 | except ZeroDivisionError: perp = (float('inf'), float('inf')) 56 | try: para = ((x1 - x0)/L, (y1 - y0)/L) 57 | except ZeroDivisionError: para = (float('inf'), float('inf')) 58 | 59 | para = -((x0 - x)*para[0] + (y0 - y)*para[1]) 60 | if para < -r: return None 61 | if para > L+r: return None 62 | 63 | # Perpendicular distance to line 64 | return self if abs((x0 - x)*perp[0] + 65 | (y0 - y)*perp[1]) < r else None 66 | 67 | def drag(self, dx, dy): 68 | self.A.drag(dx, dy) 69 | self.B.drag(dx, dy) 70 | 71 | 72 | -------------------------------------------------------------------------------- /koko/prims/menu.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | import wx 4 | 5 | from koko.prims.core import Primitive 6 | 7 | import koko.prims.points 8 | import koko.prims.utils 9 | import koko.prims.lines 10 | 11 | constructors = {} 12 | for _, module in inspect.getmembers(koko.prims): 13 | if not inspect.ismodule(module): continue 14 | try: menu_name = module.MENU_NAME 15 | except AttributeError: continue 16 | 17 | for _, cls in inspect.getmembers(module): 18 | if not inspect.isclass(cls) or not issubclass(cls, Primitive): 19 | continue 20 | 21 | if inspect.getmodule(cls) != module: continue 22 | 23 | try: name = cls.MENU_NAME 24 | except AttributeError: continue 25 | 26 | if not name in constructors: constructors[menu_name] = {} 27 | constructors[menu_name][name] = cls.new 28 | 29 | def show_menu(): 30 | ''' Returns a menu of constructors for a prim objects. ''' 31 | 32 | def build_from(init): 33 | ''' Decorator that calls the provided method with appropriate 34 | values for x, y, and scale. Results are added to the global 35 | PrimSet object in koko.PRIMS. ''' 36 | 37 | koko.CANVAS.mouse = (wx.GetMousePosition() - 38 | koko.CANVAS.GetScreenPosition()) 39 | koko.CANVAS.click = koko.CANVAS.mouse 40 | x, y = koko.CANVAS.pixel_to_pos(*koko.CANVAS.mouse) 41 | scale = 100/koko.CANVAS.scale 42 | 43 | p = init(x, y, scale) 44 | if type(p) is list: p = tuple(p) 45 | elif type(p) is not tuple: p = (p,) 46 | 47 | koko.CANVAS.drag_target = p[-1] 48 | for q in p: koko.PRIMS.add(q) 49 | koko.PRIMS.close_panels() 50 | 51 | menu = wx.Menu() 52 | for T in sorted(constructors): 53 | sub = wx.Menu() 54 | for name in sorted(constructors[T]): 55 | init = constructors[T][name] 56 | m = sub.Append(wx.ID_ANY, text=name) 57 | koko.CANVAS.Bind(wx.EVT_MENU, lambda e, c=init: build_from(c), m) 58 | menu.AppendMenu(wx.ID_ANY, T, sub) 59 | return menu 60 | -------------------------------------------------------------------------------- /koko/prims/points.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import koko 3 | from koko.prims.core import Primitive 4 | 5 | MENU_NAME = 'Points' 6 | 7 | class Point(Primitive): 8 | ''' Defines a basic point with intersect and draw functions.''' 9 | 10 | MENU_NAME = 'Point' 11 | PARAMETERS = ['name','x','y'] 12 | 13 | def __init__(self, name='point', x=0, y=0): 14 | Primitive.__init__(self, name) 15 | self.create_evaluators(x=(x,float), y=(y,float)) 16 | 17 | @classmethod 18 | def new(cls, x, y, scale): 19 | name = koko.PRIMS.get_name('pt') 20 | return cls(name, x, y) 21 | 22 | def drag(self, dx, dy): 23 | try: x = float(self.parameters['x'].expr) 24 | except ValueError: pass 25 | else: self.parameters['x'].expr = str(x+dx) 26 | 27 | try: y = float(self.parameters['y'].expr) 28 | except ValueError: pass 29 | else: self.parameters['y'].expr = str(y+dy) 30 | 31 | def intersects(self, x, y, r): 32 | ''' Checks whether a circle with center (x,y) and radius r 33 | intersects this point.''' 34 | distance = ((x - self.x)**2 + (y - self.y)**2)**0.5 35 | return self if distance < r else None 36 | 37 | def draw(self, canvas): 38 | ''' Draws a vertex on the given canvas. 39 | 40 | A valid point is drawn as a circle, while an invalid vertex 41 | is drawn as a red X. In each case, a highlight is drawn 42 | if the object is hovered, selected, or dragged. 43 | ''' 44 | 45 | # Find canvas-space coordinates 46 | x, y = canvas.pos_to_pixel(self.x, self.y) 47 | 48 | if self.valid: 49 | light = (200, 200, 200) 50 | dark = (100, 100, 100) 51 | else: 52 | light = (255, 80, 60) 53 | dark = (255, 0, 0) 54 | 55 | # Valid vertexs are drawn as circles 56 | if self.valid: 57 | 58 | # Draw small marks to show if we can drag the point 59 | if self.dragging or self.hover: 60 | self.draw_handles(canvas) 61 | 62 | canvas.dc.SetBrush(wx.Brush(light)) 63 | canvas.dc.SetPen(wx.Pen(dark, 2)) 64 | canvas.dc.DrawCircle(x, y, 6) 65 | 66 | # Invalid vertexs are drawn as red Xs 67 | else: 68 | r = 3 69 | if self.hover or self.dragging: 70 | canvas.dc.SetPen(wx.Pen(light, 8)) 71 | canvas.dc.DrawLine(x-r, y-r, x+r, y+r) 72 | canvas.dc.DrawLine(x-r, y+r, x+r, y-r) 73 | canvas.dc.SetPen(wx.Pen(dark, 4)) 74 | canvas.dc.DrawLine(x-r, y-r, x+r, y+r) 75 | canvas.dc.DrawLine(x-r, y+r, x+r, y-r) 76 | 77 | def draw_handles(self, canvas): 78 | ''' Draws small handles based on whether we can drag this 79 | point around on its two axes. ''' 80 | 81 | x, y = canvas.pos_to_pixel(self.x, self.y) 82 | 83 | try: float(self.parameters['x'].expr) 84 | except ValueError: x_free = False 85 | else: x_free = True 86 | 87 | try: float(self.parameters['y'].expr) 88 | except ValueError: y_free = False 89 | else: y_free = True 90 | 91 | x_light = (200, 200, 200) if x_free else (100, 100, 100) 92 | x_dark = (100, 100, 100) if x_free else(60, 60, 60) 93 | 94 | y_light = (200, 200, 200) if y_free else (100, 100, 100) 95 | y_dark = (100, 100, 100) if y_free else(60, 60, 60) 96 | 97 | 98 | canvas.dc.SetPen(wx.Pen(x_dark, 8)) 99 | canvas.dc.DrawLine(x-10, y, x+10, y) 100 | 101 | canvas.dc.SetPen(wx.Pen(y_dark, 8)) 102 | canvas.dc.DrawLine(x, y-10, x, y+10) 103 | 104 | canvas.dc.SetPen(wx.Pen(x_light, 4)) 105 | canvas.dc.DrawLine(x-10, y, x+10, y) 106 | 107 | canvas.dc.SetPen(wx.Pen(y_light, 4)) 108 | canvas.dc.DrawLine(x, y-10, x, y+10) 109 | -------------------------------------------------------------------------------- /koko/prims/utils.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import koko 3 | import math 4 | 5 | from koko.prims.core import Primitive 6 | 7 | MENU_NAME = 'Utilities' 8 | 9 | class Slider(Primitive): 10 | ''' Defines a slider that you can slide. ''' 11 | 12 | MENU_NAME = 'Slider' 13 | PARAMETERS = ['name','min','max','value','size'] 14 | 15 | ################################################################ 16 | class Handle(Primitive): 17 | def __init__(self, parent): 18 | Primitive.__init__(self, 'slider') 19 | self.parent = parent 20 | 21 | def drag(self, dx, dy): 22 | dx *= (self.parent.max - self.parent.min) / self.parent.size 23 | self.parent.parameters['value'].expr = self.parent.value + dx 24 | 25 | def intersects(self, x, y, r): 26 | if (abs(y - self.y) < 10/koko.CANVAS.scale and 27 | abs(x - self.x) < 5/koko.CANVAS.scale): 28 | return self 29 | 30 | @property 31 | def hover(self): 32 | if self.parent.dragging: return False 33 | x, y = koko.CANVAS.pixel_to_pos(*(wx.GetMousePosition() - 34 | koko.CANVAS.GetScreenPosition())) 35 | r = 5 / koko.CANVAS.scale 36 | return self.intersects(x, y, r) == self 37 | 38 | @property 39 | def x(self): 40 | d = float(self.parent.max - self.parent.min) 41 | if d: p = (self.parent.value - self.parent.min) / d 42 | else: p = 0 43 | L = self.parent.size 44 | return self.parent.x - L/2. + L*p 45 | @property 46 | def y(self): return self.parent.y 47 | 48 | def draw(self, canvas): 49 | if self.parent.value < self.parent.min: 50 | self.parent.parameters['value'].expr = self.parent.min 51 | if self.parent.value > self.parent.max: 52 | self.parent.parameters['value'].expr = self.parent.max 53 | x, y = canvas.pos_to_pixel(self.x, self.y) 54 | 55 | if self.parent.valid: 56 | highlight = (160, 160, 160) 57 | glow = (128, 128, 128, 128) 58 | light = (128, 128, 128) 59 | dark = (64, 64, 64) 60 | else: 61 | highlight = (255, 160, 140) 62 | glow = (255, 80, 60, 128) 63 | light = (255, 80, 60) 64 | dark = (255, 0, 0) 65 | 66 | if self.hover or self.dragging: 67 | self.draw_label(canvas) 68 | canvas.dc.SetBrush(wx.Brush(light)) 69 | canvas.dc.SetPen(wx.Pen(glow, 6)) 70 | canvas.dc.DrawRectangle(x - 5, y-10, 10, 20) 71 | 72 | canvas.dc.SetBrush(wx.Brush(light)) 73 | canvas.dc.SetPen(wx.Pen(dark, 2)) 74 | canvas.dc.DrawRectangle(x - 5, y-10, 10, 20) 75 | p = wx.Pen(highlight, 2) 76 | p.SetCap(wx.CAP_BUTT) 77 | canvas.dc.SetPen(p) 78 | canvas.dc.DrawLine(x+3, y-9, x+3, y+9) 79 | 80 | def draw_label(self, canvas): 81 | ''' Labels this node with its name and value.''' 82 | 83 | x, y = canvas.pos_to_pixel(self.x, self.y) 84 | 85 | canvas.dc.SetFont(wx.Font(12 + 4*self.priority, 86 | wx.FONTFAMILY_DEFAULT, 87 | wx.FONTSTYLE_NORMAL, 88 | wx.FONTWEIGHT_NORMAL)) 89 | 90 | txt = '%s: %2g' % (self.parent.name, self.parent.value) 91 | w, h = canvas.dc.GetTextExtent(txt) 92 | x -= w/2 93 | y -= 14 94 | 95 | canvas.dc.SetBrush(wx.Brush((0, 0, 0, 150))) 96 | canvas.dc.SetPen(wx.TRANSPARENT_PEN) 97 | canvas.dc.DrawRectangle(x-5, y - h - 5, w + 10, h+10) 98 | 99 | canvas.dc.SetTextForeground((255,255,255)) 100 | canvas.dc.DrawText(txt, x, y - h) 101 | 102 | def open_panel(self): self.parent.open_panel() 103 | def close_panel(self): self.parent.close_panel() 104 | 105 | ################################################################ 106 | 107 | def __init__(self, name='point', x=0, y=0, min=0, max=1, 108 | value=0.5, size=1): 109 | Primitive.__init__(self, name) 110 | self.handle = Slider.Handle(self) 111 | self.create_evaluators(x=(x,float), y=(y,float), 112 | min=(min,float), max=(max,float), 113 | value=(value, float), size=(size, float)) 114 | 115 | @classmethod 116 | def new(cls, x, y, scale): 117 | name = koko.PRIMS.get_name('slider') 118 | return cls(name, x, y, size=2.5*float('%.1f' % scale)) 119 | 120 | @property 121 | def hover(self): 122 | if self.handle.dragging: return False 123 | x, y = koko.CANVAS.pixel_to_pos(*(wx.GetMousePosition() - 124 | koko.CANVAS.GetScreenPosition())) 125 | r = 5 / koko.CANVAS.scale 126 | return self.intersects(x, y, r) == self 127 | 128 | def drag(self, dx, dy): 129 | self.parameters['x'].expr = str(self.x + dx) 130 | self.parameters['y'].expr = str(self.y + dy) 131 | 132 | def intersects(self, x, y, r): 133 | if self.handle.intersects(x, y, r): 134 | return self.handle 135 | elif abs(y - self.y) < r and abs(x - self.x) < self.size/2 + r: 136 | return self 137 | 138 | def draw(self, canvas): 139 | x, y = canvas.pos_to_pixel(self.x, self.y) 140 | w = canvas.pos_to_pixel(self.size) 141 | 142 | if self.valid: 143 | highlight = (128, 128, 128, 128) 144 | light = (128, 128, 128) 145 | dark = (64, 64, 64) 146 | else: 147 | highlight = (255, 80, 60, 128) 148 | light = (255, 80, 60) 149 | dark = (255, 0, 0) 150 | 151 | if self.hover: 152 | canvas.dc.SetPen(wx.Pen(highlight, 10)) 153 | canvas.dc.DrawLine(x - w/2, y, x+w/2, y) 154 | elif self.dragging: 155 | canvas.dc.SetPen(wx.Pen(highlight, 8)) 156 | canvas.dc.DrawLine(x - w/2, y, x+w/2, y) 157 | 158 | canvas.dc.SetPen(wx.Pen(dark, 6)) 159 | canvas.dc.DrawLine(x - w/2, y, x+w/2, y) 160 | canvas.dc.SetPen(wx.Pen(light, 4)) 161 | canvas.dc.DrawLine(x - w/2, y, x+w/2, y) 162 | 163 | self.handle.draw(canvas) 164 | -------------------------------------------------------------------------------- /koko/struct.py: -------------------------------------------------------------------------------- 1 | class Struct: 2 | """ @class Struct 3 | @brief Class with named member variables. 4 | """ 5 | def __init__(self, **entries): 6 | """ @brief Struct constructor 7 | @param entries 8 | """ 9 | self.__dict__.update(entries) 10 | 11 | def __str__(self): 12 | s = '' 13 | for d in self.__dict__: 14 | s += '%s: %s\n' % (d, self.__dict__[d]) 15 | return s[:-1] 16 | -------------------------------------------------------------------------------- /koko/template.py: -------------------------------------------------------------------------------- 1 | TEMPLATE = '''from koko.lib.shapes import * 2 | 3 | cad.shape = circle(0, 0, 0.5) 4 | ''' 5 | -------------------------------------------------------------------------------- /koko/themes.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import wx.py 3 | import wx.stc 4 | 5 | class Theme: 6 | def __init__(self, txt, background, header, foreground, txtbox): 7 | self.txt = txt 8 | self.background = background 9 | self.header = header 10 | self.foreground = foreground 11 | self.txtbox = txtbox 12 | 13 | 14 | def apply(self, target, depth=0): 15 | ''' Recursively apply the theme to a frame or sizer. ''' 16 | if isinstance(target, wx.Sizer): 17 | sizer = target 18 | else: 19 | if isinstance(target, wx.py.editwindow.EditWindow): 20 | for s in self.txt: 21 | if len(s) == 3: 22 | target.StyleSetBackground(s[0], s[1]) 23 | target.StyleSetForeground(s[0], s[2]) 24 | else: 25 | target.StyleSetBackground(s[0], self.background) 26 | target.StyleSetForeground(s[0], s[1]) 27 | elif isinstance(target, wx.TextCtrl): 28 | target.SetBackgroundColour(self.txtbox) 29 | target.SetForegroundColour(self.foreground) 30 | elif hasattr(target, 'header'): 31 | try: 32 | target.SetBackgroundColour(self.header) 33 | target.SetForegroundColour(self.foreground) 34 | except AttributeError: 35 | pass 36 | elif hasattr(target, 'immune'): 37 | pass 38 | else: 39 | try: 40 | target.SetBackgroundColour(self.background) 41 | target.SetForegroundColour(self.foreground) 42 | except AttributeError: 43 | pass 44 | sizer = target.Sizer 45 | 46 | if sizer is None: return 47 | 48 | for c in sizer.Children: 49 | if c.Window is not None: 50 | self.apply(c.Window, depth+1) 51 | elif c.Sizer is not None: 52 | self.apply(c.Sizer, depth+1) 53 | 54 | DARK_THEME = Theme( 55 | txt=[ 56 | (wx.stc.STC_STYLE_DEFAULT, '#000000', '#000000'), 57 | (wx.stc.STC_STYLE_LINENUMBER, '#303030', '#c8c8c8'), 58 | (wx.stc.STC_P_CHARACTER, '#000000', '#ff73fd'), 59 | (wx.stc.STC_P_CLASSNAME, '#000000', '#96cbfe'), 60 | (wx.stc.STC_P_COMMENTBLOCK, '#000000', '#7f7f7f'), 61 | (wx.stc.STC_P_COMMENTLINE, '#000000', '#a8ff60'), 62 | (wx.stc.STC_P_DEFAULT, '#000000', '#ffffff'), 63 | (wx.stc.STC_P_DEFNAME, '#000000', '#96cbfe'), 64 | (wx.stc.STC_P_IDENTIFIER, '#000000', '#ffffff'), 65 | (wx.stc.STC_P_NUMBER, '#000000', '#ffffff'), 66 | (wx.stc.STC_P_OPERATOR, '#000000', '#ffffff'), 67 | (wx.stc.STC_P_STRING, '#000000', '#ff73fd'), 68 | (wx.stc.STC_P_STRINGEOL, '#000000', '#ffffff'), 69 | (wx.stc.STC_P_TRIPLE, '#000000', '#ff6c60'), 70 | (wx.stc.STC_P_TRIPLEDOUBLE, '#000000', '#96cbfe'), 71 | (wx.stc.STC_P_WORD, '#000000', '#b5dcff') 72 | ], 73 | background='#252525', 74 | header='#303030', 75 | foreground='#c8c8c8', 76 | txtbox='#353535') 77 | 78 | SOLARIZED_THEME = Theme( 79 | txt=[ 80 | (wx.stc.STC_STYLE_DEFAULT, '#002b36' '#002b36'), # base00 81 | (wx.stc.STC_STYLE_LINENUMBER, '#073642','#839496'), 82 | (wx.stc.STC_P_CHARACTER, '#2aa198'), # cyan 83 | (wx.stc.STC_P_CLASSNAME, '#268bd2'), # blue 84 | (wx.stc.STC_P_COMMENTBLOCK, '#586e75'), # base01 85 | (wx.stc.STC_P_COMMENTLINE, '#586e75'), # base01 86 | (wx.stc.STC_P_DEFAULT, '#657b83'), # base00 87 | (wx.stc.STC_P_DEFNAME, '#268bd2'), # blue 88 | (wx.stc.STC_P_IDENTIFIER, '#657b83'), # base00 89 | (wx.stc.STC_P_NUMBER, '#2aa198'), # blue 90 | (wx.stc.STC_P_OPERATOR, '#657b83'), # base00 91 | (wx.stc.STC_P_STRING, '#2aa198'), # cyan 92 | (wx.stc.STC_P_STRINGEOL, '#657b83'), # base00 93 | (wx.stc.STC_P_TRIPLE, '#dc322f'), # red 94 | (wx.stc.STC_P_TRIPLEDOUBLE, '#268bd2'), # blue 95 | (wx.stc.STC_P_WORD, '#cb4b16') # green 96 | ], 97 | background='#002b36', # base03 98 | header='#073642', # base02 99 | foreground='#839496', # base0 100 | txtbox='#073642' # base02 101 | ) 102 | # http://www.zovirl.com/2011/07/22/solarized_cheat_sheet/ 103 | 104 | APP_THEME = DARK_THEME 105 | -------------------------------------------------------------------------------- /kokopelli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | print '\r'+' '*80+'\r[|---------] importing os', 5 | sys.stdout.flush() 6 | import os 7 | 8 | print '\r'+' '*80+'\r[||--------] importing wx', 9 | sys.stdout.flush() 10 | try: 11 | import wx 12 | except ImportError: 13 | print "kokopelli error: wx import failed!" 14 | sys.exit(1) 15 | else: 16 | if float(wx.version()[:3]) < 2.9: 17 | print "\n\nkokopelli error: wxPython 2.9 or 3.0 required" 18 | if 'Linux' in os.uname(): 19 | print "Run the script named install_wxpython3.0.sh in the util folder" 20 | print "to automatically build and install the newer version.\n" 21 | elif 'Darwin' in os.uname(): 22 | print "Download wxPython3.0-osx-cocoa-py2.7 from" 23 | print "http://wxpython.org/download.php and install it.\n" 24 | sys.exit(1) 25 | print '\r'+' '*80+'\r[|||||-----] importing koko', 26 | sys.stdout.flush() 27 | 28 | 29 | import koko 30 | koko.BASE_DIR = os.path.abspath(os.getcwd())+'/' 31 | if '.app' in sys.argv[0]: 32 | koko.BUNDLED = True 33 | sys.path.append('') 34 | os.chdir(koko.BASE_DIR+'../../..') 35 | else: 36 | koko.BUNDLED = False 37 | 38 | while len(sys.argv) > 1: 39 | if sys.argv[1] == '--debug': 40 | import subprocess 41 | python = 'python-64' if 'Darwin' in os.uname() else 'python' 42 | subprocess.call(['gdb', '--quiet', 43 | '--eval-command', 44 | 'run %s %s' % (sys.argv[0], ' '.join(sys.argv[2:])), 45 | '--eval-command', 'cont', 46 | '--eval-command', 'quit', python]) 47 | sys.exit(0) 48 | elif sys.argv[1] in ['--help', '-h']: 49 | print '''Usage: 50 | kokopelli [--help|-h] [FILENAME]' 51 | 52 | Options: 53 | --help Print this message and exit 54 | 55 | Arguments: 56 | FILENAME Target file to open''' 57 | sys.exit(0) 58 | else: 59 | break 60 | 61 | 62 | print '\r'+' '*80+'\r[||||||----] importing koko.app', 63 | sys.stdout.flush() 64 | from koko.app import App 65 | 66 | 67 | if __name__ == '__main__': 68 | print '\r'+' '*80+'\r[||||||||||] starting...', 69 | sys.stdout.flush() 70 | wx.Log.EnableLogging(False) 71 | app = App() 72 | print '\r'+' '*80+'\r', 73 | sys.stdout.flush() 74 | app.MainLoop() 75 | -------------------------------------------------------------------------------- /libfab/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(libfab) 3 | 4 | #set(CMAKE_BUILD_TYPE DEBUG) 5 | set(CMAKE_BUILD_TYPE RELEASE) 6 | 7 | set(CMAKE_C_FLAGS "-Wall -g -fPIC -pedantic -std=gnu99") 8 | set(CMAKE_C_FLAGS_RELEASE "-O3 -D '_STATIC_=static'") 9 | set(CMAKE_C_FLAGS_DEBUG "-O0 -D '_STATIC_= '") 10 | 11 | find_package(PNG REQUIRED) 12 | include_directories(${PNG_INCLUDE_DIR}) 13 | find_library(M_LIB m) 14 | 15 | include_directories(.) 16 | 17 | add_library(fab SHARED 18 | asdf/asdf.c asdf/render.c asdf/file_io.c 19 | asdf/triangulate.c asdf/import.c asdf/cache.c 20 | asdf/neighbors.c asdf/contour.c asdf/distance.c 21 | asdf/cms.c 22 | 23 | tree/eval.c tree/render.c 24 | tree/tree.c tree/packed.c 25 | tree/parser.c 26 | 27 | tree/math/math_f.c tree/math/math_i.c tree/math/math_r.c 28 | 29 | tree/node/node.c tree/node/opcodes.c 30 | tree/node/printers.c tree/node/results.c 31 | 32 | cam/toolpath.c cam/distance.c cam/slices.c 33 | 34 | formats/png_image.c formats/stl.c formats/mesh.c 35 | 36 | util/region.c util/vec3f.c util/path.c 37 | ) 38 | 39 | target_link_libraries(fab ${PNG_LIBRARY} ${M_LIB}) 40 | install(TARGETS fab DESTINATION ${PROJECT_SOURCE_DIR}) 41 | -------------------------------------------------------------------------------- /libfab/asdf/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef CACHE_H 2 | #define CACHE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util/region.h" 9 | #include "util/vec3f.h" 10 | 11 | struct ASDF_; 12 | 13 | /** @struct Corner_ 14 | @brief A tree-based cache indexed by i, j, k 15 | position in a discrete lattice. 16 | 17 | @details 18 | Each cache has a depth value. For any given i, j, k value, the 19 | corresponding cache corner is reached when bits below the depth-th 20 | bit (counting from the top) are all zeros. 21 | 22 | For example (showing only the top eight bits)\n 23 | i = 0b11010000, j = 0b10111000, k = 0b00000000\n 24 | will be at a depth of 5 (j is the limiting factor) 25 | */ 26 | typedef struct Corner_ { 27 | /** @var depth 28 | Cache recursion level */ 29 | uint8_t depth; 30 | /** @var value 31 | Sample value (or NAN if uninitialized)*/ 32 | float value; 33 | 34 | /** @var branches 35 | Recursive branches (or NULL if uninitialized) */ 36 | struct Corner_* branches[8]; 37 | } Corner; 38 | 39 | 40 | /** @brief Recursively frees a corner cache 41 | */ 42 | void free_corner_cache(Corner* const cache); 43 | 44 | 45 | /** @brief Writes a cache out to a file 46 | @details Cache values are discretized to 16 bit integers 47 | */ 48 | void write_cache(const Corner* const cache, FILE* file); 49 | 50 | 51 | /** @brief Reads a cache in from a file 52 | */ 53 | Corner* read_cache(FILE* file); 54 | 55 | 56 | 57 | /** @brief Returns the corner corresponding to the given i, j, k position. 58 | @details The corner may be uninitialized (with value of NAN) 59 | */ 60 | Corner* get_corner(Corner* const cache, 61 | const uint16_t i, 62 | const uint16_t j, 63 | const uint16_t k); 64 | 65 | 66 | /** @brief Returns a subcache containing the given region 67 | */ 68 | Corner* corner_subcache(Corner* const cache, 69 | const uint16_t imin, const uint16_t imax, 70 | const uint16_t jmin, const uint16_t jmax, 71 | const uint16_t kmin, const uint16_t kmax); 72 | 73 | 74 | /** @brief Creates a populated cache with corner values of ASDF leaf cells. 75 | */ 76 | Corner* fill_corner_cache(const struct ASDF_* const asdf); 77 | 78 | /** @brief Creates a populated cache with corner values of ASDF leaf, empty, and full cells. 79 | */ 80 | Corner* fill_corner_cache_all(const struct ASDF_* const asdf); 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /libfab/asdf/cms.h: -------------------------------------------------------------------------------- 1 | #ifndef ASDF_CMS_H 2 | #define ASDF_CMS_H 3 | 4 | struct ASDF_; 5 | struct Mesh_; 6 | 7 | /** @brief Triangulates an ASDF using cubical marching squares. */ 8 | struct Mesh_* triangulate_cms(struct ASDF_* const asdf); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /libfab/asdf/contour.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTOUR_H 2 | #define CONTOUR_H 3 | 4 | struct ASDF_; 5 | struct Path_; 6 | 7 | /** @brief Finds ASDF contours 8 | @details paths can be dereferenced to get an array of path pointers 9 | It may be reallocated to increase the storage size. 10 | @param asdf ASDF to contour 11 | @param paths Pointer to an array of path pointers 12 | @returns The number of paths stored. 13 | */ 14 | int contour(struct ASDF_* const asdf, 15 | struct Path_*** const paths, volatile int* const halt); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /libfab/asdf/distance.h: -------------------------------------------------------------------------------- 1 | #ifndef DISTANCE_H 2 | #define DISTANCE_H 3 | 4 | struct ASDF_*; 5 | 6 | /** @brief Offsets an ASDF by the given distance 7 | @param asdf ASDF to offset 8 | @param offset Distance to offset (in mm, which are the ASDFs native units) 9 | @param pixels_per_mm Lattice resolution to use while offsetting 10 | @returns An offset ASDF 11 | */ 12 | struct ASDF_* asdf_offset( 13 | const struct ASDF_* const asdf, float offset, float pixels_per_mm); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /libfab/asdf/file_io.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_IO_H 2 | #define FILE_IO_H 3 | 4 | #include 5 | 6 | struct ASDF_; 7 | 8 | /** @brief Saves an ASDF to a file 9 | @param asdf Pointer to an ASDF 10 | @param filename Name of file 11 | */ 12 | void asdf_write(struct ASDF_* const asdf, const char* filename); 13 | 14 | /** @brief Loads an ASDF from a file 15 | @param filename Filename 16 | @returns The loaded ASDF 17 | */ 18 | struct ASDF_* asdf_read(const char* filename); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /libfab/asdf/import.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_H 2 | #define IMPORT_H 3 | 4 | #include 5 | 6 | #include "util/region.h" 7 | 8 | struct ASDF_; 9 | 10 | /** @brief Converts a .vol file into an ASDF 11 | @param filename Target filename 12 | @param ni Number of samples on X axis 13 | @param nj Number of samples on Y axis 14 | @param nk Number of samples on Z axis 15 | @param offset Isosurface density value 16 | @param mm_per_voxel Voxel scale 17 | @param merge_leafs Boolean determining whether leaf cells are combined. 18 | @param close_border Boolean determining if border samples are set to zero. 19 | */ 20 | struct ASDF_* import_vol(const char* filename, 21 | const int ni, const int nj, const int nk, 22 | const float offset, const float mm_per_voxel, 23 | const _Bool merge_leafs, const _Bool close_border); 24 | 25 | /** @brief Imports a region within a .vol file 26 | @details Recurses until the region size will fill less than 100 MB in RAM, then loads the relevant samples from the file and recursively constructs tree. 27 | @param filename Target filename 28 | @param ni Number of samples (in full region) on X axis 29 | @param nj Number of samples (in full region) on Y axis 30 | @param nk Number of samples (in full region) on Z axis 31 | @param r Target region to import (should be within full region lattice) 32 | @param shift Sampling level 33 | @param offset Isosurface density value 34 | @param merge_leafs Boolean determining whether leaf cells are combined. 35 | @param close_border Boolean determining if border samples are set to zero. 36 | */ 37 | struct ASDF_* import_vol_region( 38 | const char* filename, const int ni, const int nj, const int nk, 39 | const Region r, const int shift, const float offset, 40 | const _Bool merge_leafs, const _Bool close_border); 41 | 42 | 43 | /** @brief Imports a 2D lattice 44 | @param distances 2D array of distance samples 45 | @param ni Number of samples in X direction 46 | @param nj Number of samples in Y direction 47 | @param offset Isosurface distance values 48 | @param mm_per_pixel Lattice scale factor 49 | @param merge_leafs Boolean determining whether leaf cells are combined. 50 | */ 51 | struct ASDF_* import_lattice(float const*const*const distances, 52 | const int ni, const int nj, const float offset, 53 | const float mm_per_pixel, _Bool merge_leafs); 54 | 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /libfab/asdf/neighbors.h: -------------------------------------------------------------------------------- 1 | #ifndef NEIGHBORS_H 2 | #define NEIGHBORS_H 3 | 4 | #include 5 | 6 | struct ASDF_; 7 | 8 | /** @brief Returns a neighboring ASDF. 9 | @details The returned ASDF will exactly match the child ASDF's 10 | face. If the child ASDF's face is a subset of another's face, then 11 | we return NULL. If the child ASDF's face intersects another's face, 12 | returns a virtual ASDF with no branches and d = {split, nsplit, axis}. 13 | 14 | @param asdf Parent ASDF 15 | @param branch ID of child ASDF branch 16 | @param face ID of target face (in order -z, +z, -y, +y, -x, +x) 17 | @param neighbor Neighbor of parent ASDF on this face 18 | */ 19 | const ASDF* get_neighbor_v( 20 | const struct ASDF_* const asdf, const uint8_t branch, 21 | const uint8_t face, const struct ASDF_* const neighbor); 22 | 23 | /** @brief Populates 'new' with the six neighbors of branch b of the asdf 24 | in the order -z, +z, -y, +y, -x, +x 25 | 26 | @details Neighbors are saved if they are at the same scale. Virtual 27 | ASDFs are constructed to prevent subtle cracks in multiscale neighbors. 28 | 29 | @param asdf Top-level ASDF 30 | @param old Neighbors of top-level ASDF 31 | @param new Array to fill with new neighbors 32 | @param b Branch of top-level ASDF to examine (0-8) 33 | */ 34 | void get_neighbors_v(const struct ASDF_* asdf, 35 | const struct ASDF_* const old[6], 36 | const struct ASDF_* new[6], uint8_t b); 37 | 38 | 39 | /** @brief Populates 'new' with the six neighbors of branch b of the asdf 40 | in the order -z, +z, -y, +y, -x, +x 41 | 42 | @details Neighbors are only saved if they are at the same scale, 43 | to prevent cracks. 44 | 45 | @param asdf Top-level ASDF 46 | @param old Neighbors of top-level ASDF 47 | @param new Array to fill with new neighbors 48 | @param b Branch of top-level ASDF to examine (0-8) 49 | */ 50 | void get_neighbors_3d(const struct ASDF_* asdf, 51 | const struct ASDF_* const old[6], 52 | const struct ASDF_* new[6], uint8_t b); 53 | 54 | 55 | /** @brief Populates 'new' with the six neighbors of branch b of the asdf 56 | in the order -z, +z, -y, +y, -x, +x 57 | 58 | @details Neighbors at different scales are still recorded, because in 2D 59 | you can't get cracks from marching square (assuming a well-constructed 60 | distance tree). 61 | 62 | @param asdf Top-level ASDF 63 | @param old Neighbors of top-level ASDF 64 | @param new Array to fill with new neighbors 65 | @param b Branch of top-level ASDF to examine (0-8) 66 | */ 67 | void get_neighbors_2d(const struct ASDF_* asdf, 68 | const struct ASDF_* const old[4], 69 | const struct ASDF_* new[4], uint8_t b); 70 | 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /libfab/asdf/render.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDER_H 2 | #define RENDER_H 3 | 4 | #include "util/region.h" 5 | 6 | // Forward declaration of ASDF structure 7 | struct ASDF_; 8 | 9 | //////////////////////////////////////////////////////////////////////////////// 10 | 11 | /** @brief Renders an ASDF to a height-map image 12 | @details (might be broken at the moment) 13 | @param asdf ASDF to render 14 | @param r_ Render region 15 | @param M Array of rotation parameters [cos(a), sin(a), cos(b), sin(b)] 16 | @param depth Height-map lattice to populate 17 | */ 18 | void render_asdf(const struct ASDF_* const asdf, const Region r_, 19 | const float M[4], uint16_t*const*const depth); 20 | 21 | //////////////////////////////////////////////////////////////////////////////// 22 | 23 | /** @brief Renders an ASDF to a height-map image, shaded, and normals image 24 | @param asdf ASDF to render 25 | @param r_ Render region (ni, nj must be lattice dimensions) 26 | @param M Array of rotation parameters [cos(a), sin(a), cos(b), sin(b)] 27 | @param depth Height-map lattice to populate 28 | @param shaded Shaded image to populate with shaded render 29 | @param normals RGB image to populate with colored normals 30 | */ 31 | void render_asdf_shaded(const struct ASDF_* const asdf, const Region r_, 32 | const float M[4], uint16_t*const*const depth, 33 | uint16_t*const*const shaded, uint8_t (**normals)[3]); 34 | 35 | //////////////////////////////////////////////////////////////////////////////// 36 | 37 | /** @brief Draws the outline of ASDF cells on an image. 38 | @param a ASDF to render 39 | @param r Region to render (ni, nj must be lattice dimensions) 40 | @param img RGB image on which outlines will be drawn 41 | */ 42 | void draw_asdf_cells(ASDF* a, Region r, uint8_t (**img)[3]); 43 | 44 | //////////////////////////////////////////////////////////////////////////////// 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /libfab/asdf/triangulate.h: -------------------------------------------------------------------------------- 1 | #ifndef GL_H 2 | #define GL_H 3 | 4 | #include 5 | 6 | #include "util/region.h" 7 | #include "util/vec3f.h" 8 | 9 | struct ASDF_; 10 | struct Edge_; 11 | struct Mesh_; 12 | 13 | 14 | /** @brief Recursively triangulates an ASDF 15 | @details Vertices (and vertex normals) are stored in the array vdata as six consecutive values (3 values each for position and normal) 16 | 17 | vcount keeps track of how many floats have been place in the array 18 | (so it should always be a factor of 6). 19 | 20 | Triangles are stored as sets of three array indices, with icount recording 21 | the number of indices saved. Note that you have to multiply an index by 6 to gets its actual position in the array. 22 | */ 23 | struct Mesh_* triangulate(struct ASDF_* const asdf, volatile int* const halt); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /libfab/cam/distance.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "cam/distance.h" 7 | 8 | // Euclidean distance evaluation function 9 | unsigned f_edt(const int x, const int i, const int32_t*const g) 10 | { 11 | return (x-i)*(x-i) + g[i]*g[i]; 12 | } 13 | 14 | 15 | // Euclidean distance separator function 16 | unsigned sep_edt(const int i, const int u, const int32_t*const g) 17 | { 18 | return (u*u - i*i + g[u]*g[u] - g[i]*g[i]) / (2*(u-i)); 19 | } 20 | 21 | 22 | void distance_transform1(const int imin, const int imax, 23 | const int ni, const int nj, 24 | uint8_t const*const*const img, int32_t** const g) 25 | { 26 | // printf("Running G on %i %i\n", imin, imax); 27 | for (int i=imin; i < imax; ++i) { 28 | g[0][i] = img[0][i] ? 0 : (ni+nj); 29 | 30 | // Sweep down 31 | for (int j=1; j < nj; ++j) { 32 | g[j][i] = img[j][i] ? 0 : (g[j-1][i]+1); 33 | } 34 | 35 | // Sweep up 36 | for (int j=nj-2; j >= 0; --j) { 37 | if (g[j+1][i] < g[j][i]) { 38 | g[j][i] = g[j+1][i]+1; 39 | } 40 | } 41 | } 42 | // printf("Done running G on %i %i\n", imin, imax); 43 | } 44 | 45 | void distance_transform2(const int jmin, const int jmax, 46 | const int ni, const float pixels_per_mm, 47 | int32_t** const g, 48 | float* const*const distances) 49 | { 50 | // printf("Running D on %i %i\n", jmin, jmax); 51 | 52 | // Starting points of each region 53 | unsigned t[ni]; 54 | unsigned s[ni]; 55 | 56 | for (int j=jmin; j < jmax; ++j) { 57 | int q=0; // Number of regions found so far 58 | s[0] = 0; 59 | t[0] = 0; 60 | for (int u=1; u < ni; ++u) { 61 | 62 | // Slide q backwards until we find a point where the 63 | // curve to be added is above the previous curve. 64 | while (q >= 0 && f_edt(t[q],s[q],g[j]) > f_edt(t[q],u,g[j])) { 65 | q--; 66 | } 67 | 68 | // If the new segment is below all previous curves, 69 | // then replace them all 70 | if (q < 0) { 71 | q = 0; 72 | s[0] = u; 73 | } 74 | 75 | // Otherwise, find the starting point for the new segment and 76 | // save it if it's within the bounds of the image. 77 | else { 78 | unsigned w = 1 + sep_edt(s[q], u, g[j]); 79 | if (w < ni) { 80 | q++; 81 | s[q] = u; 82 | t[q] = w; 83 | } 84 | } 85 | } 86 | 87 | // Finally, calculate and store distance values. 88 | for (int u = ni-1; u >= 0; --u) { 89 | distances[j][u] = sqrt(f_edt(u, s[q], g[j])) / pixels_per_mm; 90 | if (u == t[q]) q--; 91 | } 92 | } 93 | // printf("Done running D on %i %i\n", jmin, jmax); 94 | } 95 | 96 | 97 | void distance_transform(const int ni, const int nj, 98 | const float pixels_per_mm, 99 | uint8_t const*const*const img, // Input 100 | float* const*const distances) // Output 101 | { 102 | 103 | int** const g = malloc(nj*sizeof(int*)); 104 | for (int j=0; j < nj; ++j) g[j] = malloc(ni*sizeof(int)); 105 | 106 | // Calculate g[j][i] 107 | for (int i=0; i < ni; ++i) { 108 | // Initialize the top point in this column 109 | g[0][i] = img[0][i] ? 0 : (ni+nj); 110 | 111 | // Sweep down 112 | for (int j=1; j < nj; ++j) { 113 | g[j][i] = img[j][i] ? 0 : (g[j-1][i]+1); 114 | } 115 | 116 | // Sweep up 117 | for (int j=nj-2; j >= 0; --j) { 118 | if (g[j+1][i] < g[j][i]) { 119 | g[j][i] = g[j+1][i]+1; 120 | } 121 | } 122 | } 123 | 124 | // Starting points of each region 125 | unsigned t[ni]; 126 | unsigned s[ni]; 127 | 128 | for (int j=0; j < nj; ++j) { 129 | int q=0; // Number of regions found so far 130 | s[0] = 0; 131 | t[0] = 0; 132 | for (int u=1; u < ni; ++u) { 133 | 134 | // Slide q backwards until we find a point where the 135 | // curve to be added is above the previous curve. 136 | while (q >= 0 && f_edt(t[q],s[q],g[j]) > f_edt(t[q],u,g[j])) { 137 | q--; 138 | } 139 | 140 | // If the new segment is below all previous curves, 141 | // then replace them all 142 | if (q < 0) { 143 | q = 0; 144 | s[0] = u; 145 | } 146 | 147 | // Otherwise, find the starting point for the new segment and 148 | // save it if it's within the bounds of the image. 149 | else { 150 | unsigned w = 1 + sep_edt(s[q], u, g[j]); 151 | if (w < ni) { 152 | q++; 153 | s[q] = u; 154 | t[q] = w; 155 | } 156 | } 157 | } 158 | 159 | // Finally, calculate and store distance values. 160 | for (int u = ni-1; u >= 0; --u) { 161 | distances[j][u] = sqrt(f_edt(u, s[q], g[j])) / pixels_per_mm; 162 | if (u == t[q]) q--; 163 | } 164 | } 165 | 166 | for (int j=0; j < nj; ++j) free(g[j]); 167 | free(g); 168 | } 169 | -------------------------------------------------------------------------------- /libfab/cam/distance.h: -------------------------------------------------------------------------------- 1 | #ifndef DISTANCE_H 2 | #define DISTANCE_H 3 | 4 | #include 5 | 6 | /** @brief Computes the Euclidean distance transform of the input image. 7 | @details Non-zero parts of the input image are considered filled. 8 | Output values in the distance array are in mm, scaled based on the 9 | pixels_per_mm input argument. 10 | 11 | @param ni Image width 12 | @param nj Image height 13 | @param pixels_per_mm Image scale 14 | @param img Image lattice 15 | @param distances Output lattice 16 | */ 17 | void distance_transform(const int ni, const int nj, 18 | const float pixels_per_mm, 19 | uint8_t const*const*const img, // Input 20 | float* const*const distances); // Output 21 | 22 | 23 | /** @brief Performs the first stage of the Meijster distance transform. 24 | @details Non-zero parts of the input image are considered filled. 25 | 26 | @param imin Start of region to process (column number) 27 | @param imax End of region (column number) 28 | @param ni Image width 29 | @param nj Image height 30 | @param img Image lattice 31 | @param g Output lattice (minimum vertical distance) 32 | */ 33 | void distance_transform1(const int imin, const int imax, 34 | const int ni, const int nj, 35 | uint8_t const*const*const img, int32_t** const g); 36 | 37 | /** @brief Performs the second stage of the Meijster distance transform. 38 | @details Output values in the distances array are in mm, scaled based on the 39 | pixels_per_mm input argument. 40 | 41 | @param jmin Start of region to process (row number) 42 | @param jmax End of region (row number) 43 | @param ni Image width 44 | @param nj Image height 45 | @param pixels_per_mm Image scale 46 | @param g Input G lattice (from first stage of transform) 47 | @param distances Output lattice 48 | */ 49 | void distance_transform2(const int jmin, const int jmax, 50 | const int ni, const float pixels_per_mm, 51 | int32_t** const g, 52 | float* const*const distances); 53 | 54 | 55 | /** @brief Euclidean distance evaluation function 56 | @details See Meijster's 2000 paper for details. 57 | */ 58 | unsigned f_edt(const int x, const int i, const int*const g); 59 | 60 | 61 | /** @brief Euclidean distance separator 62 | @details See Meijster's 2000 paper for details. 63 | */ 64 | unsigned sep_edt(const int i, const int u, const int*const g); 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /libfab/cam/slices.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Slices for 3d printing and other fun stuff 5 | 6 | #include "cam/slices.h" 7 | #include "cam/distance.h" 8 | 9 | // These values are assigned to the current slice 10 | #define SUPPORTED 255 11 | #define NEEDS_SUPPORT 200 12 | #define CANNOT_SUPPORT 100 13 | 14 | // These values are defined for the previous slice 15 | #define FILLED 255 16 | #define BLOCKED 100 17 | #define UNBLOCKED 0 18 | 19 | void find_support(const int ni, const int nj, 20 | uint8_t const*const*const prev, 21 | uint8_t* const*const curr) 22 | { 23 | for (int j=0; j < nj; ++j) { 24 | for (int i=0; i < ni; ++i) { 25 | if (!curr[j][i]) 26 | continue; 27 | else if (prev[j][i] == FILLED) 28 | curr[j][i] = SUPPORTED; 29 | else if (prev[j][i] == UNBLOCKED) 30 | curr[j][i] = NEEDS_SUPPORT; 31 | else if (prev[j][i] == BLOCKED) 32 | curr[j][i] = CANNOT_SUPPORT; 33 | } 34 | } 35 | } 36 | 37 | 38 | void colorize_slice(const int ni, const int nj, 39 | uint8_t const*const*const curr, 40 | uint8_t (**out)[3]) 41 | { 42 | for (int j=0; j < nj; ++j) { 43 | for (int i=0; i < ni; ++i) { 44 | if (curr[j][i] == SUPPORTED) { 45 | out[j][i][0] = 255; 46 | out[j][i][1] = 255; 47 | out[j][i][2] = 255; 48 | } else if (curr[j][i] == NEEDS_SUPPORT) { 49 | out[j][i][1] = 255; 50 | } else if (curr[j][i] == CANNOT_SUPPORT) { 51 | out[j][i][0] = 255; 52 | } 53 | } 54 | } 55 | } 56 | 57 | 58 | void next_slice(const int ni, const int nj, 59 | const float pixels_per_mm, 60 | const float support_offset, 61 | uint8_t const*const*const prev, 62 | uint8_t* const*const curr) 63 | { 64 | float** const d = malloc(nj*sizeof(float*)); 65 | for (int j=0; j < nj; ++j) d[j] = malloc(ni*sizeof(float)); 66 | 67 | distance_transform(ni, nj, pixels_per_mm, prev, d); 68 | 69 | 70 | for (int j=0; j < nj; ++j) { 71 | for (int i=0; i < ni; ++i) { 72 | if (curr[j][i] || d[j][i] < support_offset) 73 | curr[j][i] = FILLED; 74 | else if (prev[j][i] == BLOCKED || prev[j][i] == FILLED) 75 | curr[j][i] = BLOCKED; 76 | } 77 | } 78 | 79 | for (int j=0; j < nj; ++j) free(d[j]); 80 | free(d); 81 | } 82 | -------------------------------------------------------------------------------- /libfab/cam/slices.h: -------------------------------------------------------------------------------- 1 | #ifndef SLICES_H 2 | #define SLICES_H 3 | 4 | #include 5 | 6 | /* find_support 7 | * 8 | * Determines whether or not a pixel is supported. 9 | * 'curr' should be a binary input image with pixels set to 10 | * either 0 or 255. After running, the pixels will be set to 11 | * SUPPORTED, NEEDS_SUPPORT, or CANNOT_SUPPORT. 12 | */ 13 | void find_support(const int ni, const int nj, 14 | uint8_t const*const*const prev, 15 | uint8_t* const*const curr); 16 | 17 | /* next_slice 18 | * 19 | * Modifies 'curr' so that it can be used as 'prev' in find_support 20 | * 'curr' should be an lattice where solid is represented by 21 | * non-zero pixels. After running, its pixels will be set to 22 | * FILLED, BLOCKED, and UNBLOCKED as appropriate. 23 | * 24 | * support_offset is the maximum horizontal offset at which a lower 25 | * layer can support an upper layer (measured in mm) 26 | */ 27 | void next_slice(const int ni, const int nj, 28 | const float pixels_per_mm, 29 | const float support_offset, 30 | uint8_t const*const*const prev, 31 | uint8_t* const*const curr); 32 | #endif 33 | -------------------------------------------------------------------------------- /libfab/cam/toolpath.h: -------------------------------------------------------------------------------- 1 | #ifndef TOOLPATH_H 2 | #define TOOLPATH_H 3 | 4 | struct Path_; 5 | 6 | /** @brief Extracts a set of contours from a distance image 7 | @param ni Image width 8 | @param nj Image height 9 | @param distance Distance field (with distances in mm) 10 | @param mm_per_pixel Lattice scale factor 11 | @param num_contours Number of contours to extract 12 | @param contour_levels List of contour levels 13 | @param paths Pointer to an array of path pointers 14 | @details paths can be dereferenced to get an array of path pointers. 15 | It may be reallocated to increase storage size. 16 | @returns The number of paths stored. 17 | */ 18 | int find_paths(const int ni, const int nj, 19 | float const*const*const distances, 20 | const float mm_per_pixel, const int num_contours, 21 | const float* const contour_levels, 22 | struct Path_*** const paths); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /libfab/formats/mesh.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "formats/mesh.h" 7 | 8 | void free_mesh(Mesh* mesh) 9 | { 10 | free(mesh->vdata); 11 | free(mesh->tdata); 12 | free(mesh); 13 | } 14 | 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | void mesh_reserve_t(Mesh* const mesh, const uint32_t tcount) 18 | { 19 | while (mesh->talloc < tcount) { 20 | if (mesh->talloc) mesh->talloc *= 2; 21 | else mesh->talloc = 2; 22 | mesh->tdata = realloc( 23 | mesh->tdata, sizeof(*mesh->tdata)*3*mesh->talloc 24 | ); 25 | } 26 | } 27 | 28 | void mesh_reserve_v(Mesh* const mesh, const uint32_t vcount) 29 | { 30 | while (mesh->valloc < vcount) { 31 | if (mesh->valloc) mesh->valloc *= 2; 32 | else mesh->valloc = 2; 33 | mesh->vdata = realloc( 34 | mesh->vdata, sizeof(*mesh->vdata)*6*mesh->valloc 35 | ); 36 | } 37 | } 38 | 39 | 40 | void increase_indices(Mesh* const mesh, uint32_t di) 41 | { 42 | for (uint32_t t = 0; t < mesh->tcount; ++t) { 43 | mesh->tdata[t*3] += di; 44 | mesh->tdata[t*3+1] += di; 45 | mesh->tdata[t*3+2] += di; 46 | } 47 | } 48 | 49 | //////////////////////////////////////////////////////////////////////////////// 50 | 51 | void save_mesh(const char* filename, const Mesh* const mesh) 52 | { 53 | FILE* f = fopen(filename, "wb"); 54 | 55 | float header_bounds[] = {mesh->X.lower, mesh->X.upper, 56 | mesh->Y.lower, mesh->Y.upper, 57 | mesh->Z.lower, mesh->Z.upper}; 58 | 59 | // Write out the mesh's bounds 60 | for (int h=0; h < sizeof(header_bounds); ++h) { 61 | fputc(((char*)&header_bounds)[h], f); 62 | } 63 | 64 | // Write out the triangle count 65 | for (int t=0; t < sizeof(mesh->tcount); ++t) { 66 | fputc(((char*)&mesh->tcount)[t], f); 67 | } 68 | 69 | // Write out the vertex count 70 | for (int v=0; v < sizeof(mesh->vcount); ++v) { 71 | fputc(((char*)&mesh->vcount)[v], f); 72 | } 73 | 74 | // Write the buffers to file 75 | fwrite((void*)mesh->tdata, sizeof(mesh->tdata[0])*3, mesh->tcount, f); 76 | fwrite((void*)mesh->vdata, sizeof(mesh->vdata[0])*6, mesh->vcount, f); 77 | 78 | fclose(f); 79 | } 80 | 81 | //////////////////////////////////////////////////////////////////////////////// 82 | 83 | Mesh* load_mesh(const char* filename) 84 | { 85 | FILE* f = fopen(filename, "rb"); 86 | 87 | Mesh* const mesh = calloc(1, sizeof(Mesh)); 88 | 89 | // Read in the header 90 | fscanf(f, "%4c", (char*)(&(mesh->X.lower))); 91 | fscanf(f, "%4c", (char*)(&(mesh->X.upper))); 92 | fscanf(f, "%4c", (char*)(&(mesh->Y.lower))); 93 | fscanf(f, "%4c", (char*)(&(mesh->Y.upper))); 94 | fscanf(f, "%4c", (char*)(&(mesh->Z.lower))); 95 | fscanf(f, "%4c", (char*)(&(mesh->Z.upper))); 96 | 97 | fscanf(f, "%4c", (char*)&(mesh->tcount)); 98 | fscanf(f, "%4c", (char*)&(mesh->vcount)); 99 | 100 | // Allocate space for the mesh data 101 | mesh_reserve_v(mesh, mesh->vcount); 102 | mesh_reserve_t(mesh, mesh->tcount); 103 | 104 | // Load all the data 105 | fread((void*)mesh->tdata, sizeof(*mesh->tdata)*3, mesh->tcount, f); 106 | fread((void*)mesh->vdata, sizeof(*mesh->vdata)*6, mesh->vcount, f); 107 | 108 | fclose(f); 109 | 110 | return mesh; 111 | } 112 | 113 | //////////////////////////////////////////////////////////////////////////////// 114 | 115 | Mesh* merge_meshes(const uint32_t count, const Mesh* const* const meshes) 116 | { 117 | Mesh* const out = calloc(1, sizeof(Mesh)); 118 | 119 | out->X = (Interval){INFINITY, -INFINITY}; 120 | out->Y = (Interval){INFINITY, -INFINITY}; 121 | out->Z = (Interval){INFINITY, -INFINITY}; 122 | 123 | for (int m=0; m < count; ++m) { 124 | 125 | mesh_reserve_v(out, out->vcount + meshes[m]->vcount); 126 | memcpy(out->vdata + out->vcount*6, 127 | meshes[m]->vdata, meshes[m]->vcount*6*sizeof(*out->vdata) 128 | ); 129 | 130 | mesh_reserve_t(out, out->tcount + meshes[m]->tcount); 131 | memcpy(out->tdata + out->tcount*3, 132 | meshes[m]->tdata, meshes[m]->tcount*3*sizeof(*out->tdata) 133 | ); 134 | 135 | // Adjust indices of offset vertices within the larger list 136 | for (int i=0; i < meshes[m]->tcount; ++i) { 137 | out->tdata[(out->tcount+i)*3] += out->vcount; 138 | out->tdata[(out->tcount+i)*3 + 1] += out->vcount; 139 | out->tdata[(out->tcount+i)*3 + 2] += out->vcount; 140 | } 141 | 142 | out->vcount += meshes[m]->vcount; 143 | out->tcount += meshes[m]->tcount; 144 | 145 | 146 | out->X.lower = fmin(out->X.lower, meshes[m]->X.lower); 147 | out->X.upper = fmax(out->X.upper, meshes[m]->X.upper); 148 | out->Y.lower = fmin(out->Y.lower, meshes[m]->Y.lower); 149 | out->Y.upper = fmax(out->Y.upper, meshes[m]->Y.upper); 150 | out->Z.lower = fmin(out->Z.lower, meshes[m]->Z.lower); 151 | out->Z.upper = fmax(out->Z.upper, meshes[m]->Z.upper); 152 | } 153 | return out; 154 | } 155 | -------------------------------------------------------------------------------- /libfab/formats/mesh.h: -------------------------------------------------------------------------------- 1 | #ifndef FORMATS_MESH_H 2 | #define FORMATS_MESH_H 3 | 4 | #include 5 | 6 | #include "util/interval.h" 7 | 8 | typedef struct Mesh_ { 9 | float* vdata; 10 | uint32_t vcount; 11 | uint32_t valloc; 12 | 13 | uint32_t* tdata; 14 | uint32_t tcount; 15 | uint32_t talloc; 16 | 17 | Interval X, Y, Z; 18 | } Mesh; 19 | 20 | /** @brief Frees a mesh object */ 21 | void free_mesh(Mesh* mesh); 22 | 23 | /** @brief Ensures that the mesh can store the requested number of triangles 24 | @details Expands the tdata buffer if needed. 25 | */ 26 | void mesh_reserve_t(Mesh* mesh, const uint32_t tcount); 27 | 28 | /** @brief Ensures that the mesh can store the requested number of vertices 29 | @details Expands the vdata buffer if needed. 30 | */ 31 | void mesh_reserve_v(Mesh* mesh, const uint32_t vcount); 32 | 33 | /** @brief Increases a set of indices in a list 34 | @details Used when combining mesh vertex and index lists 35 | @param idata List of indices 36 | @param icount Number of indices in idata 37 | @param di Index increment 38 | */ 39 | void increase_indices(Mesh*, uint32_t di); 40 | 41 | /** @brief Writes a mesh to a binary file full of indexed geometry 42 | */ 43 | void save_mesh(const char* filename, const Mesh* const mesh); 44 | 45 | /** @brief Reads in a mesh structure from a file. 46 | */ 47 | Mesh* load_mesh(const char* filename); 48 | 49 | 50 | /** @brief Merges a set of meshes into a single model 51 | @details Does not do anything fancy like vertex deduplication. 52 | @param count Number of meshes to merge 53 | @param meshes Array of pointers to meshes 54 | @returns A single merged Mesh object 55 | */ 56 | Mesh* merge_meshes(const uint32_t count, const Mesh* const* const meshes); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /libfab/formats/png_image.h: -------------------------------------------------------------------------------- 1 | #ifndef FORMATS_PNG_H 2 | #define FORMATS_PNG_H 3 | 4 | #include 5 | 6 | /** @brief Saves a 16-bit luminosity .png image 7 | @param output_file_name Target filename 8 | @param ni Image width (pixels) 9 | @param nj Image height (pixels) 10 | @param bounds Image bounds (mm) in the order [xmin, ymin, zmin, xmax, ymax, zmax] 11 | */ 12 | void save_png16L(const char *output_file_name, const int ni, const int nj, 13 | const float bounds[6], uint16_t const*const*const pixels); 14 | 15 | 16 | /** @brief Loads various image parameters from a .png header 17 | @param filename Target .png image to examine 18 | @param ni Field to store image width 19 | @param nj Field to store image height 20 | @param dx Field to store image width (or NAN if invalid) 21 | @param dy Field to store image height (or NAN if invalid) 22 | @param dz Field to store image depth (or NAN if invalid) 23 | */ 24 | void load_png_stats(const char* filename, int* const ni, int* const nj, 25 | float* const dx, float* const dy, float* const dz); 26 | 27 | 28 | /** @brief Counts image pixels by color 29 | @details A index (R<<16) + (G<<8) + B is calculated for each RGB pixel, 30 | the corresponding item in the array 'count' is incremented. 31 | 32 | @param image Image as list of pixel values 33 | (in r,g,b,r,g,b,r,g,b... order) 34 | @param w Image width (pixels) 35 | @param h Image height (pixels) 36 | @param maxindex Maximum index in count 37 | @param count Zero-initialized array to fill with counts 38 | */ 39 | void count_by_color(const char* const image, const int w, const int h, 40 | const uint32_t maxindex, uint32_t* const count); 41 | 42 | 43 | /** @brief Copies src onto dst, applying the color (R,G,B) 44 | and filtering by height. 45 | @details If src is brighter than depth at a given pixel, then we modify depth and save a colored version of the src pixel into rgb. 46 | src and depth need to have the same scales on both axes and in terms of bits per mm (for height-map). 47 | 48 | @param src New image's height-map 49 | @param depth Destination image's height-map 50 | @param rgb Target RGB image 51 | @param x Position of src's left edge within depth 52 | @param y Position of src's bottom edge within depth 53 | @param ni src's width 54 | @param nj src's height 55 | @param R Red color (0-1) 56 | @param G Green color (0-1) 57 | @param B Blue color (0-1) 58 | */ 59 | void depth_blit(uint8_t const*const*const src, 60 | uint8_t* const*const depth, 61 | uint8_t (*const*const rgb)[3], 62 | const int x, const int y, 63 | const int ni, const int nj, 64 | const float R, const float G, const float B); 65 | #endif 66 | -------------------------------------------------------------------------------- /libfab/formats/stl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | uint32_t read_int(FILE* file) 9 | { 10 | struct { 11 | uint32_t i; 12 | char c; 13 | } ret; 14 | char * buf = fgets((char*)&ret, 5, file); 15 | return ret.i; 16 | } 17 | 18 | float read_float(FILE* file) 19 | { 20 | struct { 21 | float f; 22 | char c; 23 | } ret; 24 | char * buf = fgets((char*)&ret, 5, file); 25 | return ret.f; 26 | } 27 | 28 | Mesh* load_stl(const char* filename) 29 | { 30 | FILE* input = fopen(filename, "rb"); 31 | 32 | Mesh* const mesh = calloc(1, sizeof(Mesh)); 33 | mesh->X = (Interval){INFINITY, -INFINITY}; 34 | mesh->Y = (Interval){INFINITY, -INFINITY}; 35 | mesh->Z = (Interval){INFINITY, -INFINITY}; 36 | 37 | // Skip the STL file header 38 | fseek(input, 80, SEEK_SET); 39 | // Read in the triangle count 40 | mesh->tcount = read_int(input); 41 | mesh->vcount = mesh->tcount * 3; 42 | 43 | // Allocate space for the incoming triangles and vertices 44 | mesh_reserve_t(mesh, mesh->tcount); 45 | mesh_reserve_v(mesh, mesh->vcount); 46 | 47 | for (int t=0; t < mesh->tcount; ++t) { 48 | // Current position in the vertex buffer 49 | // (each triangle is 3 vertices with 6 floats each) 50 | const unsigned v = t * 18; 51 | float normal[3]; 52 | 53 | // First read the normal vector 54 | normal[0] = read_float(input); 55 | normal[1] = read_float(input); 56 | normal[2] = read_float(input); 57 | 58 | // Read 3 sets of 3 floats (each 4 bytes) 59 | for (int j=0; j < 18; j+=6) { 60 | float X = read_float(input); 61 | float Y = read_float(input); 62 | float Z = read_float(input); 63 | mesh->X.lower = fmin(mesh->X.lower, X); 64 | mesh->X.upper = fmax(mesh->X.upper, X); 65 | mesh->Y.lower = fmin(mesh->Y.lower, Y); 66 | mesh->Y.upper = fmax(mesh->Y.upper, Y); 67 | mesh->Z.lower = fmin(mesh->Z.lower, Z); 68 | mesh->Z.upper = fmax(mesh->Z.upper, Z); 69 | mesh->vdata[v+j] = X; 70 | mesh->vdata[v+1+j] = Y; 71 | mesh->vdata[v+2+j] = Z; 72 | mesh->vdata[v+3+j] = normal[0]; 73 | mesh->vdata[v+4+j] = normal[1]; 74 | mesh->vdata[v+5+j] = normal[2]; 75 | } 76 | 77 | // Recompute the normal if it was not done in the file 78 | if (normal[0] == 0.0 && normal[1] == 0 && normal[2] == 0.0) { 79 | const float a1 = mesh->vdata[v+6] - mesh->vdata[v], 80 | b1 = mesh->vdata[v+12] - mesh->vdata[v], 81 | a2 = mesh->vdata[v+7] - mesh->vdata[v+1], 82 | b2 = mesh->vdata[v+13] - mesh->vdata[v+1], 83 | a3 = mesh->vdata[v+8] - mesh->vdata[v+2], 84 | b3 = mesh->vdata[v+14] - mesh->vdata[v+2]; 85 | 86 | // Get normal with cross product 87 | const float nx = a2*b3 - a3*b2, 88 | ny = a3*b1 - a1*b3, 89 | nz = a1*b2 - a2*b1; 90 | 91 | // And save the normal in the vertex buffer 92 | for (int i=0; i < 3; ++i) { 93 | mesh->vdata[v+3+i*6] = nx; 94 | mesh->vdata[v+4+i*6] = ny; 95 | mesh->vdata[v+5+i*6] = nz; 96 | } 97 | } 98 | 99 | // Ignore attribute byte count 100 | fseek(input, 2, SEEK_CUR); 101 | 102 | mesh->tdata[t*3] = v; 103 | mesh->tdata[t*3 + 1] = v + 6; 104 | mesh->tdata[t*3 + 2] = v + 12; 105 | } 106 | 107 | fclose(input); 108 | 109 | return mesh; 110 | } 111 | 112 | //////////////////////////////////////////////////////////////////////////////// 113 | 114 | void save_stl(Mesh* mesh, const char* filename) 115 | { 116 | FILE* stl = fopen(filename, "wb"); 117 | 118 | // 80-character header 119 | fprintf(stl, "This is a binary STL file made in kokopelli \n(github.com/mkeeter/kokopelli)\n\n"); 120 | 121 | for (int i=0; i<4; ++i) { 122 | fputc(((char*)&mesh->tcount)[i], stl); 123 | } 124 | 125 | for (int t=0; t < mesh->tcount; ++t) { 126 | 127 | // Write the face normal (which we'll keep empty) 128 | for (int j=0; j < 12; ++j) fputc(0, stl); 129 | 130 | // Write out all of the vertices. 131 | for (int v=0; v < 3; ++v) { 132 | float xyz[3] = { 133 | mesh->vdata[6*mesh->tdata[t*3+v]], 134 | mesh->vdata[6*mesh->tdata[t*3+v]+1], 135 | mesh->vdata[6*mesh->tdata[t*3+v]+2] 136 | }; 137 | for (int j=0; j < 12; ++j) { 138 | fputc(((char*)&xyz)[j], stl); 139 | } 140 | } 141 | 142 | fputc(0, stl); 143 | fputc(0, stl); 144 | } 145 | 146 | fclose(stl); 147 | } 148 | 149 | //////////////////////////////////////////////////////////////////////////////// 150 | 151 | /* 152 | void draw_triangle(Triangle tri, Region r, uint16_t*const*const img) 153 | { 154 | int imin = ni*(fmin(fmin(tri.x0, tri.x1), tri.x2) - xmin) / (xmax - xmin); 155 | if (imin < 0) imin = 0; 156 | 157 | int imax = ni*(fmax(fmax(tri.x0, tri.x1), tri.x2) - xmin) / (xmax - xmin); 158 | if (imax >= ni) imax = ni-1; 159 | 160 | int jmin = nj*(fmin(fmin(tri.y0, tri.y1), tri.y2) - ymin) / (ymax - ymin); 161 | if (jmin < 0) jmin = 0; 162 | 163 | int jmax = nj*(fmax(fmax(tri.y0, tri.y1), tri.y2) - ymin) / (ymax - ymin); 164 | if (jmax >= nj) jmax = 0; 165 | } 166 | */ 167 | -------------------------------------------------------------------------------- /libfab/formats/stl.h: -------------------------------------------------------------------------------- 1 | #ifndef FORMATS_STL_H 2 | #define FORMATS_STL_H 3 | 4 | #include 5 | 6 | struct Mesh_; 7 | 8 | /** @brief Loads an STL file. 9 | */ 10 | struct Mesh_* load_stl(const char* filename); 11 | 12 | /** @brief Writes a mesh to an STL file */ 13 | void save_stl(struct Mesh_* mesh, const char* filename); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /libfab/tree/eval.h: -------------------------------------------------------------------------------- 1 | #ifndef EVAL_H 2 | #define EVAL_H 3 | 4 | #include "util/interval.h" 5 | #include "util/region.h" 6 | #include "util/switches.h" 7 | 8 | // Forward declarations 9 | struct PackedTree_; 10 | 11 | 12 | /** @brief Evaluates a math expression at a given floating-point position. 13 | @details Results are stored in n->head->results.f 14 | */ 15 | float eval_f(struct PackedTree_* n, const float x, const float y, const float z); 16 | 17 | 18 | /** @brief Evaluates a math expression over an interval region 19 | @details Results are stored in n->head->results.i 20 | */ 21 | Interval eval_i(struct PackedTree_* n, const Interval X, 22 | const Interval Y, 23 | const Interval Z); 24 | 25 | /** @brief Evaluates a math expression over a set of many positions 26 | @details Results are stored in n->head->results.r 27 | */ 28 | float* eval_r(struct PackedTree_* n, const Region r); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /libfab/tree/math/math_f.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "tree/math/math_f.h" 4 | 5 | float add_f(float A, float B) 6 | { 7 | return A+B; 8 | } 9 | 10 | float sub_f(float A, float B) 11 | { 12 | return A-B; 13 | } 14 | 15 | float mul_f(float A, float B) 16 | { 17 | return A*B; 18 | } 19 | 20 | float div_f(float A, float B) 21 | { 22 | return A/B; 23 | } 24 | 25 | float min_f(float A, float B) 26 | { 27 | return A < B ? A : B; 28 | } 29 | 30 | float max_f(float A, float B) 31 | { 32 | return A > B ? A : B; 33 | } 34 | 35 | float pow_f(float A, float B) 36 | { 37 | return pow(A, B); 38 | } 39 | 40 | //////////////////////////////////////////////////////////////////////////////// 41 | 42 | float abs_f(float A) 43 | { 44 | return fabs(A); 45 | } 46 | 47 | float square_f(float A) 48 | { 49 | return A*A; 50 | } 51 | 52 | float sqrt_f(float A) 53 | { 54 | if (A < 0) return 0; 55 | else return sqrt(A); 56 | } 57 | 58 | float sin_f(float A) 59 | { 60 | return sin(A); 61 | } 62 | 63 | float cos_f(float A) 64 | { 65 | return cos(A); 66 | } 67 | 68 | float tan_f(float A) 69 | { 70 | return tan(A); 71 | } 72 | 73 | float asin_f(float A) 74 | { 75 | if (A < -1) return -M_PI_2; 76 | else if (A > 1) return M_PI_2; 77 | else return asin(A); 78 | } 79 | 80 | float acos_f(float A) 81 | { 82 | if (A < -1) return M_PI; 83 | else if (A > 1) return 0; 84 | else return acos(A); 85 | } 86 | 87 | float atan_f(float A) 88 | { 89 | return atan(A); 90 | } 91 | 92 | float neg_f(float A) 93 | { 94 | return -A; 95 | } 96 | 97 | //////////////////////////////////////////////////////////////////////////////// 98 | 99 | float X_f(float X) 100 | { return X; } 101 | 102 | float Y_f(float Y) 103 | { return Y; } 104 | 105 | float Z_f(float Z) 106 | { return Z; } 107 | -------------------------------------------------------------------------------- /libfab/tree/math/math_f.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_F_H 2 | #define MATH_F_H 3 | 4 | /** @file tree/math/math_f.h 5 | @brief Functions for doing math on floating-point numbers. 6 | @details These functions take in input floats A and B, 7 | and return the result of their computation. 8 | */ 9 | 10 | // Binary functions 11 | float add_f(float A, float B); 12 | float sub_f(float A, float B); 13 | float mul_f(float A, float B); 14 | float div_f(float A, float B); 15 | 16 | float min_f(float A, float B); 17 | float max_f(float A, float B); 18 | 19 | float pow_f(float A, float B); 20 | 21 | // Unary functions 22 | float abs_f(float A); 23 | float square_f(float A); 24 | float sqrt_f(float A); 25 | float sin_f(float A); 26 | float cos_f(float A); 27 | float tan_f(float A); 28 | float asin_f(float A); 29 | float acos_f(float A); 30 | float atan_f(float A); 31 | float neg_f(float A); 32 | 33 | // Variables 34 | float X_f(float X); 35 | float Y_f(float Y); 36 | float Z_f(float Z); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /libfab/tree/math/math_i.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tree/math/math_i.h" 6 | 7 | Interval add_i(Interval A, Interval B) 8 | { 9 | return (Interval){.lower = A.lower + B.lower, 10 | .upper = A.upper + B.upper}; 11 | } 12 | 13 | Interval sub_i(Interval A, Interval B) 14 | { 15 | return (Interval){.lower = A.lower - B.upper, 16 | .upper = A.upper - B.lower}; 17 | } 18 | 19 | Interval mul_i(Interval A, Interval B) 20 | { 21 | Interval i; 22 | 23 | float c1 = A.lower * B.lower, 24 | c2 = A.lower * B.upper, 25 | c3 = A.upper * B.lower, 26 | c4 = A.upper * B.upper; 27 | 28 | i.lower = fmin(fmin(c1, c2), fmin(c3, c4)); 29 | i.upper = fmax(fmax(c1, c2), fmax(c3, c4)); 30 | return i; 31 | } 32 | 33 | Interval div_i(Interval A, Interval B) 34 | { 35 | Interval i; 36 | 37 | if (B.lower <=0 && B.upper >= 0) 38 | { 39 | i.lower = -INFINITY; 40 | i.upper = INFINITY; 41 | return i; 42 | } 43 | 44 | B.lower = 1/B.lower; 45 | B.upper = 1/B.upper; 46 | float c1 = A.lower * B.lower, 47 | c2 = A.lower * B.upper, 48 | c3 = A.upper * B.lower, 49 | c4 = A.upper * B.upper; 50 | 51 | i.lower = fmin(fmin(c1, c2), fmin(c3, c4)); 52 | i.upper = fmax(fmax(c1, c2), fmax(c3, c4)); 53 | return i; 54 | } 55 | 56 | Interval min_i(Interval A, Interval B) 57 | { 58 | Interval i; 59 | 60 | i.lower = A.lower < B.lower ? A.lower : B.lower; 61 | i.upper = A.upper < B.upper ? A.upper : B.upper; 62 | return i; 63 | } 64 | 65 | Interval max_i(Interval A, Interval B) 66 | { 67 | Interval i; 68 | 69 | i.lower = A.lower > B.lower ? A.lower : B.lower; 70 | i.upper = A.upper > B.upper ? A.upper : B.upper; 71 | return i; 72 | } 73 | 74 | Interval pow_i(Interval A, Interval B) 75 | { 76 | Interval i; 77 | 78 | int p = B.lower; 79 | 80 | if (p % 2) { 81 | i.lower = pow(A.lower, p); 82 | i.upper = pow(A.upper, p); 83 | } else { 84 | float L = fabs(A.lower), U = fabs(A.upper); 85 | if (A.lower <= 0 && A.upper >= 0) 86 | i.lower = 0; 87 | else 88 | i.lower = pow(L < U ? L : U, p); 89 | i.upper = pow(L < U ? U : L, p); 90 | } 91 | 92 | return i; 93 | } 94 | 95 | //////////////////////////////////////////////////////////////////////////////// 96 | 97 | Interval abs_i(Interval A) 98 | { 99 | Interval i; 100 | 101 | if (A.lower < 0) 102 | i.lower = 0; 103 | else 104 | i.lower = fmin(fabs(A.lower), fabs(A.upper)); 105 | i.upper = fmax(fabs(A.lower), fabs(A.upper)); 106 | 107 | return i; 108 | } 109 | 110 | Interval square_i(Interval A) 111 | { 112 | Interval i; 113 | 114 | float u = A.upper * A.upper; 115 | float l = A.lower * A.lower; 116 | 117 | if (A.upper > 0 && A.lower < 0) 118 | i.lower = 0; 119 | else 120 | i.lower = fmin(u, l); 121 | i.upper = fmax(u, l); 122 | 123 | return i; 124 | } 125 | 126 | Interval sqrt_i(Interval A) 127 | { 128 | Interval i; 129 | if (A.lower <= 0) i.lower = 0; 130 | else i.lower = sqrt(A.lower); 131 | 132 | if (A.upper <= 0) i.upper = 0; 133 | else i.upper = sqrt(A.upper); 134 | 135 | return i; 136 | } 137 | 138 | Interval sin_i(Interval A) 139 | { 140 | Interval i; 141 | 142 | if (A.lower == A.upper) { 143 | i.lower = sin(A.lower); 144 | i.upper = i.lower; 145 | } else { 146 | i.lower = -1; 147 | i.upper = 1; 148 | } 149 | return i; 150 | } 151 | 152 | Interval cos_i(Interval A) 153 | { 154 | Interval i; 155 | 156 | if (A.lower == A.upper) { 157 | i.lower = cos(A.lower); 158 | i.upper = i.lower; 159 | } else { 160 | i.lower = -1; 161 | i.upper = 1; 162 | } 163 | return i; 164 | } 165 | 166 | Interval tan_i(Interval A) 167 | { 168 | Interval i; 169 | 170 | if (A.lower == A.upper) { 171 | i.lower = tan(A.lower); 172 | i.upper = i.lower; 173 | } else { 174 | i.lower = -INFINITY; 175 | i.upper = INFINITY; 176 | } 177 | return i; 178 | } 179 | 180 | Interval asin_i(Interval A) 181 | { 182 | Interval i; 183 | 184 | if (A.lower == A.upper) { 185 | if (A.lower <= -1) i.lower = -M_PI_2; 186 | else if (A.lower >= 1) i.lower = M_PI_2; 187 | else i.lower = asin(A.lower); 188 | i.upper = i.lower; 189 | } else { 190 | if (A.lower <= -1) i.lower = -M_PI_2; 191 | else if (A.lower >= 1) i.lower = M_PI_2; 192 | else i.lower = asin(A.lower); 193 | 194 | if (A.upper <= -1) i.upper = -M_PI_2; 195 | else if (A.upper >= 1) i.upper = M_PI_2; 196 | else i.upper = asin(A.upper); 197 | } 198 | 199 | return i; 200 | } 201 | 202 | Interval acos_i(Interval A) 203 | { 204 | Interval i; 205 | 206 | if (A.lower == A.upper) { 207 | if (A.lower <= -1) i.lower = M_PI; 208 | else if (A.lower >= 1) i.lower = 0; 209 | else i.lower = acos(A.lower); 210 | i.upper = i.lower; 211 | } else { 212 | if (A.lower <= -1) i.upper = M_PI; 213 | else if (A.lower > 1) i.upper = 0; 214 | else i.upper = acos(A.lower); 215 | 216 | if (A.upper <= -1) i.lower = M_PI; 217 | else if (A.upper > 1) i.lower = 0; 218 | else i.lower = acos(A.upper); 219 | } 220 | 221 | return i; 222 | } 223 | 224 | Interval atan_i(Interval A) 225 | { 226 | Interval i; 227 | 228 | if (A.lower == A.upper) { 229 | i.lower = atan(A.lower); 230 | i.upper = i.lower; 231 | } else { 232 | i.lower = atan(A.lower); 233 | i.upper = atan(A.upper); 234 | } 235 | 236 | return i; 237 | } 238 | 239 | Interval neg_i(Interval A) 240 | { 241 | return (Interval){.lower = -A.upper, 242 | .upper = -A.lower}; 243 | } 244 | 245 | //////////////////////////////////////////////////////////////////////////////// 246 | 247 | Interval X_i(Interval X) 248 | { return X; } 249 | 250 | Interval Y_i(Interval Y) 251 | { return Y; } 252 | 253 | Interval Z_i(Interval Z) 254 | { return Z; } 255 | -------------------------------------------------------------------------------- /libfab/tree/math/math_i.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_I_H 2 | #define MATH_I_H 3 | 4 | #include "util/interval.h" 5 | 6 | /** @file tree/math/math_i.h 7 | @brief Functions for doing math on intervals 8 | @details These functions take in input Intervals A and B 9 | and return the resulting Interval. 10 | */ 11 | 12 | // Binary functions 13 | Interval add_i(Interval A, Interval B); 14 | Interval sub_i(Interval A, Interval B); 15 | Interval mul_i(Interval A, Interval B); 16 | Interval div_i(Interval A, Interval B); 17 | 18 | Interval min_i(Interval A, Interval B); 19 | Interval max_i(Interval A, Interval B); 20 | 21 | Interval pow_i(Interval A, Interval B); 22 | 23 | // Unary functions 24 | Interval abs_i(Interval A); 25 | Interval square_i(Interval A); 26 | Interval sqrt_i(Interval A); 27 | Interval sin_i(Interval A); 28 | Interval cos_i(Interval A); 29 | Interval tan_i(Interval A); 30 | Interval asin_i(Interval A); 31 | Interval acos_i(Interval A); 32 | Interval atan_i(Interval A); 33 | Interval neg_i(Interval A); 34 | 35 | // Variables 36 | Interval X_i(Interval X); 37 | Interval Y_i(Interval Y); 38 | Interval Z_i(Interval Z); 39 | 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /libfab/tree/math/math_r.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tree/math/math_r.h" 5 | 6 | float* add_r(float* A, float* B, float* R, int c) 7 | { 8 | for (int q = 0; q < c; ++q) 9 | R[q] = A[q] + B[q]; 10 | return R; 11 | } 12 | 13 | float* sub_r(float* A, float* B, float* R, int c) 14 | { 15 | for (int q = 0; q < c; ++q) 16 | R[q] = A[q] - B[q]; 17 | return R; 18 | } 19 | 20 | float* mul_r(float* A, float* B, float* R, int c) 21 | { 22 | for (int q = 0; q < c; ++q) 23 | R[q] = A[q] * B[q]; 24 | return R; 25 | } 26 | 27 | float* div_r(float* A, float* B, float* R, int c) 28 | { 29 | for (int q = 0; q < c; ++q) 30 | R[q] = A[q] / B[q]; 31 | return R; 32 | } 33 | 34 | float* min_r(float* A, float* B, float* R, int c) 35 | { 36 | for (int q = 0; q < c; ++q) 37 | R[q] = fmin(A[q], B[q]); 38 | return R; 39 | } 40 | 41 | float* max_r(float* A, float* B, float* R, int c) 42 | { 43 | for (int q = 0; q < c; ++q) 44 | R[q] = fmax(A[q], B[q]); 45 | return R; 46 | } 47 | 48 | float* pow_r(float* A, float* B, float* R, int c) 49 | { 50 | for (int q = 0; q < c; ++q) 51 | R[q] = pow(A[q], B[q]); 52 | return R; 53 | } 54 | 55 | //////////////////////////////////////////////////////////////////////////////// 56 | 57 | float* abs_r(float* A, float* R, int c) 58 | { 59 | for (int q=0; q < c; ++q) 60 | R[q] = fabs(A[q]); 61 | return R; 62 | } 63 | 64 | float* square_r(float* A, float* R, int c) 65 | { 66 | for (int q = 0; q < c; ++q) 67 | R[q] = A[q]*A[q]; 68 | return R; 69 | } 70 | 71 | float* sqrt_r(float* A, float* R, int c) 72 | { 73 | for (int q = 0; q < c; ++q) 74 | if (A[q] < 0) R[q] = 0; 75 | else R[q] = sqrt(A[q]); 76 | return R; 77 | } 78 | 79 | float* sin_r(float* A, float* R, int c) 80 | { 81 | for (int q = 0; q < c; ++q) 82 | R[q] = sin(A[q]); 83 | return R; 84 | } 85 | 86 | float* cos_r(float* A, float* R, int c) 87 | { 88 | for (int q = 0; q < c; ++q) 89 | R[q] = cos(A[q]); 90 | return R; 91 | } 92 | 93 | float* tan_r(float* A, float* R, int c) 94 | { 95 | for (int q = 0; q < c; ++q) 96 | R[q] = tan(A[q]); 97 | return R; 98 | } 99 | 100 | float* asin_r(float* A, float* R, int c) 101 | { 102 | for (int q = 0; q < c; ++q) 103 | R[q] = asin(A[q]); 104 | return R; 105 | } 106 | 107 | float* acos_r(float* A, float* R, int c) 108 | { 109 | for (int q = 0; q < c; ++q) { 110 | if (A[q] < -1) R[q] = M_PI; 111 | else if (A[q] > 1) R[q] = 0; 112 | else R[q] = acos(A[q]); 113 | } 114 | return R; 115 | } 116 | 117 | float* atan_r(float* A, float* R, int c) 118 | { 119 | for (int q = 0; q < c; ++q) 120 | R[q] = atan(A[q]); 121 | return R; 122 | } 123 | 124 | float* neg_r(float* A, float* R, int c) 125 | { 126 | for (int q = 0; q < c; ++q) 127 | R[q] = -A[q]; 128 | return R; 129 | } 130 | 131 | //////////////////////////////////////////////////////////////////////////////// 132 | 133 | float* X_r(float* X, float* R, int c) 134 | { 135 | memcpy(R, X,c*sizeof(float)); 136 | return R; 137 | } 138 | 139 | float* Y_r(float* Y, float* R, int c) 140 | { 141 | memcpy(R, Y, c*sizeof(float)); 142 | return R; 143 | } 144 | 145 | float* Z_r(float* Z, float* R, int c) 146 | { 147 | memcpy(R, Z, c*sizeof(float)); 148 | return R; 149 | } 150 | -------------------------------------------------------------------------------- /libfab/tree/math/math_r.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_R_H 2 | #define MATH_R_H 3 | 4 | /** @file tree/math/math_r.h 5 | @brief Functions for doing math on many distinct points at once 6 | @details These functions take in input arrays A and B, 7 | and array point count c. Results are stored in the array R. 8 | */ 9 | 10 | // Binary functions 11 | float* add_r(float* A, float* B, float* R, int c); 12 | float* sub_r(float* A, float* B, float* R, int c); 13 | float* mul_r(float* A, float* B, float* R, int c); 14 | float* div_r(float* A, float* B, float* R, int c); 15 | 16 | float* min_r(float* A, float* B, float* R, int c); 17 | float* max_r(float* A, float* B, float* R, int c); 18 | 19 | float* pow_r(float* A, float* B, float* R, int c); 20 | 21 | // Unary functions 22 | float* abs_r(float* A, float* R, int c); 23 | float* square_r(float* A, float* R, int c); 24 | float* sqrt_r(float* A, float* R, int c); 25 | float* sin_r(float* A, float* R, int c); 26 | float* cos_r(float* A, float* R, int c); 27 | float* tan_r(float* A, float* R, int c); 28 | float* asin_r(float* A, float* R, int c); 29 | float* acos_r(float* A, float* R, int c); 30 | float* atan_r(float* A, float* R, int c); 31 | float* neg_r(float* A, float* R, int c); 32 | 33 | // Variables 34 | float* X_r(float* X, float* R, int c); 35 | float* Y_r(float* Y, float* R, int c); 36 | float* Z_r(float* Z, float* R, int c); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /libfab/tree/node/node.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tree/node/node.h" 6 | #include "tree/node/printers.h" 7 | #include "tree/math/math_f.h" 8 | 9 | #include "util/switches.h" 10 | 11 | // Non-recursively clone a node. 12 | Node* clone_node(Node* n) 13 | { 14 | // Allocate memory and copy everything over 15 | Node* clone = malloc(sizeof(Node)); 16 | memcpy(clone, n, sizeof(Node)); 17 | 18 | // Update children clone pointers 19 | if (n->lhs) clone->lhs = n->lhs->clone_address; 20 | if (n->rhs) clone->rhs = n->rhs->clone_address; 21 | 22 | // Record the address of the new clone, so that clones of 23 | // its parents can be adjusted to point in the right place 24 | n->clone_address = clone; 25 | 26 | return clone; 27 | } 28 | 29 | //////////////////////////////////////////////////////////////////////////////// 30 | 31 | Node* binary_n(Node* lhs, Node* rhs, float (*f)(float, float), Opcode op) 32 | { 33 | Node* n = malloc(sizeof(Node)); 34 | 35 | _Bool constant = (lhs->flags & NODE_CONSTANT) && 36 | (rhs->flags & NODE_CONSTANT); 37 | 38 | *n = (Node) { 39 | .opcode = constant ? OP_CONST : op, 40 | .rank = constant ? 0 : 1 + (lhs->rank > rhs->rank ? 41 | lhs->rank : rhs->rank), 42 | .flags = constant ? NODE_CONSTANT : 0, 43 | .lhs = constant ? NULL : lhs, 44 | .rhs = constant ? NULL : rhs, 45 | .clone_address = NULL, 46 | }; 47 | 48 | if (constant) { 49 | fill_results(n, (*f)(lhs->results.f, rhs->results.f)); 50 | } 51 | 52 | return n; 53 | } 54 | 55 | Node* add_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, add_f, OP_ADD); } 56 | Node* sub_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, sub_f, OP_SUB); } 57 | Node* mul_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, mul_f, OP_MUL); } 58 | Node* div_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, div_f, OP_DIV); } 59 | 60 | Node* min_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, min_f, OP_MIN); } 61 | Node* max_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, max_f, OP_MAX); } 62 | Node* pow_n(Node* lhs, Node* rhs) { return binary_n(lhs, rhs, pow_f, OP_POW); } 63 | 64 | 65 | //////////////////////////////////////////////////////////////////////////////// 66 | 67 | Node* unary_n(Node* arg, float (*f)(float), Opcode op) 68 | { 69 | Node* n = malloc(sizeof(Node)); 70 | 71 | _Bool constant = arg->flags & NODE_CONSTANT; 72 | 73 | *n = (Node) { 74 | .opcode = constant ? OP_CONST : op, 75 | .rank = constant ? 0 : 1 + arg->rank, 76 | .flags = constant ? NODE_CONSTANT : 0, 77 | .lhs = constant ? NULL : arg, 78 | .rhs = NULL, 79 | .clone_address = NULL, 80 | }; 81 | 82 | if (constant) { 83 | fill_results(n, (*f)(arg->results.f)); 84 | } 85 | return n; 86 | } 87 | 88 | Node* abs_n(Node* child) { return unary_n(child, abs_f, OP_ABS); } 89 | Node* square_n(Node* child) { return unary_n(child, square_f, OP_SQUARE); } 90 | Node* sqrt_n(Node* child) { return unary_n(child, sqrt_f, OP_SQRT); } 91 | Node* sin_n(Node* child) { return unary_n(child, sin_f, OP_SIN); } 92 | Node* cos_n(Node* child) { return unary_n(child, cos_f, OP_COS); } 93 | Node* tan_n(Node* child) { return unary_n(child, tan_f, OP_TAN); } 94 | Node* asin_n(Node* child) { return unary_n(child, asin_f, OP_ASIN); } 95 | Node* acos_n(Node* child) { return unary_n(child, acos_f, OP_ACOS); } 96 | Node* atan_n(Node* child) { return unary_n(child, atan_f, OP_ATAN); } 97 | Node* neg_n(Node* child) { return unary_n(child, neg_f, OP_NEG); } 98 | 99 | //////////////////////////////////////////////////////////////////////////////// 100 | 101 | Node* nonary_n(Opcode op) 102 | { 103 | Node* n = malloc(sizeof(Node)); 104 | 105 | *n = (Node) { 106 | .opcode = op, 107 | .rank = 0, 108 | .flags = 0, 109 | .lhs = NULL, 110 | .rhs = NULL, 111 | .clone_address = NULL, 112 | }; 113 | 114 | return n; 115 | } 116 | 117 | Node* constant_n(float value) 118 | { 119 | Node* n = nonary_n(OP_CONST); 120 | n->flags = NODE_CONSTANT; 121 | fill_results(n, value); 122 | return n; 123 | } 124 | 125 | Node* X_n() 126 | { 127 | return nonary_n(OP_X); 128 | } 129 | 130 | Node* Y_n() 131 | { 132 | return nonary_n(OP_Y); 133 | } 134 | 135 | Node* Z_n() 136 | { 137 | return nonary_n(OP_Z); 138 | } 139 | -------------------------------------------------------------------------------- /libfab/tree/node/node.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_H 2 | #define NODE_H 3 | 4 | #include 5 | 6 | #include "tree/node/results.h" 7 | #include "tree/node/opcodes.h" 8 | #include "util/interval.h" 9 | #include "util/region.h" 10 | 11 | #define NODE_CONSTANT 1 12 | #define NODE_IGNORED 2 13 | #define NODE_BOOLEAN 4 14 | #define NODE_IN_TREE 8 15 | 16 | /** @struct Node_ 17 | @brief Recursive data structure defining a node in a math tree. 18 | */ 19 | typedef struct Node_ { 20 | /** @var opcode 21 | Node operation */ 22 | Opcode opcode; 23 | 24 | /** @var results 25 | Saved results from most recent evaluation */ 26 | Results results; 27 | 28 | /** @var rank 29 | Rank of the node in the tree. */ 30 | int rank; 31 | 32 | /** @var flags 33 | Flags (combination of be NODE_CONSTANT, NODE_IGNORED, NODE_BOOLEAN, and NODE_IN_TREE) */ 34 | uint8_t flags; 35 | 36 | /** @var lhs 37 | Left-hand child node (or NULL) 38 | */ 39 | struct Node_* lhs; 40 | 41 | /** @var rhs 42 | Right-hand child node (or NULL) 43 | */ 44 | struct Node_* rhs; 45 | 46 | /** @var clone_address 47 | Most recent place to which this node was cloned 48 | */ 49 | struct Node_* clone_address; 50 | } Node; 51 | 52 | 53 | /** @brief Clones a single node (non-recursively). 54 | @details Looks up clone_address of children, which must 55 | be populated with a sane value. 56 | @param n Node to clone 57 | @returns Pointer to clone 58 | */ 59 | Node* clone_node(Node* n); 60 | 61 | //////////////////////////////////////////////////////////////////////////////// 62 | // Node constructors 63 | //////////////////////////////////////////////////////////////////////////////// 64 | 65 | // Binary operations 66 | Node* add_n(Node* left, Node* right); 67 | Node* sub_n(Node* left, Node* right); 68 | Node* mul_n(Node* left, Node* right); 69 | Node* div_n(Node* left, Node* right); 70 | 71 | Node* min_n(Node* left, Node* right); 72 | Node* max_n(Node* left, Node* right); 73 | Node* pow_n(Node* left, Node* right); 74 | 75 | // Unary arithmetic operators 76 | Node* abs_n(Node* n); 77 | Node* square_n(Node* n); 78 | Node* sqrt_n(Node* n); 79 | Node* sin_n(Node* n); 80 | Node* cos_n(Node* n); 81 | Node* tan_n(Node* n); 82 | Node* asin_n(Node* n); 83 | Node* acos_n(Node* n); 84 | Node* atan_n(Node* n); 85 | Node* neg_n(Node* n); 86 | 87 | // Constants 88 | Node* constant_n(float value); 89 | 90 | // Variables 91 | Node* X_n(void); 92 | Node* Y_n(void); 93 | Node* Z_n(void); 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /libfab/tree/node/opcodes.c: -------------------------------------------------------------------------------- 1 | #include "tree/node/opcodes.h" 2 | 3 | const char* OPCODE_NAMES[] = { 4 | "OP_ADD", 5 | "OP_SUB", 6 | "OP_MUL", 7 | "OP_DIV", 8 | "OP_MIN", 9 | "OP_MAX", 10 | "OP_POW", 11 | 12 | "OP_ABS", 13 | "OP_SQUARE", 14 | "OP_SQRT", 15 | "OP_SIN", 16 | "OP_COS", 17 | "OP_TAN", 18 | "OP_ASIN", 19 | "OP_ACOS", 20 | "OP_ATAN", 21 | "OP_NEG", 22 | 23 | "OP_X", 24 | "OP_Y", 25 | "OP_Z", 26 | "OP_CONST", 27 | 28 | "LAST_OP" 29 | }; 30 | 31 | const char* dot_symbol(Opcode op) { 32 | switch (op) { 33 | case OP_ADD: return "+"; 34 | case OP_SUB: return "−"; 35 | case OP_MUL: return "×"; 36 | case OP_DIV: return "/"; 37 | case OP_MIN: return "min"; 38 | case OP_MAX: return "max"; 39 | case OP_POW: return "pow"; 40 | case OP_ABS: return "abs"; 41 | case OP_SQUARE: return "square"; 42 | case OP_SQRT: return "sqrt"; 43 | case OP_SIN: return "sin"; 44 | case OP_COS: return "cos"; 45 | case OP_TAN: return "tan"; 46 | case OP_ASIN: return "asin"; 47 | case OP_ACOS: return "acos"; 48 | case OP_ATAN: return "atan"; 49 | case OP_NEG: return "−"; 50 | case OP_X: return "X"; 51 | case OP_Y: return "Y"; 52 | case OP_Z: return "Z"; 53 | case OP_CONST: return "C"; 54 | default: return ""; 55 | } 56 | } 57 | 58 | const char* dot_color(Opcode op) { 59 | switch (op) { 60 | case OP_ADD: 61 | case OP_SUB: 62 | case OP_MUL: 63 | case OP_DIV: 64 | case OP_POW: 65 | case OP_ABS: 66 | case OP_SQUARE: 67 | case OP_SQRT: 68 | case OP_SIN: 69 | case OP_COS: 70 | case OP_TAN: 71 | case OP_ASIN: 72 | case OP_ACOS: 73 | case OP_ATAN: 74 | return "goldenrod"; 75 | case OP_MIN: 76 | case OP_MAX: 77 | case OP_NEG: 78 | return "dodgerblue"; 79 | case OP_X: 80 | case OP_Y: 81 | case OP_Z: 82 | return "red"; 83 | case OP_CONST: 84 | return "green"; 85 | default: 86 | return "black"; 87 | } 88 | } 89 | 90 | int dot_fontsize(Opcode op) { 91 | switch (op) { 92 | case OP_ADD: 93 | case OP_SUB: 94 | case OP_MUL: 95 | case OP_DIV: 96 | case OP_NEG: 97 | case OP_X: 98 | case OP_Y: 99 | case OP_Z: 100 | case OP_CONST: 101 | return 24; 102 | case OP_MIN: 103 | case OP_MAX: 104 | case OP_POW: 105 | case OP_ABS: 106 | case OP_SQUARE: 107 | case OP_SQRT: 108 | case OP_SIN: 109 | case OP_COS: 110 | case OP_TAN: 111 | case OP_ASIN: 112 | case OP_ACOS: 113 | case OP_ATAN: 114 | return 14; 115 | default: 116 | return 0; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /libfab/tree/node/opcodes.h: -------------------------------------------------------------------------------- 1 | #ifndef OPCODES_H 2 | #define OPCODES_H 3 | 4 | /** @enum Opcode_ 5 | @brief Node operations 6 | */ 7 | typedef enum Opcode_ { 8 | OP_ADD, 9 | OP_SUB, 10 | OP_MUL, 11 | OP_DIV, 12 | OP_MIN, 13 | OP_MAX, 14 | OP_POW, 15 | 16 | OP_ABS, 17 | OP_SQUARE, 18 | OP_SQRT, 19 | OP_SIN, 20 | OP_COS, 21 | OP_TAN, 22 | OP_ASIN, 23 | OP_ACOS, 24 | OP_ATAN, 25 | OP_NEG, 26 | 27 | OP_X, 28 | OP_Y, 29 | OP_Z, 30 | OP_CONST, 31 | 32 | LAST_OP 33 | } Opcode; 34 | 35 | /**@var OPCODE_NAMES 36 | Names of node operations */ 37 | extern const char* OPCODE_NAMES[]; 38 | 39 | /** @returns Node label for a dot graph */ 40 | const char* dot_symbol(Opcode op); 41 | 42 | /** @returns Node color for a dot graph */ 43 | const char* dot_color(Opcode op); 44 | 45 | /** @returns Node font size for a dot graph */ 46 | int dot_fontsize(Opcode op); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /libfab/tree/node/printers.c: -------------------------------------------------------------------------------- 1 | #include "tree/node/printers.h" 2 | #include "tree/node/node.h" 3 | 4 | //////////////////////////////////////////////////////////////////////////////// 5 | 6 | static void add_p(Node* n, FILE* f) 7 | { 8 | fprintf(f, "("); 9 | fprint_node(n->lhs, f); 10 | fprintf(f, "+"); 11 | fprint_node(n->rhs, f); 12 | fprintf(f, ")"); 13 | } 14 | 15 | static void sub_p(Node* n, FILE* f) 16 | { 17 | fprintf(f, "("); 18 | fprint_node(n->lhs, f); 19 | fprintf(f, "-"); 20 | fprint_node(n->rhs, f); 21 | fprintf(f, ")"); 22 | } 23 | 24 | static void mul_p(Node* n, FILE* f) 25 | { 26 | fprintf(f, "("); 27 | fprint_node(n->lhs, f); 28 | fprintf(f, "*"); 29 | fprint_node(n->rhs, f); 30 | fprintf(f, ")"); 31 | } 32 | 33 | static void div_p(Node* n, FILE* f) 34 | { 35 | fprintf(f, "("); 36 | fprint_node(n->lhs, f); 37 | fprintf(f, "/"); 38 | fprint_node(n->rhs, f); 39 | fprintf(f, ")"); 40 | } 41 | 42 | static void min_p(Node* n, FILE* f) 43 | { 44 | fprintf(f, "min("); 45 | fprint_node(n->lhs, f); 46 | fprintf(f, ", "); 47 | fprint_node(n->rhs, f); 48 | fprintf(f, ")"); 49 | } 50 | 51 | static void max_p(Node* n, FILE* f) 52 | { 53 | fprintf(f, "max("); 54 | fprint_node(n->lhs, f); 55 | fprintf(f, ", "); 56 | fprint_node(n->rhs, f); 57 | fprintf(f, ")"); 58 | } 59 | 60 | static void pow_p(Node* n, FILE* f) 61 | { 62 | fprintf(f, "pow("); 63 | fprint_node(n->lhs, f); 64 | fprintf(f, ", "); 65 | fprint_node(n->rhs, f); 66 | fprintf(f, ")"); 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | 71 | static void square_p(Node* n, FILE* f) 72 | { 73 | fprintf(f, "pow("); 74 | fprint_node(n->lhs, f); 75 | fprintf(f, ", 2)"); 76 | } 77 | 78 | static void sqrt_p(Node* n, FILE* f) 79 | { 80 | fprintf(f, "sqrt("); 81 | fprint_node(n->lhs, f); 82 | fprintf(f, ")"); 83 | } 84 | 85 | static void sin_p(Node* n, FILE* f) 86 | { 87 | fprintf(f, "sin("); 88 | fprint_node(n->lhs, f); 89 | fprintf(f, ")"); 90 | } 91 | 92 | static void cos_p(Node* n, FILE* f) 93 | { 94 | fprintf(f, "cos("); 95 | fprint_node(n->lhs, f); 96 | fprintf(f, ")"); 97 | } 98 | 99 | static void tan_p(Node* n, FILE* f) 100 | { 101 | fprintf(f, "tan("); 102 | fprint_node(n->lhs, f); 103 | fprintf(f, ")"); 104 | } 105 | 106 | static void asin_p(Node* n, FILE* f) 107 | { 108 | fprintf(f, "asin("); 109 | fprint_node(n->lhs, f); 110 | fprintf(f, ")"); 111 | } 112 | 113 | static void acos_p(Node* n, FILE* f) 114 | { 115 | fprintf(f, "acos("); 116 | fprint_node(n->lhs, f); 117 | fprintf(f, ")"); 118 | } 119 | 120 | static void atan_p(Node* n, FILE* f) 121 | { 122 | fprintf(f, "atan("); 123 | fprint_node(n->lhs, f); 124 | fprintf(f, ")"); 125 | } 126 | 127 | static void neg_p(Node* n, FILE* f) 128 | { 129 | fprintf(f, "-"); 130 | fprint_node(n->lhs, f); 131 | } 132 | 133 | //////////////////////////////////////////////////////////////////////////////// 134 | 135 | static void constant_p(Node* n, FILE* f) 136 | { 137 | fprintf(f, "%g", n->results.f); 138 | } 139 | 140 | static void X_p(Node* n, FILE* f) 141 | { 142 | fprintf(f, "X"); 143 | } 144 | 145 | static void Y_p(Node* n, FILE* f) 146 | { 147 | fprintf(f, "Y"); 148 | } 149 | 150 | static void Z_p(Node* n, FILE* f) 151 | { 152 | fprintf(f, "Z"); 153 | } 154 | 155 | //////////////////////////////////////////////////////////////////////////////// 156 | 157 | void print_node(Node* n) 158 | { 159 | fprint_node(n, stdout); 160 | } 161 | 162 | void fdprint_node(Node* n, int fd) 163 | { 164 | FILE* f = fdopen(fd, "w"); 165 | fprint_node(n, f); 166 | fclose(f); 167 | } 168 | 169 | void fprint_node(Node* n, FILE* f) 170 | { 171 | switch (n->opcode) { 172 | case OP_ADD: add_p(n, f); break; 173 | case OP_SUB: sub_p(n, f); break; 174 | case OP_MUL: mul_p(n, f); break; 175 | case OP_DIV: div_p(n, f); break; 176 | case OP_MIN: min_p(n, f); break; 177 | case OP_MAX: max_p(n, f); break; 178 | case OP_POW: pow_p(n, f); break; 179 | 180 | case OP_SQUARE: square_p(n, f); break; 181 | case OP_SQRT: sqrt_p(n, f); break; 182 | case OP_SIN: sin_p(n, f); break; 183 | case OP_COS: cos_p(n, f); break; 184 | case OP_TAN: tan_p(n, f); break; 185 | case OP_ASIN: asin_p(n, f); break; 186 | case OP_ACOS: acos_p(n, f); break; 187 | case OP_ATAN: atan_p(n, f); break; 188 | case OP_NEG: neg_p(n, f); break; 189 | 190 | case OP_CONST: constant_p(n, f); break; 191 | case OP_X: X_p(n, f); break; 192 | case OP_Y: Y_p(n, f); break; 193 | case OP_Z: Z_p(n, f); break; 194 | default: 195 | fprintf(f, "Unknown opcode!\n"); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /libfab/tree/node/printers.h: -------------------------------------------------------------------------------- 1 | #ifndef PRINTERS_H 2 | #define PRINTERS_H 3 | 4 | #include 5 | 6 | struct Node_; 7 | 8 | /** @brief Recursively prints a node to standard output. */ 9 | void print_node(struct Node_* n); 10 | 11 | 12 | /** @brief Recursively prints a node to a given file descriptor */ 13 | void fdprint_node(struct Node_* n, int fd); 14 | 15 | 16 | /** @brief Recursively prints a node to a given file */ 17 | void fprint_node(struct Node_* n, FILE* file); 18 | 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /libfab/tree/node/results.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tree/node/node.h" 5 | 6 | void fill_results(Node* n, float value) 7 | { 8 | n->results.f = value; 9 | n->results.i = (Interval) { .lower=value, .upper=value}; 10 | 11 | // Fill the region cache 12 | for (int q = 0; q < MIN_VOLUME; ++q) 13 | n->results.r[q] = value; 14 | } 15 | -------------------------------------------------------------------------------- /libfab/tree/node/results.h: -------------------------------------------------------------------------------- 1 | #ifndef CACHE_H 2 | #define CACHE_H 3 | 4 | #include "util/interval.h" 5 | #include "util/region.h" 6 | #include "util/switches.h" 7 | 8 | struct Node_; 9 | 10 | /** @struct Results_ 11 | @brief Container for intermediate calculation results 12 | */ 13 | typedef struct Results_ 14 | { 15 | float f; 16 | Interval i; 17 | float r[MIN_VOLUME]; 18 | } Results; 19 | 20 | 21 | /** @brief Fills node results with a constant 22 | @details n->results.{f,i,r} are all set equal to the constant 23 | @param n Target node 24 | @param value Constant to fill 25 | */ 26 | void fill_results(struct Node_* n, float value); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /libfab/tree/packed.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKED_H 2 | #define PACKED_H 3 | 4 | #include 5 | 6 | #include "tree/tree.h" 7 | 8 | /** @struct ustack_ 9 | @brief A simple FIFO stack of unsigned integers. 10 | */ 11 | typedef struct ustack_ { 12 | /** @var count 13 | Stored value */ 14 | unsigned count; 15 | 16 | /** @var next 17 | Next item in the stack */ 18 | struct ustack_* next; 19 | } ustack; 20 | 21 | /** @struct PackedTree_ 22 | @brief A structure containing nodes organized by rank 23 | */ 24 | typedef struct PackedTree_ { 25 | /** @var nodes 26 | Node pointers, stored in rows indexed by level */ 27 | struct Node_*** nodes; 28 | 29 | /** @var active 30 | Number of active nodes, indexed by level */ 31 | unsigned* active; 32 | 33 | /** @var disabled 34 | Stacks of disabled node counts, indexed by level */ 35 | ustack** disabled; 36 | 37 | /** @var num_levels 38 | Number of levels in this tree */ 39 | unsigned num_levels; 40 | 41 | /** @var head 42 | Root of this tree */ 43 | struct Node_* head; 44 | } PackedTree; 45 | 46 | 47 | /** @brief Converts a MathTree into a PackedTree 48 | @param tree A well-formed, deduplicated MathTree. 49 | @returns A PackedTree with the same nodes. 50 | @details Nodes are not copied; they point back to the original MathTree 51 | */ 52 | PackedTree* make_packed(struct MathTree_* tree); 53 | 54 | 55 | /** @brief Frees a packed tree. 56 | @details Does not free nodes, since they should be 57 | pointers to the same nodes as the original MathTree. 58 | */ 59 | void free_packed(PackedTree* packed); 60 | 61 | 62 | /** @brief Travels down the tree, disabling nodes whose values will not matter 63 | upon further spatial subdivision 64 | 65 | @details 66 | Nodes are disabled by swapping them to the back of their respective list 67 | and decrementing the active count (so that the evaluation loop doesn't 68 | touch them at all). 69 | 70 | A count of nodes disabled in this pass is stored on the top of the 71 | disabled stack (used by enable_nodes). 72 | */ 73 | void disable_nodes(PackedTree* tree); 74 | 75 | 76 | /** @brief Disables nodes that won't affect the output truth value 77 | (i.e. whether it is larger or smaller than zero) 78 | 79 | @details 80 | Must be called after disable_nodes, otherwise the stacks won't be 81 | in place to store the number of disabled nodes. 82 | */ 83 | void disable_nodes_binary(PackedTree* tree); 84 | 85 | 86 | /** @brief Enables nodes that were disabled on the most recent call to disable_nodes. 87 | 88 | @details 89 | Nodes are enabled by increasing the value of active[level][op], so that 90 | an evaluation continues further down the list. 91 | */ 92 | void enable_nodes(PackedTree* tree); 93 | 94 | /** @brief Returns a bit mask containing active axes in a tree. 95 | @details 96 | The bit mask is of the form (x_active << 2) | (y_active << 1) | (z_active) 97 | */ 98 | uint8_t active_axes(const PackedTree* const tree); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /libfab/tree/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_H 2 | #define PARSER_H 3 | 4 | struct MathTree_; 5 | 6 | /** @brief Parses a prefix-notation math string 7 | @param input A null-terminated math string 8 | @returns The constructed MathTree, or NULL if failed 9 | */ 10 | struct MathTree_* parse(const char* input); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /libfab/tree/render.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_RENDER_H 2 | #define TREE_RENDER_H 3 | 4 | #include 5 | 6 | #include "util/region.h" 7 | 8 | struct PackedTree_; 9 | 10 | /** @brief Recursively renders a tree 11 | @param tree Target tree 12 | @param region Region to render (ni, nj must be image dimensions) 13 | @param img Target image to populate 14 | @param halt Flag to abort (if *halt becomes true) 15 | */ 16 | void render8(struct PackedTree_* tree, Region region, 17 | uint8_t** img, volatile int* halt); 18 | 19 | 20 | 21 | /** @brief Recursively renders a tree 22 | @param tree Target tree 23 | @param region Region to render (ni, nj must be image dimensions) 24 | @param img Target image to populate 25 | @param halt Flag to abort (if *halt becomes true) 26 | */ 27 | void render16(struct PackedTree_* tree, Region region, 28 | uint16_t** img, volatile int* halt); 29 | 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /libfab/tree/tree.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_H 2 | #define TREE_H 3 | 4 | #include "tree/node/opcodes.h" 5 | 6 | /** @struct MathTree_ 7 | @brief A structure containing nodes organized by rank and opcode. 8 | */ 9 | typedef struct MathTree_ { 10 | /** @var nodes 11 | Array of nodes, indexed by [level][opcode][node] */ 12 | struct Node_** ((*nodes)[LAST_OP]); 13 | 14 | /** @var active 15 | Active node count, indexed by [level][opcode] */ 16 | unsigned (*active)[LAST_OP]; 17 | 18 | 19 | /** @var constants 20 | Array of constant nodes */ 21 | struct Node_ **constants; 22 | 23 | /** @var num_constants 24 | Size of contants array */ 25 | unsigned num_constants; 26 | 27 | /** @var head 28 | Pointer to tree head*/ 29 | struct Node_* head; 30 | 31 | /** @var num_levels 32 | Number of levels in the tree */ 33 | unsigned num_levels; 34 | } MathTree; 35 | 36 | 37 | /** @brief Creates a new tree, allocating the appropriate arrays. 38 | @param num_levels Number of levels in tree 39 | @param num_constants Number of constants in tree 40 | */ 41 | MathTree* new_tree(unsigned num_levels, unsigned num_constants); 42 | 43 | /** @brief Frees a tree and all of its nodes 44 | @param tree Target tree 45 | */ 46 | void free_tree(MathTree* tree); 47 | 48 | 49 | 50 | /** @brief Prints a math tree to stdout. */ 51 | void print_tree(MathTree* tree); 52 | /** @brief Verbosely prints a math tree to stdout. */ 53 | void print_tree_verbose(MathTree* tree); 54 | 55 | 56 | /** @brief Prints a math tree to a given file descriptor. */ 57 | void fprint_tree(MathTree* tree, int fd); 58 | /** @brief Verbosely prints a math tree to a given file descriptor. */ 59 | void fprint_tree_verbose(MathTree* tree, int fd); 60 | 61 | 62 | /** @brief Saves a graphviz array representation of the tree to the given file 63 | */ 64 | void dot_arrys(const MathTree* const tree, const char* filename); 65 | 66 | 67 | /** @brief Clones a tree and all of its nodes 68 | @details The tree must not have any deactivated nodes. 69 | */ 70 | MathTree* clone_tree(MathTree* orig); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /libfab/util/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_CONSTANTS_H 2 | #define UTIL_CONSTANTS_H 3 | 4 | #define EPSILON 1e-6 5 | 6 | #endif 7 | 8 | -------------------------------------------------------------------------------- /libfab/util/interval.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERVAL_H 2 | #define INTERVAL_H 3 | 4 | /* Interval (struct) 5 | * 6 | * One-dimensional interval used to hold a floating-point range. 7 | */ 8 | typedef struct Interval_{ 9 | float lower; 10 | float upper; 11 | } Interval; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /libfab/util/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_MACROS_H 2 | #define UTIL_MACROS_H 3 | 4 | #define BOUND(A, L, H) (((A) >= (L) && (A) <= (H)) ? (A) : ((A >= L) ? (H) : (L))) 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /libfab/util/path.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "util/path.h" 7 | 8 | Path* backtrace_path(Path* const p, Path* const orig) 9 | { 10 | if (p == NULL) return orig; 11 | if (p == orig || !p->prev) return p; 12 | return backtrace_path(p->prev, orig); 13 | } 14 | 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | _STATIC_ 18 | void disconnect_node(Path* const p) 19 | { 20 | for (int i=0; i < p->ptr_count; ++i) { 21 | *(p->ptrs[i]) = NULL; 22 | } 23 | free(p->ptrs); 24 | p->ptrs = NULL; 25 | p->ptr_count = 0; 26 | } 27 | 28 | void disconnect_path(Path* const start) 29 | { 30 | Path* current = start; 31 | do { 32 | disconnect_node(current); 33 | current = current->next; 34 | } while (current != NULL && current != start); 35 | } 36 | 37 | //////////////////////////////////////////////////////////////////////////////// 38 | 39 | Path* decimate_path(Path* start, float error) 40 | { 41 | Path* current = start; 42 | _Bool at_start = true; 43 | 44 | while (current && (current != start || at_start)) { 45 | // Store the next step in the linked list 46 | Path* next = current->next; 47 | at_start = false; 48 | 49 | // If we have two segments in a row, check to see if we can 50 | // delete the middle node (in the variable 'current') 51 | if (current->prev && current->next && current->prev != current->next) 52 | { 53 | const float A = sqrt(pow(current->x - current->prev->x,2) + 54 | pow(current->y - current->prev->y, 2)); 55 | const float B = sqrt(pow(current->x - current->next->x,2) + 56 | pow(current->y - current->next->y, 2)); 57 | const float C = sqrt(pow(current->next->x - current->prev->x,2) + 58 | pow(current->next->y - current->prev->y, 2)); 59 | 60 | // Sort so that a >= b >= c 61 | const float a = fmax(A, fmax(B, C)); 62 | const float c = fmin(A, fmin(B, C)); 63 | const float b = A + B + C - a - c; 64 | 65 | // Heron's formula (numerically stable version) 66 | const float area = sqrt((a+(b+c))*(c-(a-b))* 67 | (c+(a-b))*(a+(b-c)))/4; 68 | 69 | if (area < error) { 70 | // Stitch the ends together 71 | current->prev->next = current->next; 72 | current->next->prev = current->prev; 73 | 74 | // Disconnect this node from the array 75 | disconnect_node(current); 76 | 77 | // Adjust the start point so that we don't do loop 78 | // detection by searching for a deleted node 79 | if (current == start) { 80 | start = current->next; 81 | at_start = true; 82 | } 83 | free(current); 84 | } 85 | } 86 | 87 | // Continue to travel through the list 88 | current = next; 89 | } 90 | 91 | return start; 92 | } 93 | 94 | 95 | void free_paths(Path** const paths, int count) 96 | { 97 | for (int p=0; p < count; ++p) free_path(paths[p]); 98 | free(paths); 99 | } 100 | 101 | 102 | void free_path(Path* const start) 103 | { 104 | Path* pt = start; 105 | do { 106 | disconnect_node(pt); 107 | Path* const prev = pt; 108 | pt = pt->next; 109 | free(prev); 110 | } while (pt != NULL && pt != start); 111 | } 112 | -------------------------------------------------------------------------------- /libfab/util/path.h: -------------------------------------------------------------------------------- 1 | #ifndef PATH_H 2 | #define PATH_H 3 | 4 | /* struct Path_ 5 | * 6 | * Stores x and y coordinates, pointers to neighbors, and 7 | * pointers to the places where cells are pointing to it 8 | * (so that it can disconnect itself from the cells when 9 | * being deleted). 10 | */ 11 | typedef struct Path_ { 12 | struct Path_* prev; 13 | struct Path_* next; 14 | float x, y, z; 15 | struct Path_*** ptrs; // parent pointers to this path 16 | int ptr_count; 17 | } Path; 18 | 19 | 20 | /* backtrace 21 | * 22 | * Travels backwards along a path until reaching a start node 23 | * or the orig node. 24 | */ 25 | Path* backtrace_path(Path* const p, Path* const orig); 26 | 27 | 28 | /* disconnect_path 29 | * 30 | * Removes a path from the ptrs array, using the self 31 | * ptrs stored in each path node. 32 | */ 33 | void disconnect_path(Path* const p); 34 | 35 | 36 | /* decimate_path 37 | * 38 | * Removes nodes in the path that can be removed without significant error. 39 | * Returns a new start node, which will be different if the original start 40 | * node is removed during the decimation. 41 | */ 42 | Path* decimate_path(Path* start, float error); 43 | 44 | 45 | /* free_paths 46 | * 47 | * Frees each path in an array and the array itself. 48 | */ 49 | void free_paths(Path** const paths, int count); 50 | 51 | 52 | /* free_path 53 | * 54 | * Frees a single path, disconnecting nodes from their referents. 55 | */ 56 | void free_path(Path* const start); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /libfab/util/region.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_H 2 | #define REGION_H 3 | 4 | #include 5 | 6 | #include "util/vec3f.h" 7 | 8 | struct ASDF_; 9 | struct PackedTree_; 10 | 11 | typedef struct Region_ { 12 | uint32_t imin, jmin, kmin; 13 | uint32_t ni, nj, nk; 14 | uint64_t voxels; 15 | 16 | // Real-space coordinate arrays 17 | float *X, *Y, *Z; 18 | 19 | // Luminosity array (same size as Z) 20 | uint16_t* L; 21 | } Region; 22 | 23 | 24 | /* build_arrays 25 | * 26 | * Populates the X, Y, Z, and L arrays of a region. 27 | */ 28 | void build_arrays(Region* const R, 29 | const float xmin, const float ymin, const float zmin, 30 | const float xmax, const float ymax, const float zmax); 31 | 32 | 33 | /* free_arrays 34 | * 35 | * Frees the X, Y, Z, and L arrays of a region 36 | */ 37 | void free_arrays(Region* const R); 38 | 39 | 40 | /* bisect 41 | * 42 | * Splits a region in two along its longest axis. 43 | * Returns 1 if the region is of volume 1 and can't be split, 44 | * 0 otherwise. 45 | */ 46 | int bisect(const Region r, Region* const A, Region* const B); 47 | 48 | /* octsect 49 | * 50 | * Splits a region in two along all three axes if possible, storing 51 | * results in the array 'out'. Returns a bitfield with bits set 52 | * in the positions where a region was stored. 53 | */ 54 | uint8_t octsect(const Region R, Region* const out); 55 | 56 | /** @brief Splits a region along each active axis of the given MathTree 57 | @returns Bit mask of newly populated regions. 58 | */ 59 | int octsect_active(const Region r, const struct PackedTree_* tree, 60 | Region* const out); 61 | 62 | /* octsect_merged 63 | * 64 | * Splits a region in two along each axis if the provided ASDF 65 | * splits along that axis. 66 | */ 67 | uint8_t octsect_merged(const Region R, const struct ASDF_* const asdf, 68 | Region* const out); 69 | 70 | /* split 71 | * 72 | * Repeatedly cut a region in half along its longest axis to 73 | * create some number of subregions, stored in the array 'out'. 74 | */ 75 | int split(const Region R, Region* const out, const int count); 76 | 77 | 78 | /* split_xy 79 | * 80 | * Repeatedly cut a region in half along the X or Y axis to 81 | * create some number of subregions, stored in the array 'out'. 82 | */ 83 | int split_xy(const Region R, Region* const out, const int count); 84 | 85 | 86 | /* bisect_{x,y,z} 87 | * 88 | * Bisects a region along one axis. 89 | */ 90 | void bisect_x(const Region r, Region* const A, Region* const B); 91 | void bisect_y(const Region r, Region* const A, Region* const B); 92 | void bisect_z(const Region r, Region* const A, Region* const B); 93 | 94 | 95 | /* bound_region 96 | * 97 | * Return a new region that is bounded by both the original region 98 | * and the size of the top-level ASDF cell. 99 | */ 100 | Region bound_region(const struct ASDF_* const asdf, const Region r); 101 | 102 | 103 | /* rot_bound_region 104 | * 105 | * Return a new region that is bounded by both the original region 106 | * and the size of the top-level ASDF cell, rotated to a new position. 107 | */ 108 | Region rot_bound_region(const struct ASDF_* const asdf, const Region r, 109 | const float M[4]); 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /libfab/util/squares.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | Data tables for marching squares 5 | 6 | 7 | Vertices have the following IDs 8 | 1 3 9 | -------- 10 | | | 11 | | | ^ Y 12 | | | | 13 | -------- --> X 14 | 0 2 15 | 16 | (divided by two from d[i] indices, since we're 17 | looking at a flat ASDF and we don't care about z) 18 | 19 | 20 | Edges are numbered as follows: 21 | 1 22 | -------- 23 | | | 24 | 2 | | 3 ^ Y 25 | | | | 26 | -------- --> X 27 | 0 28 | 29 | 30 | */ 31 | 32 | // For a given set of filled corners, this array defines 33 | // the cell edges from which we draw interior edges 34 | static const int8_t EDGE_MAP[16][2][2] = { 35 | {{-1, -1}, {-1, -1}}, // ---- 36 | {{0, 2}, {-1, -1}}, // ---0 37 | {{2, 1}, {-1, -1}}, // --1- 38 | {{0, 1}, {-1, -1}}, // --10 39 | {{3, 0}, {-1, -1}}, // -2-- 40 | {{3, 2}, {-1, -1}}, // -2-0 41 | {{3, 0}, { 2, 1}}, // -21- 42 | {{3, 1}, {-1, -1}}, // -210 43 | 44 | {{1, 3}, {-1, -1}}, // 3--- 45 | {{1, 3}, { 0, 2}}, // 3--0 46 | {{2, 3}, {-1, -1}}, // 3-1- 47 | {{0, 3}, {-1, -1}}, // 3-10 48 | {{1, 0}, {-1, -1}}, // 32-- 49 | {{1, 2}, {-1, -1}}, // 32-0 50 | {{2, 0}, {-1, -1}}, // 321- 51 | {{-1,-1}, {-1, -1}} // 3210 52 | }; 53 | 54 | 55 | // Indexed by edge number, returns vertex index 56 | static const int8_t VERTEX_MAP[4][2] = { 57 | {0, 2}, 58 | {3, 1}, 59 | {1, 0}, 60 | {2, 3} 61 | }; 62 | -------------------------------------------------------------------------------- /libfab/util/switches.h: -------------------------------------------------------------------------------- 1 | #ifndef SWITCHES_H 2 | #define SWITCHES_H 3 | 4 | #define MIN_VOLUME 64 // Minimum volume for interval evaluation 5 | #define DEDUPLICATE 1 // Remove duplicate nodes when combining MathTrees 6 | #define PRUNE 1 // Deactivate inactive tree branches 7 | 8 | #define DEBUG 0 // Print debug data 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /libfab/util/vec3f.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "util/vec3f.h" 4 | 5 | // Take coordinates in the cell's frame and project them into the view frame. 6 | Vec3f project(const Vec3f p, const float M[4]) 7 | { 8 | // M = {cos a, sin a, cos b, sin b} 9 | 10 | // Spin around the Z axis 11 | float x_ = M[0]*p.x - M[1]*p.y, 12 | y_ = M[1]*p.x + M[0]*p.y, 13 | z_ = p.z; 14 | 15 | // Then spin around the X axis 16 | return (Vec3f){ .x = x_, 17 | .y = M[2]*y_ + M[3]*z_, 18 | .z = -M[3]*y_ + M[2]*z_}; 19 | } 20 | 21 | //////////////////////////////////////////////////////////////////////////////// 22 | 23 | void project_cube(const Interval X, const Interval Y, const Interval Z, 24 | Interval* X_, Interval* Y_, Interval* Z_, 25 | const float M[4]) 26 | { 27 | Vec3f p = project( (Vec3f){X.lower, Y.lower, Z.lower}, M); 28 | *X_ = (Interval){ p.x, p.x }; 29 | *Y_ = (Interval){ p.y, p.y }; 30 | *Z_ = (Interval){ p.z, p.z }; 31 | for (int n=1; n < 8; ++n) { 32 | p = project( (Vec3f){ 33 | (n & 4) ? X.upper : X.lower, 34 | (n & 2) ? Y.upper : Y.lower, 35 | (n & 1) ? Z.upper : Z.lower}, M); 36 | if (p.x < X_->lower) X_->lower = p.x; 37 | if (p.x > X_->upper) X_->upper = p.x; 38 | 39 | if (p.y < Y_->lower) Y_->lower = p.y; 40 | if (p.y > Y_->upper) Y_->upper = p.y; 41 | 42 | if (p.z < Z_->lower) Z_->lower = p.z; 43 | if (p.z > Z_->upper) Z_->upper = p.z; 44 | } 45 | } 46 | 47 | //////////////////////////////////////////////////////////////////////////////// 48 | 49 | // Take coordinates in the view frame and project them into the cell frame. 50 | Vec3f deproject(const Vec3f p__, const float M[4]) 51 | { 52 | // M = {ca, sa, cb, sb} 53 | float x_ = p__.x, 54 | y_ = (M[2]*p__.y - M[3]*p__.z), 55 | z_ = (M[2]*p__.z + M[3]*p__.y); 56 | 57 | return (Vec3f){ .x = (M[0]*x_ + M[1]*y_), 58 | .y = (M[0]*y_ - M[1]*x_), 59 | .z = z_ }; 60 | } 61 | 62 | //////////////////////////////////////////////////////////////////////////////// 63 | 64 | void deproject_cube(const Interval X, const Interval Y, const Interval Z, 65 | Interval* X_, Interval* Y_, Interval* Z_, 66 | const float M[4]) 67 | { 68 | Vec3f p = deproject( (Vec3f){X.lower, Y.lower, Z.lower}, M); 69 | *X_ = (Interval){ p.x, p.x }; 70 | *Y_ = (Interval){ p.y, p.y }; 71 | *Z_ = (Interval){ p.z, p.z }; 72 | for (int n=1; n < 8; ++n) { 73 | p = deproject( (Vec3f){ 74 | (n & 4) ? X.upper : X.lower, 75 | (n & 2) ? Y.upper : Y.lower, 76 | (n & 1) ? Z.upper : Z.lower}, M); 77 | if (p.x < X_->lower) X_->lower = p.x; 78 | if (p.x > X_->upper) X_->upper = p.x; 79 | 80 | if (p.y < Y_->lower) Y_->lower = p.y; 81 | if (p.y > Y_->upper) Y_->upper = p.y; 82 | 83 | if (p.z < Z_->lower) Z_->lower = p.z; 84 | if (p.z > Z_->upper) Z_->upper = p.z; 85 | } 86 | } 87 | 88 | //////////////////////////////////////////////////////////////////////////////// 89 | 90 | Vec3f normalize(const Vec3f v) 91 | { 92 | float L = sqrt(pow(v.x, 2) + pow(v.y, 2) + pow(v.z, 2)); 93 | return (Vec3f){ .x=v.x/L, .y=v.y/L, .z=v.z/L }; 94 | } 95 | -------------------------------------------------------------------------------- /libfab/util/vec3f.h: -------------------------------------------------------------------------------- 1 | #ifndef VEC3F_H 2 | #define VEC3F_H 3 | 4 | #include "util/interval.h" 5 | 6 | typedef struct Vec3f_ { 7 | float x; 8 | float y; 9 | float z; 10 | } Vec3f; 11 | 12 | /* project 13 | * 14 | * Transform from cell frame to view frame 15 | * 16 | * M should be floats with values [cos(a), sin(a), cos(b), sin(b)] 17 | * where a is rotation about the z axis and b is rotation about 18 | * the new x axis 19 | */ 20 | Vec3f project(const Vec3f p, const float M[4]); 21 | 22 | 23 | /* project_cube 24 | * 25 | * Transforms a cubical region from cell frame to view frame, storing 26 | * the maximum possible bounding box in X_, Y_, and Z_ variables. 27 | */ 28 | void project_cube(const Interval X, const Interval Y, const Interval Z, 29 | Interval* X_, Interval* Y_, Interval* Z_, 30 | const float M[4]); 31 | 32 | 33 | /* deproject 34 | * 35 | * Transforms coordinates from view frame to cell frame 36 | * M is the same matrix as M in project. 37 | */ 38 | Vec3f deproject(const Vec3f p__, const float M[4]); 39 | 40 | 41 | /* deproject_cube 42 | * 43 | * Transforms a cubical region from view frame to cell frame, storing 44 | * the maximum possible bounding box in X_, Y_, and Z_ variables. 45 | */ 46 | void deproject_cube(const Interval X, const Interval Y, const Interval Z, 47 | Interval* X_, Interval* Y_, Interval* Z_, 48 | const float M[4]); 49 | 50 | 51 | /* normalize 52 | * 53 | * Normalizes a vector so that it is 1 unit long. 54 | */ 55 | Vec3f normalize(const Vec3f v); 56 | 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /util/app/.gitignore: -------------------------------------------------------------------------------- 1 | *.ttf 2 | -------------------------------------------------------------------------------- /util/app/README: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | kokopelli is a tool for computer-aided design and manufacturing (CAD/CAM). 4 | 5 | It uses Python as a hardware description language for solid models. A set of core libraries define common shapes and transforms, but users are free to extend their designs with their own definitions. 6 | 7 | The CAM tools enable path planning for two, three, and five-axis machines. At the moment, paths can be exported to Universal and Epilog laser cutters, the Roland Modela mini-mill, three and five-axis Shopbot machines, and plain G-code. A modular workflow system makes adding new machines easy. 8 | 9 | In addition, models can be saved as .svg and water-tight .stl files. 10 | 11 | 12 | Online 13 | ====== 14 | kokopelli is hosted on GitHub at https://github.com/mkeeter/kokopelli 15 | 16 | 17 | License 18 | ======= 19 | This work may be reproduced, modified, distributed, performed, and displayed for any purpose, but must acknowledge the "kokopelli" project. Copyright is retained and must be preserved. The work is provided as is; no warranty is provided, and users accept all liability. 20 | 21 | 22 | Copyright 23 | ========= 24 | (c) 2012-2013 Massachusetts Institute of Technology 25 | -------------------------------------------------------------------------------- /util/app/make_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This is a setup.py script generated by py2applet 4 | 5 | Usage: 6 | python setup.py py2app 7 | """ 8 | 9 | from setuptools import setup 10 | import shutil 11 | import os 12 | import stat 13 | import subprocess 14 | import glob 15 | 16 | # Import the make_icon module, which uses ImageMagick and png2icns 17 | # to make an application icon 18 | if (not os.path.isfile('ko.icns') or 19 | os.path.getctime('ko.icns') < os.path.getctime('make_icon.py')): 20 | print "Generating icon using ImageMagick and png2icns" 21 | import make_icon 22 | 23 | # Trick to make this run properly 24 | import sys 25 | sys.argv += ['py2app'] 26 | 27 | subprocess.call(['make'], cwd='../..') 28 | 29 | try: shutil.rmtree('build') 30 | except OSError: pass 31 | 32 | try: shutil.rmtree('koko') 33 | except OSError: pass 34 | 35 | try: os.remove('kokopelli.py') 36 | except OSError: pass 37 | 38 | try: shutil.rmtree('kokopelli.app') 39 | except OSError: pass 40 | 41 | try: shutil.rmtree('examples') 42 | except OSError: pass 43 | 44 | # Modify a line in __init__.py to store current hash 45 | git_hash = subprocess.check_output( 46 | "git log --pretty=format:'%h' -n 1".split(' '))[1:-1] 47 | if 'working directory clean' not in subprocess.check_output(['git','status']): 48 | git_hash += '+' 49 | 50 | # This is the pythons script that we're bundling into an application. 51 | shutil.copy('../../kokopelli','kokopelli.py') 52 | shutil.copytree('../../koko','koko') 53 | 54 | with open('koko/__init__.py', 'r') as f: 55 | lines = f.readlines() 56 | 57 | with open('koko/__init__.py', 'w') as f: 58 | for L in lines: 59 | if 'HASH = None' in L: 60 | f.write("HASH = '%s'\n" % git_hash) 61 | else: 62 | f.write(L) 63 | 64 | 65 | # Setup details for py2app 66 | APP = ['kokopelli.py'] 67 | DATA_FILES = glob.glob('../../koko/lib/*.py') 68 | 69 | OPTIONS = {'argv_emulation': True, 70 | 'iconfile':'ko.icns', 71 | 'includes':'koko.lib.pcb,koko.lib.shapes'} 72 | 73 | # Run py2app to bundle everything. 74 | setup( 75 | app=APP, 76 | data_files=DATA_FILES, 77 | options={'py2app': OPTIONS}, 78 | setup_requires=['py2app'], 79 | ) 80 | 81 | # Copy libtree 82 | shutil.copy('../../libfab/libfab.dylib', 83 | 'dist/kokopelli.app/Contents/Frameworks/libfab.dylib') 84 | shutil.copy('/usr/local/lib/libpng.dylib', 85 | 'dist/kokopelli.app/Contents/Frameworks/libpng.dylib') 86 | 87 | # Copy the readme and examples into the distribution directory, then zip it up 88 | shutil.rmtree('build') 89 | shutil.rmtree('koko') 90 | shutil.os.remove('kokopelli.py') 91 | 92 | shutil.move('dist/kokopelli.app', '.') 93 | shutil.rmtree('dist') 94 | 95 | shutil.copytree('../../examples', './examples') 96 | subprocess.call( 97 | 'tar -cvzf kokopelli.tar.gz README kokopelli.app examples'.split(' ') 98 | ) 99 | shutil.rmtree('examples') 100 | 101 | if 'mkeeter' in subprocess.check_output('whoami') and git_hash[-1] != '+': 102 | print "Uploading to mattkeeter.com (Ctrl+C to cancel)" 103 | subprocess.call( 104 | 'scp kokopelli.tar.gz mkeeter@mattkeeter.com:mattkeeter.com/projects/kokopelli/kokopelli.tar.gz'.split(' ') 105 | ) 106 | -------------------------------------------------------------------------------- /util/app/make_icon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | import sys 5 | import os 6 | import shutil 7 | 8 | def convert(args): 9 | subprocess.call('convert ' + args.replace('\n',' '), shell=True) 10 | 11 | def progress(i, j, t=4): 12 | print '\r' + ' '*t + '[' + '|'*i + '-'*(j-i) + ']', 13 | sys.stdout.flush() 14 | 15 | def gen(side, indent=4): 16 | black = '-size %{0}x{0} canvas:black'.format(side) 17 | blank = black + ' -alpha transparent'.format(side) 18 | 19 | progress(0,10,indent) 20 | convert(blank + ''' 21 | -fill black -draw "roundrectangle {0},{0} {1},{1} {2},{2}" 22 | -channel RGBA -gaussian-blur 0x{3} shadow.png'''.format( 23 | side/20, side - side/20, side/40, side/50 24 | ) 25 | ) 26 | 27 | progress(1,10,indent) 28 | convert(black + """ 29 | -fill white -draw "roundrectangle {0},{0} {1},{1} {2},{2}" mask.png 30 | """.format( 31 | side/20, side - side/20, side/40 32 | ) 33 | ) 34 | 35 | 36 | progress(2,10,indent) 37 | convert(''' 38 | -size {0}x{0} radial-gradient: -crop {1}x{1}+{1}+0 39 | -level 19.5%,20% -negate gradient.png'''.format( 40 | side*4, side 41 | ) 42 | ) 43 | 44 | progress(3,10,indent) 45 | convert(''' 46 | gradient.png mask.png -alpha Off -compose Multiply -composite 47 | gradient.png 48 | ''') 49 | 50 | progress(4,10,indent) 51 | convert(''' 52 | gradient.png +clone -compose CopyOpacity -composite gradient.png 53 | ''') 54 | convert(''' 55 | gradient.png -fill "rgb(88,110,117)" -colorize 100% 56 | -compose CopyOpacity gradient.png -composite gradient.png 57 | ''') 58 | 59 | progress(5,10,indent) 60 | convert(black + ''' 61 | -fill "rgb(240,240,240)" -font {3} -pointsize {0} 62 | -annotate +{1}+{2} "ko" text.png'''.format( 63 | side*0.7, side*0.15, side*0.75, 'Myriad-Pro-Bold-SemiCondensed' 64 | ) 65 | ) 66 | 67 | progress(6,10,indent) 68 | convert('''text.png 69 | -motion-blur 0x{0} 70 | -motion-blur 0x{0}+-90 71 | -motion-blur 0x{0}+-45 72 | -brightness-contrast 40 73 | text.png 74 | -alpha Off -compose CopyOpacity -composite text.png'''.format( 75 | side/140. 76 | ) 77 | ) 78 | 79 | progress(7,10,indent) 80 | convert(blank + """ 81 | -fill "rgb(0,43,54)" -draw "roundrectangle {0},{0} {1},{1} {2},{2}" 82 | base.png""".format( 83 | side/20, side - side/20, side/40 84 | ) 85 | ) 86 | 87 | progress(9,10,indent) 88 | convert(''' 89 | base.png gradient.png -composite base.png 90 | ''') 91 | 92 | progress(8,10,indent) 93 | convert('''shadow.png base.png -composite text.png -composite icon.png''') 94 | 95 | progress(10,10,indent) 96 | shutil.copy('icon.png', 'icon%i.png' % side) 97 | 98 | for img in ['icon', 'gradient', 'text', 'base', 'mask', 'shadow']: 99 | os.remove('%s.png' % img) 100 | 101 | if len(sys.argv) < 2: 102 | sizes = (16, 32, 128, 256, 512) 103 | for i in sizes: 104 | print '%i:' % i 105 | gen(i) 106 | print '' 107 | subprocess.call( 108 | ['png2icns', 'ko.icns'] + 109 | ['icon%i.png' % i for i in sizes[::-1]] 110 | ) 111 | subprocess.call(['rm'] + ['icon%i.png' % i for i in sizes]) 112 | 113 | else: 114 | gen(int(sys.argv[1]), indent=0) 115 | -------------------------------------------------------------------------------- /util/doxygen/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cd koko && doxygen Doxyfile 3 | cd libfab && doxygen Doxyfile 4 | -------------------------------------------------------------------------------- /util/install_wxpython3.0.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "Using apt-get to install necessary packages" 6 | yes | sudo apt-get install libgtk2.0-dev freeglut3-dev libsdl1.2-dev libgstreamer-plugins-base0.10-dev libwebkitgtk-dev 7 | 8 | cd ~ 9 | echo "Downloading wxPython 3.0.0.0" 10 | wget "http://downloads.sourceforge.net/wxpython/wxPython-src-3.0.0.0.tar.bz2" 11 | tar xvjf wxPython-src-3.0.0.0.tar.bz2 12 | 13 | cd wxPython-src-3.0.0.0 14 | 15 | cd wxPython 16 | echo "Compiling wxPython 3.0.0.0" 17 | sudo python build-wxpython.py --build_dir=../bld --install 18 | echo "Updating library cache" 19 | sudo ldconfig 20 | 21 | echo "Cleaning up" 22 | cd ~ 23 | rm wxPython-src-3.0.0.0.tar.bz2 24 | sudo rm -rf wxPython-src-3.0.0.0 25 | 26 | echo "Testing:" 27 | python -c "import wx; print 'wx version =', wx.version()" 28 | --------------------------------------------------------------------------------