├── .gitignore ├── .travis.yml ├── .scrutinizer.yml ├── phpunit.xml.dist ├── composer.json ├── README.md ├── LICENSE ├── stl2svg.php ├── stl2gcode.php ├── tests ├── GeometryTest.php ├── VectorTest.php ├── STLSliceTest.php └── STLMillingEditTest.php └── src ├── Geometry.php ├── Examples ├── STL2Svg.php ├── STL2GCode.php └── STLMillingEdit.php ├── Vector.php └── STLSlice.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor/ 3 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | 4 | php: 5 | - '7.0' 6 | - '7.1' 7 | 8 | install: 9 | - travis_retry composer install 10 | 11 | cache: 12 | directories: 13 | - .phpunit -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | environment: 3 | php: '7.0.6' 4 | 5 | tools: 6 | php_code_sniffer: 7 | config: 8 | standard: PSR2 9 | 10 | checks: 11 | php: 12 | code_rating: true 13 | duplication: true -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | tests 12 | 13 | 14 | 15 | 16 | src 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php3d/stlslice", 3 | "type": "library", 4 | "description": "PHP 7 library for slicing each layer of an STL file", 5 | "keywords": ["stl","3d", "stereolithography", "3d printing"], 6 | "homepage": "https://github.com/fgheorghe/stlslice", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Grosan Flaviu Gheorghe", 11 | "email": "fgheorghe@grosan.co.uk", 12 | "homepage": "http://grosan.co.uk", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=7.0", 18 | "ext-bcmath": "*", 19 | "ext-gd": "*", 20 | "php3d/stl": "1.0.2", 21 | "phpunit/phpunit": "5.4.6", 22 | "mockery/mockery": "0.9.5" 23 | }, 24 | "autoload": { 25 | "classmap": [ 26 | "src/" 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php3d/stlslice # 2 | 3 | [![Build Status](https://travis-ci.org/fgheorghe/stlslice.svg?branch=master)](https://travis-ci.org/fgheorghe/stlslice) 4 | 5 | ## Synopsis 6 | 7 | This library provides PHP 7 functionality for slicing STL formatted 3D objects, and converting them to SVG or GCODE. 8 | 9 | NOTE: GCode conversion is highly experimental - change to suit your needs. 10 | 11 | NOTE: Requires bcscale(16); 12 | 13 | ## Set-up 14 | 15 | Add this to your composer.json file: 16 | 17 | ```javascript 18 | [...] 19 | "require": { 20 | [...] 21 | "php3d/stlslice": "1.*" 22 | } 23 | ``` 24 | 25 | Then run composer: 26 | 27 | ```bash 28 | composer.phar install 29 | ``` 30 | 31 | ## Examples 32 | 33 | Extract layers: 34 | 35 | ```PHP 36 | $layers = (new \php3d\stlslice\STLSlice($stl, 10))->slice(); 37 | ``` 38 | 39 | Convert to SVG: 40 | 41 | See stl2svg.php 42 | 43 | Convert to GCode (milling machine): 44 | 45 | See stl2gcode.php 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Grosan Flaviu Gheorghe 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /stl2svg.php: -------------------------------------------------------------------------------- 1 | toArray())) 25 | ->extractMillingContent() 26 | ->getStlFileContentArray() 27 | ); 28 | 29 | echo "[+] Slicing objects...\n"; 30 | $layers = (new STLSlice($mill, 10)) 31 | ->slice(); 32 | 33 | echo "[+] Generating SVG...\n"; 34 | file_put_contents($argv[2], (new STL2Svg($layers))->toSvgString()); 35 | 36 | echo "[++] Done.\n"; 37 | } catch (Exception $ex) { 38 | die("[-] Can not convert file: " . $ex->getMessage()); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /stl2gcode.php: -------------------------------------------------------------------------------- 1 | toArray())) 25 | ->extractMillingContent() 26 | ->getStlFileContentArray() 27 | ); 28 | 29 | echo "[+] Slicing objects...\n"; 30 | $layers = (new STLSlice($mill, 10)) 31 | ->slice(); 32 | 33 | echo "[+] Generating GCode...\n"; 34 | file_put_contents($argv[2], (new STL2GCode($layers, 10))->toGCodeString()); 35 | 36 | echo "[++] Done.\n"; 37 | } catch (Exception $ex) { 38 | die("[-] Can not convert file: " . $ex->getMessage()); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/GeometryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stl; 12 | 13 | use php3d\stlslice\Geometry; 14 | use php3d\stlslice\Vector; 15 | 16 | /** 17 | * @covers Geometry 18 | */ 19 | class GeometryTest extends \PHPUnit_Framework_TestCase 20 | { 21 | public function setUp() 22 | { 23 | bcscale(16); 24 | } 25 | 26 | /** 27 | * @expectedException \Exception 28 | */ 29 | public function testIntersectFailsForLineParallelToPlane() 30 | { 31 | (new Geometry())->intersect( 32 | new Vector(1, 1, 1), 33 | new Vector(1, 1, 1), 34 | new Vector(1, 1, 1), 35 | new Vector(1, 1, 1) 36 | ); 37 | } 38 | 39 | public function testIntersectLineWithPlane() 40 | { 41 | $this->assertEquals(new Vector( 42 | 1, 1, 1.99999999999999 43 | ), (new Geometry())->intersect( 44 | new Vector(1, 1, 1), 45 | new Vector(1, 1, 10), 46 | new Vector(1, 1, 2), 47 | new Vector(0, 0, 1) 48 | )); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Geometry.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stlslice; 12 | 13 | /** 14 | * Class Geometry. Provides logic for intersecting a line with a plane. 15 | * 16 | * NOTE: Requires: bcscale(16); 17 | * 18 | */ 19 | class Geometry 20 | { 21 | const EPSILON = 1e-6; 22 | 23 | /** 24 | * Returns the vector coordinates of the point where a line intersects a plane. 25 | * 26 | * Based on: 27 | * http://stackoverflow.com/questions/5666222/3d-line-plane-intersection 28 | * https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm 29 | * http://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection 30 | * 31 | * @param Vector $lineStart 32 | * @param Vector $lineEnd 33 | * @param Vector $planePointCoordinate 34 | * @param Vector $planeNormalCoordinate 35 | * @return Vector 36 | * @throws \Exception If the line is parallel to the plane. 37 | */ 38 | public function intersect( 39 | Vector $lineStart, 40 | Vector $lineEnd, 41 | Vector $planePointCoordinate, 42 | Vector $planeNormalCoordinate 43 | ) : Vector 44 | { 45 | $uParameter = $lineEnd->sub($lineStart); 46 | $dotProduct = $planeNormalCoordinate->dot($uParameter); 47 | 48 | if (abs($dotProduct) > self::EPSILON) { 49 | $w = $lineStart->sub($planePointCoordinate); 50 | $factor = bcmul(-1, bcdiv($planeNormalCoordinate->dot($w), $dotProduct)); 51 | $uParameter = $uParameter->multiplyScalar($factor); 52 | return $uParameter->add($lineStart); 53 | } else { 54 | throw new \Exception("Line is parallel to plane."); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /tests/VectorTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stl; 12 | 13 | use php3d\stlslice\Vector; 14 | 15 | /** 16 | * @covers Vector 17 | */ 18 | class VectorTest extends \PHPUnit_Framework_TestCase 19 | { 20 | /** 21 | * @var Vector 22 | */ 23 | private $vectorA; 24 | /** 25 | * @var Vector 26 | */ 27 | private $vectorB; 28 | 29 | public function setUp() 30 | { 31 | $this->vectorA = new Vector( 32 | 1, 33 | 2, 34 | 3 35 | ); 36 | $this->vectorB = new Vector( 37 | 4, 38 | 5, 39 | 6 40 | ); 41 | } 42 | 43 | public function testAddVectors() 44 | { 45 | $this->assertEquals( 46 | new Vector( 47 | 5, 48 | 7, 49 | 9 50 | ), $this->vectorA->add($this->vectorB) 51 | ); 52 | } 53 | 54 | public function testSubVectors() 55 | { 56 | $this->assertEquals( 57 | new Vector( 58 | -3, 59 | -3, 60 | -3 61 | ), $this->vectorA->sub($this->vectorB) 62 | ); 63 | } 64 | 65 | public function testDotVectors() 66 | { 67 | $this->assertEquals( 68 | 32.0, $this->vectorA->dot($this->vectorB) 69 | ); 70 | } 71 | 72 | public function testMultiplyScalar() 73 | { 74 | $this->assertEquals( 75 | new Vector( 76 | 2, 77 | 4, 78 | 6 79 | ), $this->vectorA->multiplyScalar(2) 80 | ); 81 | } 82 | 83 | public function testToArray() 84 | { 85 | $this->assertEquals(array( 86 | 1, 87 | 2, 88 | 3 89 | ), $this->vectorA->toArray()); 90 | } 91 | } -------------------------------------------------------------------------------- /src/Examples/STL2Svg.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stlslice\Examples; 12 | 13 | class STL2Svg { 14 | /** 15 | * @var array 16 | */ 17 | private $coordinates; 18 | 19 | /** 20 | * @return array 21 | */ 22 | public function getCoordinates(): array 23 | { 24 | return $this->coordinates; 25 | } 26 | 27 | /** 28 | * @param array $coordinates 29 | * @return STL2Svg 30 | */ 31 | public function setCoordinates(array $coordinates): STL2Svg 32 | { 33 | $this->coordinates = $coordinates; 34 | return $this; 35 | } 36 | 37 | public function __construct(array $coordinates) 38 | { 39 | $this->setCoordinates($coordinates); 40 | } 41 | 42 | public function toSvgString() : string 43 | { 44 | $coordinates = $this->getCoordinates(); 45 | $height = $coordinates[0]["height"]; 46 | $width = $coordinates[0]["height"]; 47 | $svg = " 48 | 49 | \n"; 50 | 51 | foreach ($this->getCoordinates() as $coordinate) { 52 | $name = $coordinate["name"]; 53 | foreach ($coordinate["points"] as $layer => $points) { 54 | $coordinates = array(); 55 | foreach ($points as $point) { 56 | $coordinates[] = $point[0] . "," . $point[1]; 57 | } 58 | $svg .= "\n"; 59 | $svg .= ""; 61 | $svg .= "\n"; 62 | } 63 | } 64 | 65 | $svg .= "\n"; 66 | 67 | return $svg; 68 | } 69 | } -------------------------------------------------------------------------------- /src/Examples/STL2GCode.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stlslice\Examples; 12 | 13 | /** 14 | * Class STL2GCode. Milling. This is an example file, change to suit your needs. 15 | * @package php3d\stlslice\Examples 16 | */ 17 | class STL2GCode 18 | { 19 | /** 20 | * @var int 21 | */ 22 | private $feedRate; 23 | 24 | /** 25 | * @return int 26 | */ 27 | public function getFeedRate(): int 28 | { 29 | return $this->feedRate; 30 | } 31 | 32 | /** 33 | * @param int $feedRate 34 | * @return STL2GCode 35 | */ 36 | public function setFeedRate(int $feedRate): STL2GCode 37 | { 38 | $this->feedRate = $feedRate; 39 | return $this; 40 | } 41 | 42 | /** 43 | * @var array 44 | */ 45 | private $coordinates; 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getCoordinates(): array 51 | { 52 | return $this->coordinates; 53 | } 54 | 55 | /** 56 | * @param array $coordinates 57 | * @return STL2GCode 58 | */ 59 | public function setCoordinates(array $coordinates): STL2GCode 60 | { 61 | $this->coordinates = $coordinates; 62 | return $this; 63 | } 64 | 65 | /** 66 | * STL2GCode constructor. 67 | * @param array $coordinates 68 | * @param int $feedRate 69 | */ 70 | public function __construct(array $coordinates, int $feedRate) 71 | { 72 | $this->setCoordinates($coordinates); 73 | $this->setFeedRate($feedRate); 74 | } 75 | 76 | /** 77 | * @return string 78 | */ 79 | public function toGCodeString() : string 80 | { 81 | $gcode = "F " . $this->getFeedRate() . "\n"; 82 | 83 | foreach ($this->getCoordinates() as $coordinate) { 84 | foreach ($coordinate["points"] as $layer => $points) { 85 | $gcode .= $this->getPolygonCoordinates( 86 | $points, 87 | round($coordinate["width"]), 88 | round($coordinate["height"]) 89 | ); 90 | } 91 | } 92 | 93 | return $gcode; 94 | } 95 | 96 | public function getPolygonCoordinates(array $points, int $width, int $height) 97 | { 98 | $image = imagecreatetruecolor($width, $height); 99 | $polygonColor = imagecolorallocate($image, 0, 0, 255); 100 | $imagePoints = array(); 101 | $z = 0; 102 | $highestZ = 0; 103 | foreach ($points as $point) { 104 | $imagePoints[] = $point[0]; 105 | $imagePoints[] = $point[1]; 106 | $z = $point[2]; 107 | if ($z > $highestZ) $highestZ = $z; 108 | } 109 | 110 | imagefilledpolygon($image, $imagePoints, count($imagePoints) / 2, $polygonColor); 111 | $result = "G1 Z" . $highestZ . "\n"; 112 | for ($k = 0; $k < $height - 1; $k++) { 113 | for ($l = 0; $l < $width - 1; $l++) { 114 | $coordinates[$k][$l] = imagecolorat($image, $l, $k); 115 | if ($coordinates[$k][$l] != 0) { 116 | $result .= "G1 X" . $k . " Y" . $l . " Z" . $z . "\n"; 117 | } 118 | } 119 | } 120 | $result .= "G1 Z" . $highestZ . "\n"; 121 | 122 | imagedestroy($image); 123 | return $result; 124 | } 125 | } -------------------------------------------------------------------------------- /tests/STLSliceTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stl; 12 | 13 | use php3d\stlslice\STLSlice; 14 | 15 | /** 16 | * @covers STLSlice 17 | */ 18 | class STLSliceTest extends \PHPUnit_Framework_TestCase 19 | { 20 | private $stlFileString = << 47.70205, 49 | "width" => 72.107936489759865, 50 | "name" => "test0", 51 | "points" => array( 52 | "2" => array( 53 | array( 54 | 0, 55 | 46.810805299015, 56 | 4.8637817 57 | ), 58 | array( 59 | 0.017498676113121, 60 | 47.2273, 61 | 4.8637817 62 | ), 63 | array( 64 | 0.017498676113107, 65 | 47.2273, 66 | 4.8637817, 67 | ), 68 | array( 69 | 0.027872433465518, 70 | 47.30950276229, 71 | 4.8637817 72 | ), 73 | array( 74 | 0.027872433465518, 75 | 47.30950276229, 76 | 4.8637817 77 | ), 78 | array( 79 | 0.077408208182959, 80 | 47.70205, 81 | 4.8637817 82 | ) 83 | ), 84 | "1" => array( 85 | array( 86 | 0.15318166499452, 87 | 46.995123779284, 88 | 4.7637817 89 | ), 90 | array( 91 | 0.16293635449684, 92 | 47.2273, 93 | 4.7637817 94 | ), 95 | array( 96 | 0.19838791032872, 97 | 47.50822191844, 98 | 4.7637817 99 | ), 100 | array( 101 | 0.19838791032872, 102 | 47.50822191844, 103 | 4.7637817 104 | ), 105 | array( 106 | 0.22284719384851, 107 | 47.70205, 108 | 4.7637817 109 | ) 110 | ) 111 | ) 112 | ) 113 | ); 114 | 115 | 116 | function testSlice() 117 | { 118 | $stl = STL::fromString($this->stlFileString); 119 | $this->assertEquals( 120 | $this->sliceArray, 121 | (new STLSlice($stl, 10))->slice() 122 | ); 123 | } 124 | } -------------------------------------------------------------------------------- /src/Vector.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stlslice; 12 | 13 | /** 14 | * Class Vector. Provides vector math. 15 | */ 16 | class Vector 17 | { 18 | /** 19 | * @var float 20 | */ 21 | private $x; 22 | 23 | /** 24 | * @return float 25 | */ 26 | public function getX(): float 27 | { 28 | return $this->x; 29 | } 30 | 31 | /** 32 | * @param float $x 33 | * @return Vector 34 | */ 35 | private function setX(float $x): Vector 36 | { 37 | $this->x = $x; 38 | return $this; 39 | } 40 | 41 | /** 42 | * @return float 43 | */ 44 | public function getY(): float 45 | { 46 | return $this->y; 47 | } 48 | 49 | /** 50 | * @param float $y 51 | * @return Vector 52 | */ 53 | private function setY(float $y): Vector 54 | { 55 | $this->y = $y; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return float 61 | */ 62 | public function getZ(): float 63 | { 64 | return $this->z; 65 | } 66 | 67 | /** 68 | * @param float $z 69 | * @return Vector 70 | */ 71 | private function setZ(float $z): Vector 72 | { 73 | $this->z = $z; 74 | return $this; 75 | } 76 | 77 | /** 78 | * @var float 79 | */ 80 | private $y; 81 | 82 | /** 83 | * @var float 84 | */ 85 | private $z; 86 | 87 | public function __construct(float $x, float $y, float $z) 88 | { 89 | $this->setX($x); 90 | $this->setY($y); 91 | $this->setZ($z); 92 | } 93 | 94 | /** 95 | * From array constructor. 96 | * 97 | * @param array $vectorArray 98 | * @return Vector 99 | */ 100 | public static function fromArray(array $vectorArray) : Vector 101 | { 102 | return new self( 103 | $vectorArray[0], 104 | $vectorArray[1], 105 | $vectorArray[2] 106 | ); 107 | } 108 | 109 | /** 110 | * Adds this vector to another and returns the resulting vector. 111 | * 112 | * @param Vector $vector 113 | * @return Vector 114 | */ 115 | public function add(Vector $vector) : Vector 116 | { 117 | return new Vector( 118 | (float)bcadd($this->getX(), $vector->getX()), 119 | (float)bcadd($this->getY(), $vector->getY()), 120 | (float)bcadd($this->getZ(), $vector->getZ()) 121 | ); 122 | } 123 | 124 | /** 125 | * Substracts a vector from this vector and returns the resulting vector. 126 | * 127 | * @param Vector $vector 128 | * @return Vector 129 | */ 130 | public function sub(Vector $vector) : Vector 131 | { 132 | return new Vector( 133 | (float)bcsub($this->getX(), $vector->getX()), 134 | (float)bcsub($this->getY(), $vector->getY()), 135 | (float)bcsub($this->getZ(), $vector->getZ()) 136 | ); 137 | } 138 | 139 | /** 140 | * Dot value of two vectors and returns the resulting vector. 141 | * 142 | * @param Vector $vector 143 | * @return float 144 | */ 145 | public function dot(Vector $vector) : float 146 | { 147 | return (float) bcadd( 148 | bcadd( 149 | bcmul($this->getX(), $vector->getX()), 150 | bcmul($this->getY(), $vector->getY()) 151 | ), 152 | bcmul($this->getZ(), $vector->getZ()) 153 | ); 154 | } 155 | 156 | /** 157 | * Multiplies this vector to a scalar and returns the resulting vector. 158 | * 159 | * @param float $scalar 160 | * @return Vector 161 | */ 162 | public function multiplyScalar(float $scalar) : Vector 163 | { 164 | return new Vector( 165 | (float)bcmul($this->getX(), $scalar), 166 | (float)bcmul($this->getY(), $scalar), 167 | (float)bcmul($this->getZ(), $scalar) 168 | ); 169 | } 170 | 171 | /** 172 | * @return array 173 | */ 174 | public function toArray() : array 175 | { 176 | return array( 177 | $this->getX(), 178 | $this->getY(), 179 | $this->getZ() 180 | ); 181 | } 182 | } -------------------------------------------------------------------------------- /tests/STLMillingEditTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stl; 12 | 13 | use php3d\stlslice\Examples\STLMillingEdit; 14 | 15 | class STLMillingEditTest extends \PHPUnit_Framework_TestCase 16 | { 17 | private $testStlArray = array( 18 | "name" => "TEST2", 19 | "facets" => array( 20 | array( 21 | "coordinates" => array( 22 | 0, 23 | 1, 24 | 2 25 | ), 26 | "vertex" => array( 27 | array( 28 | 3, 4, 5 29 | ), 30 | array( 31 | 5, 4, 6 32 | ), 33 | array( 34 | 7, 8, 9 35 | ) 36 | ) 37 | ), 38 | array( 39 | "coordinates" => array( 40 | 10, 41 | 11, 42 | 12 43 | ), 44 | "vertex" => array( 45 | array( 46 | 12, 14, 15 47 | ), 48 | array( 49 | 16, 17, 18 50 | ), 51 | array( 52 | 19, 20, 21 53 | ) 54 | ) 55 | ) 56 | ) 57 | ); 58 | 59 | public function tearDown() 60 | { 61 | \Mockery::close(); 62 | } 63 | 64 | public function testLowestVertexX() { 65 | $stlMillingEditor = new STLMillingEdit($this->testStlArray); 66 | 67 | $this->assertEquals( 68 | 3, 69 | $stlMillingEditor->getLowestVertexX() 70 | ); 71 | } 72 | 73 | public function testHighestVertexX() { 74 | $stlMillingEditor = new STLMillingEdit($this->testStlArray); 75 | 76 | $this->assertEquals( 77 | 19, 78 | $stlMillingEditor->getHighestVertexX() 79 | ); 80 | } 81 | 82 | public function testRemoveLowestXVertices() { 83 | $stlMillingEditor = new STLMillingEdit($this->testStlArray); 84 | 85 | $this->assertEquals( 86 | array( 87 | "name" => "TEST2", 88 | "facets" => array( 89 | array( 90 | "coordinates" => array( 91 | 10, 92 | 11, 93 | 12 94 | ), 95 | "vertex" => array( 96 | array( 97 | 12, 14, 15 98 | ), 99 | array( 100 | 16, 17, 18 101 | ), 102 | array( 103 | 19, 20, 21 104 | ) 105 | ) 106 | ) 107 | ) 108 | ), 109 | $stlMillingEditor->removeLowestXVertices()->getStlFileContentArray() 110 | ); 111 | } 112 | 113 | public function testRemoveHighestXVertices() { 114 | $stlMillingEditor = new STLMillingEdit($this->testStlArray); 115 | 116 | $this->assertEquals( 117 | array( 118 | "name" => "TEST2", 119 | "facets" => array( 120 | array( 121 | "coordinates" => array( 122 | 0, 123 | 1, 124 | 2 125 | ), 126 | "vertex" => array( 127 | array( 128 | 3, 4, 5 129 | ), 130 | array( 131 | 5, 4, 6 132 | ), 133 | array( 134 | 7, 8, 9 135 | ) 136 | ) 137 | ) 138 | ) 139 | ), 140 | $stlMillingEditor->removeHighestXVertices()->getStlFileContentArray() 141 | ); 142 | } 143 | 144 | public function testExtractMillingContent() { 145 | $stlMillingEditor = new STLMillingEdit($this->testStlArray); 146 | 147 | $this->assertEquals( 148 | array( 149 | "name" => "TEST2", 150 | "facets" => array() 151 | ), 152 | $stlMillingEditor->extractMillingContent()->getStlFileContentArray() 153 | ); 154 | } 155 | } -------------------------------------------------------------------------------- /src/STLSlice.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace php3d\stlslice; 12 | 13 | use php3d\stl\STL; 14 | use php3d\stl\STLSplit; 15 | 16 | /** 17 | * Class Geometry. Provides logic for intersecting a line with a plane. 18 | * 19 | * NOTE: Requires: bcscale(16); 20 | * 21 | */ 22 | class STLSlice 23 | { 24 | /** 25 | * @var int 26 | */ 27 | private $precision; 28 | 29 | /** 30 | * @var STL 31 | */ 32 | private $stl; 33 | 34 | /** 35 | * @return STL 36 | */ 37 | public function getStl(): STL 38 | { 39 | return $this->stl; 40 | } 41 | 42 | /** 43 | * @param STL $stl 44 | * @return STLSlice 45 | */ 46 | public function setStl(STL $stl): STLSlice 47 | { 48 | $this->stl = $stl; 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return int 54 | */ 55 | public function getPrecision(): int 56 | { 57 | return $this->precision; 58 | } 59 | 60 | /** 61 | * @param int $precision 62 | * @return STLSlice 63 | */ 64 | public function setPrecision(int $precision): STLSlice 65 | { 66 | $this->precision = $precision; 67 | return $this; 68 | } 69 | 70 | /** 71 | * @param STL $stl 72 | * @param float $precision 73 | */ 74 | public function __construct(STL $stl, float $precision) 75 | { 76 | $this->setStl($stl)->setPrecision($precision); 77 | } 78 | 79 | /** 80 | * Returns an array of layers and coordinates for points for each layer for the 3D object. 81 | * 82 | * @return array 83 | */ 84 | public function slice() : array 85 | { 86 | $objects = (new STLSplit($this->getStl()))->split(); 87 | $layersNames = []; 88 | 89 | $absoluteHighestZ = 0; 90 | $zeds = array(); 91 | foreach ($objects as $key => $object) { 92 | $highestZ = null; 93 | $lowestZ = null; 94 | if ($highestZ > $absoluteHighestZ) $absoluteHighestZ = $highestZ; 95 | $lines = $this->extractLinesFromStl($object, $highestZ, $lowestZ); 96 | 97 | $planeNormalCoordinate = new Vector(0, 0, 1); 98 | $layers = array(); 99 | $layer = 0; 100 | for ($i = $lowestZ; $i < $highestZ * $this->getPrecision(); $i++) { 101 | $planePointCoordinate = new Vector(0, 0, $i / $this->getPrecision()); 102 | $layers[$layer] = array(); // Stores dot vectors. 103 | $intersectingLines = $this->findAllLinesIntersectingWithPlane($lines, $planePointCoordinate, 104 | $layers[$layer]); 105 | foreach ($intersectingLines as $intersectingLine) { 106 | $point = (new Geometry())->intersect( 107 | $intersectingLine[0], 108 | $intersectingLine[1], 109 | $planePointCoordinate, 110 | $planeNormalCoordinate 111 | ); 112 | $layers[$layer][] = $point; 113 | 114 | } 115 | if (count($layers[$layer])) { 116 | $layer++; 117 | } 118 | } 119 | 120 | $objectsLayers[] = $layers; 121 | $layersNames[] = $object->getSolidName(); 122 | $zeds[$key] = $highestZ; 123 | } 124 | 125 | $lowestX = 0; 126 | $highestX = 0; 127 | $lowestY = 0; 128 | $highestY = 0; 129 | for ($k = 0; $k < count($objectsLayers); $k++) { 130 | for ($i = 0; $i < count($objectsLayers[$k]); $i++) { 131 | foreach ($objectsLayers[$k][$i] as $dot) { 132 | if ($dot->getX() < $lowestX) { 133 | $lowestX = $dot->getX(); 134 | } 135 | if ($dot->getY() < $lowestY) { 136 | $lowestY = $dot->getY(); 137 | } 138 | if ($dot->getX() > $highestX) { 139 | $highestX = $dot->getX(); 140 | } 141 | if ($dot->getY() > $highestY) { 142 | $highestY = $dot->getY(); 143 | } 144 | } 145 | } 146 | } 147 | 148 | $addX = 0; 149 | $addY = 0; 150 | if ($lowestX < 0) $addX = -1 * $lowestX; 151 | if ($lowestY < 0) $addY = -1 * $lowestY; 152 | 153 | $objectsLayersArray = []; 154 | foreach ($objectsLayers as $key => $layers) { 155 | $objectsLayersArray[] = $this->createObjectLayers( 156 | $layers, 157 | $addX, 158 | $addY, 159 | $lowestX, 160 | $lowestY, 161 | $highestX, 162 | $highestY, 163 | $layersNames[$key] 164 | ); 165 | } 166 | 167 | return $objectsLayersArray; 168 | } 169 | 170 | /** 171 | * Creates SVG polygon. 172 | * 173 | * @param array $layers 174 | * @param float $addX 175 | * @param float $addY 176 | * @param float $lowestX 177 | * @param float $lowestY 178 | * @param float $highestX 179 | * @param float $highestY 180 | * @return array 181 | */ 182 | private function createObjectLayers(array $layers, float $addX, float $addY, float $lowestX, float $lowestY, float $highestX, float $highestY, string $name) : array 183 | { 184 | $result = []; 185 | 186 | for ($i = count($layers) - 1; $i > 0; $i--) { 187 | $layers[$i] = $this->sortPolygonCoordinates($layers[$i]); 188 | $result[$i] = array(); 189 | foreach ($layers[$i] as $dot) { 190 | $_dot = array($addX + $dot["x"], $addY + $dot["y"], $dot["z"]); 191 | if (!in_array($_dot, $result[$i])) { 192 | $result[$i][] = $_dot; 193 | } 194 | } 195 | } 196 | 197 | $width = (($lowestX < 0) ? -1 * $lowestX : $lowestX) + (($highestX < 0) ? -1 * $highestX : $highestX); 198 | $height = (($lowestY < 0) ? -1 * $lowestY : $lowestY) + (($highestY < 0) ? -1 * $highestY : $highestY); 199 | 200 | return array( 201 | "height" => $height, 202 | "width" => $width, 203 | "name" => $name, 204 | "points" => $result 205 | ); 206 | } 207 | 208 | // http://stackoverflow.com/questions/29610770/draw-a-polygon-between-coordinates-preventing-intersects 209 | private function findCenter($coordinates) : array 210 | { 211 | $x = 0; $y = 0; 212 | foreach ($coordinates as $coordinate) { 213 | $x += $coordinate->getX(); 214 | $y += $coordinate->getY(); 215 | } 216 | 217 | return array( 218 | "x" => $x / count($coordinates), 219 | "y" => $y / count($coordinates) 220 | ); 221 | } 222 | 223 | private function findAngles(array $centre, array $coordinates) : array 224 | { 225 | $angles = array(); 226 | 227 | foreach ($coordinates as $coordinate) { 228 | $angle = atan2( 229 | $coordinate->getX() - $centre["x"], 230 | $coordinate->getY() - $centre["y"] 231 | ); 232 | 233 | $angles[] = array( 234 | "x" => $coordinate->getX(), 235 | "y" => $coordinate->getY(), 236 | "z" => $coordinate->getZ(), 237 | "angle" => $angle 238 | ); 239 | } 240 | 241 | return $angles; 242 | } 243 | 244 | private function sortPolygonCoordinates(array $coordinates) { 245 | $angles = $this->findAngles($this->findCenter($coordinates), $coordinates); 246 | 247 | usort($angles, function($a, $b) { 248 | if ($a["angle"] > $b["angle"]) return 1; 249 | else if ($a["angle"] < $b["angle"]) return -1; 250 | return 0; 251 | }); 252 | 253 | return $angles; 254 | } 255 | 256 | /** 257 | * Find all the lines that WILL intersect with a plane. 258 | * 259 | * @param array $lines 260 | * @param Vector $planePointCoordinate 261 | * @param $layer array 262 | * @return array 263 | */ 264 | private function findAllLinesIntersectingWithPlane(array $lines, Vector $planePointCoordinate, array &$layer) : array 265 | { 266 | $z = $planePointCoordinate->getZ(); 267 | $intersectingLines = array(); 268 | foreach ($lines as $line) { 269 | if (($line[0]->getZ() < $z && $line[1]->getZ() > $z) 270 | || ($line[1]->getZ() < $z && $line[0]->getZ() > $z)){ 271 | $intersectingLines[] = $line; 272 | } 273 | if ($line[0]->getZ() == $z) { 274 | $layer[] = $line[0]; 275 | } 276 | if ($line[1]->getZ() == $z) { 277 | $layer[] = $line[1]; 278 | } 279 | } 280 | 281 | return $intersectingLines; 282 | } 283 | 284 | /** 285 | * Converts all STL facets to an array of lines with start and end vector coordinates. 286 | * 287 | * Sets the highest and lowest Z coordinates of all lines to figure out the highest and lowest layers. 288 | * 289 | * @param STL $stl 290 | * @param float $highestZ 291 | * @param float $lowestZ 292 | * @return array 293 | */ 294 | private function extractLinesFromStl( 295 | STL $stl, 296 | &$highestZ, 297 | &$lowestZ 298 | ) : array 299 | { 300 | $stlArray = $stl->toArray(); 301 | $lines = array(); 302 | 303 | foreach ($stlArray["facets"] as $facet) { 304 | $startVector = Vector::fromArray($facet["vertex"][0]); 305 | $endVector = Vector::fromArray($facet["vertex"][1]); 306 | $lines[] = array( 307 | $startVector, 308 | $endVector 309 | ); 310 | if (is_null($highestZ)) { 311 | $highestZ = $startVector->getZ(); 312 | } 313 | if (is_null($lowestZ)) { 314 | $lowestZ = $startVector->getZ(); 315 | } 316 | if ($startVector->getZ() > $highestZ) { 317 | $highestZ = $startVector->getZ(); 318 | } 319 | if ($endVector->getZ() > $highestZ) { 320 | $highestZ = $endVector->getZ(); 321 | } 322 | if ($startVector->getZ() < $lowestZ) { 323 | $lowestZ = $startVector->getZ(); 324 | } 325 | if ($endVector->getZ() < $lowestZ) { 326 | $lowestZ = $endVector->getZ(); 327 | } 328 | 329 | $startVector = Vector::fromArray($facet["vertex"][1]); 330 | $endVector = Vector::fromArray($facet["vertex"][2]); 331 | $lines[] = array( 332 | $startVector, 333 | $endVector 334 | ); 335 | if ($startVector->getZ() > $highestZ) { 336 | $highestZ = $startVector->getZ(); 337 | } 338 | if ($endVector->getZ() > $highestZ) { 339 | $highestZ = $endVector->getZ(); 340 | } 341 | if ($startVector->getZ() < $lowestZ) { 342 | $lowestZ = $startVector->getZ(); 343 | } 344 | if ($endVector->getZ() < $lowestZ) { 345 | $lowestZ = $endVector->getZ(); 346 | } 347 | 348 | $startVector = Vector::fromArray($facet["vertex"][2]); 349 | $endVector = Vector::fromArray($facet["vertex"][0]); 350 | $lines[] = array( 351 | $startVector, 352 | $endVector 353 | ); 354 | if ($startVector->getZ() > $highestZ) { 355 | $highestZ = $startVector->getZ(); 356 | } 357 | if ($endVector->getZ() > $highestZ) { 358 | $highestZ = $endVector->getZ(); 359 | } 360 | if ($startVector->getZ() < $lowestZ) { 361 | $lowestZ = $startVector->getZ(); 362 | } 363 | if ($endVector->getZ() < $lowestZ) { 364 | $lowestZ = $endVector->getZ(); 365 | } 366 | } 367 | 368 | return $lines; 369 | } 370 | } -------------------------------------------------------------------------------- /src/Examples/STLMillingEdit.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | 12 | namespace php3d\stlslice\Examples; 13 | 14 | /** 15 | * Class STLMillingEdit. Used for 'chopping' off parts of an STL file: 16 | * 17 | * It will extract edges from a 3D object and keep shapes to be carved. 18 | * 19 | * Useful if you pass in a 'wood plank' and want to get GCode for carving out shapes. 20 | * 21 | * NOTE: Only to be used for milling! This is highly experimental - validate resulting GCode if used 22 | * with the GCode generator example class. 23 | * 24 | * NOTE: The resulting STL content can not be viewed using a regular STL file viewer, since it does not 25 | * contain actual STL objects - but rather facets to carve out. 26 | * 27 | * TODO: Add link to viewer. 28 | * 29 | * @class STLMillingEdit 30 | */ 31 | class STLMillingEdit 32 | { 33 | private $stlFileContentArray; 34 | 35 | /** 36 | * @return mixed 37 | */ 38 | public function getStlFileContentArray() : array 39 | { 40 | return $this->stlFileContentArray; 41 | } 42 | 43 | /** 44 | * @param array $stlFileContentArray 45 | * @return STLMillingEdit 46 | */ 47 | public function setStlFileContentArray(array $stlFileContentArray) 48 | { 49 | $this->stlFileContentArray = $stlFileContentArray; 50 | return $this; 51 | } 52 | 53 | public function __construct(array $stlFileContentArray) 54 | { 55 | $this->setStlFileContentArray($stlFileContentArray); 56 | } 57 | 58 | /** 59 | * Lowest possible X coordinate of a vertex. 60 | * 61 | * @return float 62 | */ 63 | public function getLowestVertexX() : float 64 | { 65 | $min = null; 66 | foreach ($this->getStlFileContentArray()["facets"] as $facetNormal) { 67 | for ($i = 0; $i < 3; $i++) { 68 | if (is_null($min)) { 69 | $min = $facetNormal["vertex"][$i][0]; 70 | continue; 71 | } 72 | if ($min > $facetNormal["vertex"][$i][0]) { 73 | $min = $facetNormal["vertex"][$i][0]; 74 | } 75 | } 76 | } 77 | 78 | return $min; 79 | } 80 | 81 | /** 82 | * Highest possible X coordinate of a vertex. 83 | * 84 | * @return float 85 | */ 86 | public function getHighestVertexX() : float 87 | { 88 | $max = null; 89 | 90 | foreach ($this->getStlFileContentArray()["facets"] as $facetNormal) { 91 | for ($i = 0; $i < 3; $i++) { 92 | if (is_null($max)) { 93 | $max = $facetNormal["vertex"][$i][0]; 94 | continue; 95 | } 96 | if ($max < $facetNormal["vertex"][$i][0]) { 97 | $max = $facetNormal["vertex"][$i][0]; 98 | } 99 | } 100 | } 101 | 102 | return $max; 103 | } 104 | 105 | /** 106 | * Remove all vertices with the lowest X values. 107 | * 108 | * @return STLMillingEdit 109 | */ 110 | public function removeLowestXVertices() : STLMillingEdit 111 | { 112 | $facetNormals = []; 113 | $contentArray = $this->getStlFileContentArray(); 114 | $lowestVertexX = $this->getLowestVertexX(); 115 | 116 | foreach ($contentArray["facets"] as $facetNormal) { 117 | $skip = false; 118 | for ($i = 0; $i < 3; $i++) { 119 | if ($facetNormal["vertex"][$i][0] == $lowestVertexX) { 120 | $skip = true; 121 | } 122 | } 123 | if (!$skip) { 124 | $facetNormals[] = $facetNormal; 125 | } 126 | } 127 | 128 | $contentArray["facets"] = $facetNormals; 129 | 130 | $this->setStlFileContentArray($contentArray); 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * Remove all vertices with the highest X values. 137 | * 138 | * @return STLMillingEdit 139 | */ 140 | public function removeHighestXVertices() : STLMillingEdit 141 | { 142 | $facetNormals = []; 143 | $contentArray = $this->getStlFileContentArray(); 144 | $highestVertexX = $this->getHighestVertexX(); 145 | 146 | foreach ($contentArray["facets"] as $facetNormal) { 147 | $skip = false; 148 | for ($i = 0; $i < 3; $i++) { 149 | if ($facetNormal["vertex"][$i][0] == $highestVertexX) { 150 | $skip = true; 151 | } 152 | } 153 | if (!$skip) { 154 | $facetNormals[] = $facetNormal; 155 | } 156 | } 157 | 158 | $contentArray["facets"] = $facetNormals; 159 | 160 | $this->setStlFileContentArray($contentArray); 161 | 162 | return $this; 163 | } 164 | 165 | /** 166 | * Highest possible Y coordinate of a vertex. 167 | * 168 | * @return float 169 | */ 170 | public function getHighestVertexY() : float 171 | { 172 | $max = null; 173 | foreach ($this->getStlFileContentArray()["facets"] as $facetNormal) { 174 | for ($i = 0; $i < 3; $i++) { 175 | if (is_null($max)) { 176 | $max = $facetNormal["vertex"][$i][1]; 177 | continue; 178 | } 179 | if ($max < $facetNormal["vertex"][$i][1]) { 180 | $max = $facetNormal["vertex"][$i][1]; 181 | } 182 | } 183 | } 184 | 185 | return $max; 186 | } 187 | 188 | /** 189 | * Remove all vertices with the highest Y values. 190 | * 191 | * @return STLMillingEdit 192 | */ 193 | public function removeHighestYVertices() : STLMillingEdit 194 | { 195 | $facetNormals = []; 196 | $contentArray = $this->getStlFileContentArray(); 197 | $highestVertexY = $this->getHighestVertexY(); 198 | 199 | foreach ($contentArray["facets"] as $facetNormal) { 200 | $skip = false; 201 | if ($facetNormal["vertex"][0][1] == $highestVertexY && 202 | $facetNormal["vertex"][1][1] == $highestVertexY && 203 | $facetNormal["vertex"][2][1] == $highestVertexY 204 | ) { 205 | $skip = true; 206 | } 207 | if (!$skip) { 208 | $facetNormals[] = $facetNormal; 209 | } 210 | } 211 | 212 | $contentArray["facets"] = $facetNormals; 213 | 214 | $this->setStlFileContentArray($contentArray); 215 | 216 | return $this; 217 | } 218 | 219 | 220 | /** 221 | * Remove all vertices with the lowest Y values. 222 | * 223 | * @return STLMillingEdit 224 | */ 225 | public function removeLowestYVertices() : STLMillingEdit 226 | { 227 | $facetNormals = []; 228 | $contentArray = $this->getStlFileContentArray(); 229 | $lowestVertexY = $this->getLowestVertexY(); 230 | 231 | foreach ($contentArray["facets"] as $facetNormal) { 232 | $skip = false; 233 | if ($facetNormal["vertex"][0][1] == $lowestVertexY && 234 | $facetNormal["vertex"][1][1] == $lowestVertexY && 235 | $facetNormal["vertex"][2][1] == $lowestVertexY 236 | ) { 237 | $skip = true; 238 | } 239 | if (!$skip) { 240 | $facetNormals[] = $facetNormal; 241 | } 242 | } 243 | 244 | $contentArray["facets"] = $facetNormals; 245 | 246 | $this->setStlFileContentArray($contentArray); 247 | 248 | return $this; 249 | } 250 | 251 | /** 252 | * Lowest possible Y coordinate of a vertex. 253 | * 254 | * @return float 255 | */ 256 | public function getLowestVertexY() : float 257 | { 258 | $min = null; 259 | foreach ($this->getStlFileContentArray()["facets"] as $facetNormal) { 260 | for ($i = 0; $i < 3; $i++) { 261 | if (is_null($min)) { 262 | $min = $facetNormal["vertex"][$i][1]; 263 | continue; 264 | } 265 | if ($min > $facetNormal["vertex"][$i][1]) { 266 | $min = $facetNormal["vertex"][$i][1]; 267 | } 268 | } 269 | } 270 | 271 | return $min; 272 | } 273 | 274 | 275 | /** 276 | * Highest possible Z coordinate of a vertex. 277 | * 278 | * @return float 279 | */ 280 | public function getHighestVertexZ() : float 281 | { 282 | $max = null; 283 | foreach ($this->getStlFileContentArray()["facets"] as $facetNormal) { 284 | for ($i = 0; $i < 3; $i++) { 285 | if (is_null($max)) { 286 | $max = $facetNormal["vertex"][$i][2]; 287 | continue; 288 | } 289 | if ($max < $facetNormal["vertex"][$i][2]) { 290 | $max = $facetNormal["vertex"][$i][2]; 291 | } 292 | } 293 | } 294 | 295 | return $max; 296 | } 297 | 298 | /** 299 | * Remove all vertices with the highest Z values. 300 | * 301 | * @return STLMillingEdit 302 | */ 303 | public function removeHighestZVertices() : STLMillingEdit 304 | { 305 | $facetNormals = []; 306 | $contentArray = $this->getStlFileContentArray(); 307 | $highestVertexZ = $this->getHighestVertexZ(); 308 | 309 | foreach ($contentArray["facets"] as $facetNormal) { 310 | $skip = false; 311 | if ($facetNormal["vertex"][0][2] == $highestVertexZ && 312 | $facetNormal["vertex"][1][2] == $highestVertexZ && 313 | $facetNormal["vertex"][2][2] == $highestVertexZ 314 | ) { 315 | $skip = true; 316 | } 317 | 318 | if (!$skip) { 319 | $facetNormals[] = $facetNormal; 320 | } 321 | } 322 | 323 | $contentArray["facets"] = $facetNormals; 324 | 325 | $this->setStlFileContentArray($contentArray); 326 | 327 | return $this; 328 | } 329 | 330 | 331 | /** 332 | * Remove all vertices with the lowest Z values. 333 | * 334 | * @return STLMillingEdit 335 | */ 336 | public function removeLowestZVertices() : STLMillingEdit 337 | { 338 | $facetNormals = []; 339 | $contentArray = $this->getStlFileContentArray(); 340 | $lowestVertexZ = $this->getLowestVertexZ(); 341 | 342 | foreach ($contentArray["facets"] as $facetNormal) { 343 | $skip = false; 344 | for ($i = 0; $i < 3; $i++) { 345 | if ($facetNormal["vertex"][0][2] == $lowestVertexZ && 346 | $facetNormal["vertex"][1][2] == $lowestVertexZ && 347 | $facetNormal["vertex"][2][2] == $lowestVertexZ 348 | ) { 349 | $skip = true; 350 | } 351 | } 352 | if (!$skip) { 353 | $facetNormals[] = $facetNormal; 354 | } 355 | } 356 | 357 | $contentArray["facets"] = $facetNormals; 358 | 359 | $this->setStlFileContentArray($contentArray); 360 | 361 | return $this; 362 | } 363 | 364 | /** 365 | * Lowest possible Z coordinate of a vertex. 366 | * 367 | * @return float 368 | */ 369 | public function getLowestVertexZ() : float 370 | { 371 | $min = null; 372 | foreach ($this->getStlFileContentArray()["facets"] as $facetNormal) { 373 | for ($i = 0; $i < 3; $i++) { 374 | if (is_null($min)) { 375 | $min = $facetNormal["vertex"][$i][2]; 376 | continue; 377 | } 378 | if ($min > $facetNormal["vertex"][$i][2]) { 379 | $min = $facetNormal["vertex"][$i][2]; 380 | } 381 | } 382 | } 383 | 384 | return $min; 385 | } 386 | 387 | 388 | /** 389 | * Extract the actual milling objects. 390 | * 391 | * @return STLMillingEdit 392 | */ 393 | public function extractMillingContent() : STLMillingEdit 394 | { 395 | try { 396 | $this->removeHighestXVertices() 397 | ->removeLowestXVertices() 398 | ->removeHighestZVertices() 399 | ->removeLowestZVertices(); 400 | } catch (\TypeError $ex) { 401 | // Silently ignore an object without remaining facets. This is normally detected by 402 | // not being able to find a lower / higher coordinate. 403 | } 404 | return $this; 405 | } 406 | } 407 | --------------------------------------------------------------------------------