├── .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 | [](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";
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 |
--------------------------------------------------------------------------------