mirror of https://github.com/vitalif/Slic3r
Compare commits
314 Commits
Author | SHA1 | Date |
---|---|---|
Vitaliy Filippov | 0753f16e9e | |
Vitaliy Filippov | 896bf15ea2 | |
Vitaliy Filippov | e73d76e771 | |
Vitaliy Filippov | 3d85730b4e | |
Vitaliy Filippov | f1b5b5c13b | |
Vitaliy Filippov | 7b938b0002 | |
Vitaliy Filippov | 54b80e1109 | |
Vitaliy Filippov | 87ac675d14 | |
Vitaliy Filippov | 207b55eec0 | |
Alessandro Ranellucci | 97231327e0 | |
Alessandro Ranellucci | 5e80d7a388 | |
Alessandro Ranellucci | 6194cbf530 | |
Alessandro Ranellucci | 8ee11b3239 | |
Alessandro Ranellucci | 9bff6ccde7 | |
Alessandro Ranellucci | 89ff1a50b2 | |
Alessandro Ranellucci | 34750d47fe | |
Alessandro Ranellucci | e158d1e612 | |
Alessandro Ranellucci | 7eaba2d132 | |
Alessandro Ranellucci | 5dcc1eab79 | |
Alessandro Ranellucci | 1674108bac | |
Alessandro Ranellucci | 82e2dd4e62 | |
Alessandro Ranellucci | 1f497c0f7c | |
Alessandro Ranellucci | 7ccfd2f024 | |
Alessandro Ranellucci | d2ca4c1b13 | |
Alessandro Ranellucci | 5c54acf9ac | |
Alessandro Ranellucci | 2d14ddb955 | |
Alessandro Ranellucci | c5c069b9a0 | |
Alessandro Ranellucci | 3f6360ee8f | |
Alessandro Ranellucci | ba8148f4ad | |
Alessandro Ranellucci | e3e69251cb | |
Alessandro Ranellucci | af1a47c461 | |
Alessandro Ranellucci | 3599bd0bae | |
Alessandro Ranellucci | d1511f4a00 | |
Alessandro Ranellucci | d56bb940e7 | |
Alessandro Ranellucci | 2f7443169b | |
Alessandro Ranellucci | d8e1437014 | |
Alessandro Ranellucci | 64853d5661 | |
Alessandro Ranellucci | 342513a4de | |
Alessandro Ranellucci | 28695c719c | |
Alessandro Ranellucci | 39b8ac80ee | |
Alessandro Ranellucci | defe0a4279 | |
Alessandro Ranellucci | ce1e736b6b | |
Alessandro Ranellucci | 98b8936ee2 | |
Alessandro Ranellucci | 2ac40f9547 | |
Alessandro Ranellucci | 6a6439576b | |
Alessandro Ranellucci | b02e459c4b | |
Alessandro Ranellucci | fb4a971339 | |
Alessandro Ranellucci | 5ca81d699e | |
Alessandro Ranellucci | 3d25b9030c | |
Alessandro Ranellucci | 8290a006ed | |
Alessandro Ranellucci | e62672f787 | |
Alessandro Ranellucci | 147385203c | |
Alessandro Ranellucci | 2bce8bb745 | |
Alessandro Ranellucci | abdf6531f1 | |
Alessandro Ranellucci | 7ea09a0071 | |
Alessandro Ranellucci | 0decbbf910 | |
Alessandro Ranellucci | 4e5f7d74ff | |
Alessandro Ranellucci | a62457d6b5 | |
Y. Sapir | 19d18bdd84 | |
Y. Sapir | 9247f21ff8 | |
Y. Sapir | d2e51242d9 | |
Y. Sapir | 07dd799967 | |
Y. Sapir | b8b8c746d9 | |
Y. Sapir | 85cbbed663 | |
Y. Sapir | 22b05cb187 | |
Y. Sapir | 76738dc66b | |
Y. Sapir | 0486a7f857 | |
Y. Sapir | db51e4693d | |
Y. Sapir | d06c300926 | |
Y. Sapir | 8da0bded1d | |
Alessandro Ranellucci | 88a2e5c791 | |
Alessandro Ranellucci | 3df2488eca | |
Alessandro Ranellucci | ed58f35fe5 | |
Alessandro Ranellucci | bf2af85da5 | |
Alessandro Ranellucci | fcdb462abe | |
Alessandro Ranellucci | 70ceb853f1 | |
Alessandro Ranellucci | a3bd1b5302 | |
Alessandro Ranellucci | c63bd8165d | |
Alessandro Ranellucci | f2c5e799b1 | |
Alessandro Ranellucci | 254ab29a97 | |
Alessandro Ranellucci | 874c7a6e8b | |
Alessandro Ranellucci | 5e6ff952df | |
Alessandro Ranellucci | 8ca352eb62 | |
Alessandro Ranellucci | 08279ec5d8 | |
Alessandro Ranellucci | a8b6e32767 | |
Alessandro Ranellucci | 85b0a4376a | |
Alessandro Ranellucci | 0ba685f556 | |
Alessandro Ranellucci | 63d56c666b | |
Alessandro Ranellucci | a00f6c72ed | |
Alessandro Ranellucci | 038076e040 | |
Alessandro Ranellucci | 3f29a9292a | |
Alessandro Ranellucci | 0ddcefe956 | |
Alessandro Ranellucci | 58ffaca2df | |
Alessandro Ranellucci | ac0a91a162 | |
Alessandro Ranellucci | ad99b2a0fd | |
Alessandro Ranellucci | dd1183f19a | |
Alessandro Ranellucci | ee2c1c6127 | |
Alessandro Ranellucci | 5d12a03b82 | |
Alessandro Ranellucci | 59f0e76da1 | |
Alessandro Ranellucci | baefefc50d | |
Alessandro Ranellucci | 69002b8ea2 | |
Alessandro Ranellucci | 47940a712d | |
Alessandro Ranellucci | a02a7f1a0f | |
Alessandro Ranellucci | 27c73f5983 | |
Alessandro Ranellucci | 65b7d27def | |
Alessandro Ranellucci | 7ba08c90cf | |
Alessandro Ranellucci | bc023c2d51 | |
Alessandro Ranellucci | 76a8ec3d9e | |
Alessandro Ranellucci | f76e2c2222 | |
Alessandro Ranellucci | 6e207d3830 | |
Alessandro Ranellucci | c37ef2f18b | |
Alessandro Ranellucci | d2d885fc53 | |
Alessandro Ranellucci | 931f3114c0 | |
Alessandro Ranellucci | 093d1cbe2f | |
Alessandro Ranellucci | 13af16ea24 | |
Alessandro Ranellucci | 54a199919b | |
Y. Sapir | 05b2993769 | |
Alessandro Ranellucci | c72dc13d7e | |
Alessandro Ranellucci | b3c9285e7a | |
Alessandro Ranellucci | e16ef7a569 | |
Alessandro Ranellucci | cb1527f7ef | |
Alessandro Ranellucci | 8240f71d07 | |
Petr Ledvina | 028ef3a868 | |
Alessandro Ranellucci | 31113b8a78 | |
Alessandro Ranellucci | 0a88492fdc | |
Alessandro Ranellucci | fce669dea0 | |
Alessandro Ranellucci | fc3a73afe8 | |
Alessandro Ranellucci | c81ffc391d | |
Alessandro Ranellucci | 5f88135074 | |
Alessandro Ranellucci | 2a3923934e | |
Alessandro Ranellucci | 4311f30739 | |
Alessandro Ranellucci | edeb0a90dd | |
Alessandro Ranellucci | 60f640f100 | |
Alessandro Ranellucci | 9734a40647 | |
Alessandro Ranellucci | 93c1ae92c9 | |
Alessandro Ranellucci | 010b71e9d4 | |
Alessandro Ranellucci | 581376bf75 | |
Alessandro Ranellucci | 83435aebb4 | |
Alessandro Ranellucci | 4680bbdfe2 | |
Alessandro Ranellucci | 8ded268e7e | |
Alessandro Ranellucci | 1667d1826d | |
Alessandro Ranellucci | 1d10cd3da6 | |
Alessandro Ranellucci | 3e3cc4171c | |
Alessandro Ranellucci | ef2296dc8d | |
Alessandro Ranellucci | ed1c6d1aaa | |
Alessandro Ranellucci | 98e40d3fe4 | |
Petr Ledvina | 78a08e0665 | |
Alessandro Ranellucci | a31b2e6ca2 | |
Alessandro Ranellucci | caf7b3f97e | |
Alessandro Ranellucci | 7a9dec3720 | |
Alessandro Ranellucci | a4b6075600 | |
Alessandro Ranellucci | 913ab54a2b | |
Alessandro Ranellucci | 0b0ec7be37 | |
Alessandro Ranellucci | f7421053cc | |
Alessandro Ranellucci | 24571612c7 | |
Alessandro Ranellucci | 4c330b6c59 | |
Alessandro Ranellucci | 19fdf9b184 | |
Alessandro Ranellucci | 2720000a17 | |
Alessandro Ranellucci | d4e97d17d5 | |
Y. Sapir | ad03a88733 | |
Y. Sapir | e005ff32c4 | |
Y. Sapir | 71b0b211ec | |
Y. Sapir | d824de6168 | |
Y. Sapir | c2b249d059 | |
Y. Sapir | 2565d80679 | |
Y. Sapir | b319dc9361 | |
Y. Sapir | 717d099ae1 | |
Y. Sapir | 04fad70cbf | |
Y. Sapir | 66d23a2416 | |
Alessandro Ranellucci | 2738a304e8 | |
Alessandro Ranellucci | 5296867a38 | |
Petr Ledvina | 115aa6885f | |
Alessandro Ranellucci | e68b6b6f4c | |
Y. Sapir | 85bca96982 | |
Alessandro Ranellucci | 611159ae20 | |
Y. Sapir | 0224e72f8c | |
Alessandro Ranellucci | fa81147fee | |
Y. Sapir | 87eee8eea3 | |
Alessandro Ranellucci | ce9cf4d964 | |
Alessandro Ranellucci | 129b6651f6 | |
Alessandro Ranellucci | 148c773319 | |
Alessandro Ranellucci | 7e3024d6ad | |
Alessandro Ranellucci | 073b7fca1f | |
Alessandro Ranellucci | 9ee3868f92 | |
Alessandro Ranellucci | 6780e930cb | |
Alessandro Ranellucci | f7ffc48397 | |
Alessandro Ranellucci | 7ac0095018 | |
Alessandro Ranellucci | 924cb2e558 | |
Alessandro Ranellucci | f6897a346a | |
Alessandro Ranellucci | 2a2ba15665 | |
Alessandro Ranellucci | 8db4913b04 | |
Alessandro Ranellucci | 4f17c2b7d1 | |
Alessandro Ranellucci | 882a98ed44 | |
Alessandro Ranellucci | 334bc4c581 | |
Alessandro Ranellucci | 4f5d9ca795 | |
Alessandro Ranellucci | fe1691c151 | |
Alessandro Ranellucci | 37c7b958d4 | |
Alessandro Ranellucci | bb0ce3cccd | |
Alessandro Ranellucci | ca4d4211c9 | |
Alessandro Ranellucci | 6201aacf88 | |
Alessandro Ranellucci | d458a7c4d2 | |
Petr Ledvina | 4c1ffecfc4 | |
Petr Ledvina | 802ebfb0d6 | |
Alessandro Ranellucci | 766d743b0b | |
Will Miles | fbce8e6730 | |
Alessandro Ranellucci | 1fc76b73a4 | |
Petr Ledvina | c30040b691 | |
Alessandro Ranellucci | 138c169634 | |
Alessandro Ranellucci | 370df56f73 | |
Alessandro Ranellucci | 3a49dccf69 | |
Alessandro Ranellucci | c87a21f304 | |
Alessandro Ranellucci | 9989ebaabd | |
Alessandro Ranellucci | bc101bd93e | |
Alessandro Ranellucci | f7e8a99078 | |
Alessandro Ranellucci | c4bfe64fb8 | |
Alessandro Ranellucci | 7041ebdd22 | |
Alessandro Ranellucci | 5d10ef514f | |
Alessandro Ranellucci | 8018eac0f0 | |
Alessandro Ranellucci | a49e786b04 | |
Alessandro Ranellucci | 6e6fb427fb | |
Alessandro Ranellucci | 10a5a061d4 | |
Alessandro Ranellucci | 46191bf254 | |
Alessandro Ranellucci | a248c98192 | |
Alessandro Ranellucci | c3bb8a69db | |
Alessandro Ranellucci | f6e99f1467 | |
Alessandro Ranellucci | f0ff653018 | |
Alessandro Ranellucci | 1e5dcd8154 | |
Alessandro Ranellucci | f51921a11a | |
Alessandro Ranellucci | 0c50ab5323 | |
Alessandro Ranellucci | caf20664cd | |
Alessandro Ranellucci | 62e19469bc | |
Alessandro Ranellucci | a0133ba093 | |
Alessandro Ranellucci | 15628a90ed | |
Alessandro Ranellucci | 93f4fe05e1 | |
Alessandro Ranellucci | e02d33bbce | |
Alessandro Ranellucci | 43ffb7d3e8 | |
Alessandro Ranellucci | 859bf46401 | |
Alessandro Ranellucci | 7569836b3c | |
Alessandro Ranellucci | f9661b02a6 | |
Petr Ledvina | c81d26b960 | |
Alessandro Ranellucci | 2ac3b1fba9 | |
Alessandro Ranellucci | 26bdbf0210 | |
Alessandro Ranellucci | 8e5ca0ab76 | |
Alessandro Ranellucci | 627f23d5fe | |
Alessandro Ranellucci | 20ca6cea05 | |
Alessandro Ranellucci | 38f6e3b643 | |
Alessandro Ranellucci | 9be57f750d | |
Alessandro Ranellucci | 23e6abff49 | |
Alessandro Ranellucci | 119778caa9 | |
Alessandro Ranellucci | 93a7d87fc6 | |
Alessandro Ranellucci | 1c020eda78 | |
Alessandro Ranellucci | 33ade1328f | |
Alessandro Ranellucci | e4709068b9 | |
Alessandro Ranellucci | 1d35701f99 | |
Alessandro Ranellucci | c7a96a3113 | |
Alessandro Ranellucci | 20df1023a6 | |
Alessandro Ranellucci | e9e23119fc | |
Alessandro Ranellucci | fc4ac5ded4 | |
Alessandro Ranellucci | d6d6a51e0d | |
Alessandro Ranellucci | d2a6194960 | |
Alessandro Ranellucci | 6f3844c1ba | |
Alessandro Ranellucci | a6dd68a2a6 | |
Alessandro Ranellucci | b68c55fec0 | |
Alessandro Ranellucci | f308a46cd5 | |
Alessandro Ranellucci | 45559f87f3 | |
Y. Sapir | 4deeff995a | |
Y. Sapir | f56206cac3 | |
Y. Sapir | 9f71ea15cf | |
Y. Sapir | 576d02a20d | |
Y. Sapir | 09d7d9b034 | |
Alessandro Ranellucci | 017158c877 | |
Alessandro Ranellucci | 97d9c9f5e7 | |
Alessandro Ranellucci | 630004d156 | |
Alessandro Ranellucci | 2a52a318fe | |
Alessandro Ranellucci | bf1fd0cf9a | |
Alessandro Ranellucci | 96ad37f6e0 | |
Alessandro Ranellucci | 280a1a369e | |
Alessandro Ranellucci | 2d9c399d96 | |
Alessandro Ranellucci | a712284afb | |
Alessandro Ranellucci | fd6b78f7f2 | |
Alessandro Ranellucci | 8c4a0f23df | |
Alessandro Ranellucci | c1a5cddcd2 | |
Alessandro Ranellucci | 71322cc49d | |
Alessandro Ranellucci | 9ec7ae08ad | |
Alessandro Ranellucci | 35095ff12e | |
Alessandro Ranellucci | 0c7a1777de | |
Alessandro Ranellucci | 1ec6494d65 | |
Alessandro Ranellucci | ee82e56a4f | |
Alessandro Ranellucci | d099118ca7 | |
Alessandro Ranellucci | 7ce49fc2b2 | |
Alessandro Ranellucci | 7be042567d | |
Alessandro Ranellucci | cfbbb539a5 | |
Alessandro Ranellucci | 4da71e8f13 | |
Alessandro Ranellucci | a64f7aa8df | |
Alessandro Ranellucci | 130e8dd8e7 | |
Alessandro Ranellucci | 26f0fab27a | |
Alessandro Ranellucci | b43ead06fe | |
Alessandro Ranellucci | 5c02bfd310 | |
Alessandro Ranellucci | a32f548a23 | |
Alessandro Ranellucci | bb50dfb9ba | |
Alessandro Ranellucci | 468935c177 | |
Alessandro Ranellucci | 713bdd8055 | |
Alessandro Ranellucci | 6afc01c3b9 | |
Alessandro Ranellucci | 354e4ce841 | |
Alessandro Ranellucci | 907e72830f | |
Alessandro Ranellucci | 6d8d166eff | |
Alessandro Ranellucci | eb5ca20449 | |
Alessandro Ranellucci | b551852392 | |
Alessandro Ranellucci | 74f2f45554 | |
Alessandro Ranellucci | c180a2de57 | |
Alessandro Ranellucci | e403dc16ae | |
Alessandro Ranellucci | ca16567ba9 | |
Alessandro Ranellucci | 7e8841805c | |
Alessandro Ranellucci | 69ed69179b |
|
@ -2,8 +2,9 @@ language: perl
|
|||
install: true
|
||||
script: perl ./Build.PL
|
||||
perl:
|
||||
- "5.14"
|
||||
- "5.12"
|
||||
- "5.14"
|
||||
- "5.18"
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
|
13
Build.PL
13
Build.PL
|
@ -27,6 +27,7 @@ my %recommends = qw(
|
|||
XML::SAX::ExpatXS 0
|
||||
);
|
||||
|
||||
my $sudo = grep { $_ eq '--sudo' } @ARGV;
|
||||
my $gui = grep { $_ eq '--gui' } @ARGV;
|
||||
my $xs_only = grep { $_ eq '--xs' } @ARGV;
|
||||
if ($gui) {
|
||||
|
@ -89,19 +90,21 @@ If it is installed in a non-standard location you can do:
|
|||
|
||||
EOF
|
||||
if !$cpanm;
|
||||
|
||||
my @cpanm_args = ();
|
||||
push @cpanm_args, "--sudo" if $sudo;
|
||||
|
||||
# make sure our cpanm is updated (old ones don't support the ~ syntax)
|
||||
system $cpanm, 'App::cpanminus';
|
||||
system $cpanm, @cpanm_args, 'App::cpanminus';
|
||||
|
||||
# install the Windows-compatible Math::Libm
|
||||
if ($^O eq 'MSWin32' && !eval "use Math::Libm; 1") {
|
||||
system $cpanm, 'https://github.com/alexrj/Math-Libm/tarball/master';
|
||||
system $cpanm, @cpanm_args, 'https://github.com/alexrj/Math-Libm/tarball/master';
|
||||
}
|
||||
|
||||
my %modules = (%prereqs, %recommends);
|
||||
foreach my $module (sort keys %modules) {
|
||||
my $version = $modules{$module};
|
||||
my @cmd = ($cpanm, "$module~$version");
|
||||
my @cmd = ($cpanm, @cpanm_args, "$module~$version");
|
||||
if ($module eq 'XML::SAX::ExpatXS' && $^O eq 'MSWin32') {
|
||||
my $mingw = 'C:\dev\CitrusPerl\mingw64';
|
||||
$mingw = 'C:\dev\CitrusPerl\mingw32' if !-d $mingw;
|
||||
|
@ -131,7 +134,7 @@ EOF
|
|||
system './xs/Build', 'distclean';
|
||||
}
|
||||
}
|
||||
my $res = system $cpanm, '--reinstall', './xs';
|
||||
my $res = system $cpanm, @cpanm_args, '--reinstall', '--verbose', './xs';
|
||||
if ($res != 0) {
|
||||
die "The XS/C++ code failed to compile, aborting\n";
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ If possible, please include the following information when [reporting an issue](
|
|||
* Operating system type + version
|
||||
* Steps to reproduce the issue, including:
|
||||
* Command line parameters used, if any
|
||||
* Slic3r configuration file (Use ``Export Config...`` from the ``File`` menu)
|
||||
* Slic3r configuration file (Use ``Export Config...`` from the ``File`` menu - please don't export a bundle)
|
||||
* Expected result
|
||||
* Actual result
|
||||
* Any error messages
|
||||
|
|
24
README.md
24
README.md
|
@ -94,10 +94,15 @@ The author of the Silk icon set is Mark James.
|
|||
more than once to load options from multiple files.
|
||||
-o, --output <file> File to output gcode to (by default, the file will be saved
|
||||
into the same directory as the input file using the
|
||||
--output-filename-format to generate the filename)
|
||||
|
||||
--output-filename-format to generate the filename.) If a
|
||||
directory is specified for this option, the output will
|
||||
be saved under that directory, and the filename will be
|
||||
generated by --output-filename-format.
|
||||
|
||||
Non-slicing actions (no G-code will be generated):
|
||||
--repair Repair given STL files and save them as <name>_fixed.obj
|
||||
--cut <z> Cut given input files at given Z (relative) and export
|
||||
them as <name>_upper.stl and <name>_lower.stl
|
||||
--info Output information about the supplied file(s) and exit
|
||||
|
||||
-j, --threads <num> Number of threads to use (1+, default: 2)
|
||||
|
@ -165,6 +170,9 @@ The author of the Silk icon set is Mark James.
|
|||
(default: 50)
|
||||
--support-material-speed
|
||||
Speed of support material print moves in mm/s (default: 60)
|
||||
--support-material-interface-speed
|
||||
Speed of support material interface print moves in mm/s or % over support material
|
||||
speed (default: 100%)
|
||||
--bridge-speed Speed of bridge print moves in mm/s (default: 60)
|
||||
--gap-fill-speed Speed of gap fill print moves in mm/s (default: 20)
|
||||
--first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute
|
||||
|
@ -211,7 +219,7 @@ The author of the Silk icon set is Mark James.
|
|||
home X axis [G28 X], disable motors [M84]).
|
||||
--layer-gcode Load layer-change G-code from the supplied file (default: nothing).
|
||||
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
|
||||
--randomize-start Randomize starting point across layers (default: yes)
|
||||
--seam-position Position of loop starting points (random/nearest/aligned, default: aligned).
|
||||
--external-perimeters-first Reverse perimeter order. (default: no)
|
||||
--spiral-vase Experimental option to raise Z gradually when printing single-walled vases
|
||||
(default: no)
|
||||
|
@ -228,10 +236,6 @@ The author of the Silk icon set is Mark James.
|
|||
Quality options (slower slicing):
|
||||
--extra-perimeters Add more perimeters when needed (default: yes)
|
||||
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
|
||||
--start-perimeters-at-concave-points
|
||||
Try to start perimeters at concave points if any (default: no)
|
||||
--start-perimeters-at-non-overhang
|
||||
Try to start perimeters at non-overhang points if any (default: no)
|
||||
--thin-walls Detect single-width walls (default: yes)
|
||||
--overhangs Experimental option to use bridge flow, speed and fan for overhangs
|
||||
(default: yes)
|
||||
|
@ -255,6 +259,8 @@ The author of the Silk icon set is Mark James.
|
|||
--support-material-enforce-layers
|
||||
Enforce support material on the specified number of layers from bottom,
|
||||
regardless of --support-material and threshold (0+, default: 0)
|
||||
--dont-support-bridges
|
||||
Experimental option for preventing support material from being generated under bridged areas (default: yes)
|
||||
|
||||
Retraction options:
|
||||
--retract-length Length of retraction in mm when pausing extrusion (default: 1)
|
||||
|
@ -307,6 +313,8 @@ The author of the Silk icon set is Mark James.
|
|||
--bed-size Bed size, only used for auto-arrange (mm, default: 200,200)
|
||||
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
||||
--duplicate-distance Distance in mm between copies (default: 6)
|
||||
--xy-size-compensation
|
||||
Grow/shrink objects by the configured absolute distance (mm, default: 0)
|
||||
|
||||
Sequential printing options:
|
||||
--complete-objects When printing multiple objects and/or copies, complete each one before
|
||||
|
@ -327,6 +335,8 @@ The author of the Silk icon set is Mark James.
|
|||
Set a different extrusion width for first layer
|
||||
--perimeter-extrusion-width
|
||||
Set a different extrusion width for perimeters
|
||||
--external-perimeter-extrusion-width
|
||||
Set a different extrusion width for external perimeters
|
||||
--infill-extrusion-width
|
||||
Set a different extrusion width for infill
|
||||
--solid-infill-extrusion-width
|
||||
|
|
|
@ -7,7 +7,7 @@ use strict;
|
|||
use warnings;
|
||||
require v5.10;
|
||||
|
||||
our $VERSION = "1.1.0";
|
||||
our $VERSION = "1.2.0-dev";
|
||||
|
||||
our $debug = 0;
|
||||
sub debugf {
|
||||
|
@ -59,6 +59,7 @@ use Slic3r::GCode::VibrationLimit;
|
|||
use Slic3r::Geometry qw(PI);
|
||||
use Slic3r::Geometry::Clipper;
|
||||
use Slic3r::Layer;
|
||||
use Slic3r::Layer::BridgeDetector;
|
||||
use Slic3r::Layer::Region;
|
||||
use Slic3r::Line;
|
||||
use Slic3r::Model;
|
||||
|
@ -81,6 +82,7 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
|
|||
use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15;
|
||||
use constant INFILL_OVERLAP_OVER_SPACING => 0.45;
|
||||
use constant EXTERNAL_INFILL_MARGIN => 3;
|
||||
use constant INSET_OVERLAP_TOLERANCE => 0.2;
|
||||
|
||||
sub parallelize {
|
||||
my %params = @_;
|
||||
|
@ -138,19 +140,25 @@ sub thread_cleanup {
|
|||
*Slic3r::Config::PrintRegion::DESTROY = sub {};
|
||||
*Slic3r::ExPolygon::DESTROY = sub {};
|
||||
*Slic3r::ExPolygon::Collection::DESTROY = sub {};
|
||||
*Slic3r::Extruder::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionLoop::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
|
||||
*Slic3r::Flow::DESTROY = sub {};
|
||||
*Slic3r::GCode::PlaceholderParser::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
|
||||
*Slic3r::Line::DESTROY = sub {};
|
||||
*Slic3r::Model::DESTROY = sub {};
|
||||
*Slic3r::Model::Object::DESTROY = sub {};
|
||||
*Slic3r::Point::DESTROY = sub {};
|
||||
*Slic3r::Pointf::DESTROY = sub {};
|
||||
*Slic3r::Pointf3::DESTROY = sub {};
|
||||
*Slic3r::Polygon::DESTROY = sub {};
|
||||
*Slic3r::Polyline::DESTROY = sub {};
|
||||
*Slic3r::Polyline::Collection::DESTROY = sub {};
|
||||
*Slic3r::Print::State::DESTROY = sub {};
|
||||
*Slic3r::Print::DESTROY = sub {};
|
||||
*Slic3r::Print::Region::DESTROY = sub {};
|
||||
*Slic3r::Surface::DESTROY = sub {};
|
||||
*Slic3r::Surface::Collection::DESTROY = sub {};
|
||||
*Slic3r::TriangleMesh::DESTROY = sub {};
|
||||
|
|
|
@ -8,10 +8,14 @@ use List::Util qw(first max);
|
|||
# cemetery of old config settings
|
||||
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration
|
||||
adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid
|
||||
rotate scale duplicate_grid);
|
||||
rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang
|
||||
randomize_start seal_position);
|
||||
|
||||
our $Options = print_config_def();
|
||||
|
||||
# overwrite the hard-coded readonly value (this information is not available in XS)
|
||||
$Options->{threads}{readonly} = !$Slic3r::have_threads;
|
||||
|
||||
# generate accessors
|
||||
{
|
||||
no strict 'refs';
|
||||
|
@ -55,9 +59,13 @@ sub new_from_cli {
|
|||
|
||||
my $self = $class->new;
|
||||
foreach my $opt_key (keys %args) {
|
||||
if ($opt_key =~ /^(?:print_center|bed_size|duplicate_grid|extruder_offset|retract_layer_change|wipe)$/) {
|
||||
my $opt_def = $Options->{$opt_key};
|
||||
|
||||
# we use set_deserialize() for bool options since GetOpt::Long doesn't handle
|
||||
# arrays of boolean values
|
||||
if ($opt_key =~ /^(?:print_center|bed_size|duplicate_grid|extruder_offset)$/ || $opt_def->{type} eq 'bool') {
|
||||
$self->set_deserialize($opt_key, $args{$opt_key});
|
||||
} elsif (my $shortcut = $Options->{$opt_key}{shortcut}) {
|
||||
} elsif (my $shortcut = $opt_def->{shortcut}) {
|
||||
$self->set($_, $args{$opt_key}) for @$shortcut;
|
||||
} else {
|
||||
$self->set($opt_key, $args{$opt_key});
|
||||
|
@ -133,6 +141,10 @@ sub _handle_legacy {
|
|||
$value *= 100;
|
||||
$value = "$value"; # force update of the PV value, workaround for bug https://rt.cpan.org/Ticket/Display.html?id=94110
|
||||
}
|
||||
if ($opt_key eq 'randomize_start' && $value) {
|
||||
$opt_key = 'seam_position';
|
||||
$value = 'random';
|
||||
}
|
||||
|
||||
# For historical reasons, the world's full of configs having these very low values;
|
||||
# to avoid unexpected behavior we need to ignore them. Banning these two hard-coded
|
||||
|
@ -323,7 +335,7 @@ sub validate {
|
|||
die "Can't make less than one perimeter when spiral vase mode is enabled\n"
|
||||
if $self->perimeters < 1;
|
||||
|
||||
die "Spiral vase mode is not compatible with non-zero fill density\n"
|
||||
die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n"
|
||||
if $self->fill_density > 0;
|
||||
|
||||
die "Spiral vase mode is not compatible with top solid layers\n"
|
||||
|
@ -331,11 +343,6 @@ sub validate {
|
|||
|
||||
die "Spiral vase mode is not compatible with support material\n"
|
||||
if $self->support_material || $self->support_material_enforce_layers > 0;
|
||||
|
||||
# This should be enforce automatically only on spiral layers and
|
||||
# done on the others
|
||||
die "Spiral vase mode is not compatible with retraction on layer change\n"
|
||||
if defined first { $_ } @{ $self->retract_layer_change };
|
||||
}
|
||||
|
||||
# extrusion widths
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package Slic3r::Extruder;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require Exporter;
|
||||
our @ISA = qw(Exporter);
|
||||
|
@ -9,61 +10,25 @@ our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
|||
|
||||
use Slic3r::Geometry qw(PI scale);
|
||||
|
||||
use constant OPTIONS => [qw(
|
||||
extruder_offset
|
||||
nozzle_diameter filament_diameter extrusion_multiplier temperature first_layer_temperature
|
||||
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel
|
||||
retract_layer_change retract_length_toolchange retract_restart_extra_toolchange wipe
|
||||
)];
|
||||
|
||||
has 'id' => (is => 'rw', required => 1);
|
||||
has $_ => (is => 'ro', required => 1) for @{&OPTIONS};
|
||||
has 'use_relative_e_distances' => (is => 'ro', default => sub {0});
|
||||
|
||||
has 'E' => (is => 'rw', default => sub {0} );
|
||||
has 'absolute_E' => (is => 'rw', default => sub {0} );
|
||||
has 'retracted' => (is => 'rw', default => sub {0} );
|
||||
has 'restart_extra' => (is => 'rw', default => sub {0} );
|
||||
has 'e_per_mm3' => (is => 'lazy');
|
||||
has 'retract_speed_mm_min' => (is => 'lazy');
|
||||
# has 'e_per_mm3' => (is => 'lazy');
|
||||
# has 'retract_speed_mm_min' => (is => 'lazy');
|
||||
|
||||
use constant EXTRUDER_ROLE_PERIMETER => 1;
|
||||
use constant EXTRUDER_ROLE_INFILL => 2;
|
||||
use constant EXTRUDER_ROLE_SUPPORT_MATERIAL => 3;
|
||||
use constant EXTRUDER_ROLE_SUPPORT_MATERIAL_INTERFACE => 4;
|
||||
|
||||
sub new_from_config {
|
||||
my ($class, $config, $extruder_id) = @_;
|
||||
|
||||
my %conf = (
|
||||
id => $extruder_id,
|
||||
use_relative_e_distances => $config->use_relative_e_distances,
|
||||
);
|
||||
foreach my $opt_key (@{&OPTIONS}) {
|
||||
$conf{$opt_key} = $config->get_at($opt_key, $extruder_id);
|
||||
}
|
||||
return $class->new(%conf);
|
||||
}
|
||||
|
||||
sub _build_e_per_mm3 {
|
||||
sub e_per_mm3 {
|
||||
my $self = shift;
|
||||
return $self->extrusion_multiplier * (4 / (($self->filament_diameter ** 2) * PI));
|
||||
}
|
||||
|
||||
sub _build_retract_speed_mm_min {
|
||||
sub retract_speed_mm_min {
|
||||
my $self = shift;
|
||||
return $self->retract_speed * 60;
|
||||
}
|
||||
|
||||
sub reset {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->E(0);
|
||||
$self->absolute_E(0);
|
||||
$self->retracted(0);
|
||||
$self->restart_extra(0);
|
||||
}
|
||||
|
||||
sub scaled_wipe_distance {
|
||||
my ($self, $travel_speed) = @_;
|
||||
|
||||
|
@ -74,14 +39,6 @@ sub scaled_wipe_distance {
|
|||
return scale($self->retract_length / $self->retract_speed * $travel_speed * 0.8);
|
||||
}
|
||||
|
||||
sub extrude {
|
||||
my ($self, $E) = @_;
|
||||
|
||||
$self->E(0) if $self->use_relative_e_distances;
|
||||
$self->absolute_E($self->absolute_E + $E);
|
||||
return $self->E($self->E + $E);
|
||||
}
|
||||
|
||||
sub extruded_volume {
|
||||
my ($self, $E) = @_;
|
||||
return $E * ($self->filament_diameter**2) * PI/4;
|
||||
|
|
|
@ -2,14 +2,11 @@ package Slic3r::ExtrusionLoop;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub split_at {
|
||||
my $self = shift;
|
||||
|
||||
return Slic3r::ExtrusionPath->new(
|
||||
polyline => $self->polygon->split_at(@_),
|
||||
role => $self->role,
|
||||
mm3_per_mm => $self->mm3_per_mm,
|
||||
);
|
||||
}
|
||||
use parent qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT EXTRL_ROLE_EXTERNAL_PERIMETER
|
||||
EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
||||
|
||||
1;
|
||||
|
|
|
@ -4,10 +4,9 @@ use warnings;
|
|||
|
||||
use parent qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER
|
||||
EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
|
||||
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE
|
||||
EXTR_ROLE_INTERNALBRIDGE EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL);
|
||||
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
|
||||
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE
|
||||
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
||||
1;
|
||||
|
|
|
@ -13,7 +13,7 @@ use Slic3r::Fill::OctagramSpiral;
|
|||
use Slic3r::Fill::PlanePath;
|
||||
use Slic3r::Fill::Rectilinear;
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(X Y PI scale chained_path);
|
||||
use Slic3r::Geometry qw(X Y PI scale chained_path deg2rad);
|
||||
use Slic3r::Geometry::Clipper qw(union union_ex diff diff_ex intersection_ex offset offset2);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
|
@ -62,7 +62,7 @@ sub make_fill {
|
|||
# in case of bridge surfaces, the ones with defined angle will be attached to the ones
|
||||
# without any angle (shouldn't this logic be moved to process_external_surfaces()?)
|
||||
{
|
||||
my @surfaces_with_bridge_angle = grep defined $_->bridge_angle, @{$layerm->fill_surfaces};
|
||||
my @surfaces_with_bridge_angle = grep { $_->bridge_angle >= 0 } @{$layerm->fill_surfaces};
|
||||
|
||||
# group surfaces by distinct properties
|
||||
my @groups = @{$layerm->fill_surfaces->group};
|
||||
|
@ -111,13 +111,13 @@ sub make_fill {
|
|||
}
|
||||
|
||||
# give priority to bridges
|
||||
@groups = sort { defined $a->[0]->bridge_angle ? -1 : 0 } @groups;
|
||||
@groups = sort { ($a->[0]->bridge_angle >= 0) ? -1 : 0 } @groups;
|
||||
|
||||
foreach my $group (@groups) {
|
||||
my $union_p = union([ map $_->p, @$group ], 1);
|
||||
|
||||
# subtract surfaces having a defined bridge_angle from any other
|
||||
if (@surfaces_with_bridge_angle && !defined $group->[0]->bridge_angle) {
|
||||
if (@surfaces_with_bridge_angle && $group->[0]->bridge_angle < 0) {
|
||||
$union_p = diff(
|
||||
$union_p,
|
||||
[ map $_->p, @surfaces_with_bridge_angle ],
|
||||
|
@ -182,11 +182,9 @@ sub make_fill {
|
|||
next if $surface->surface_type == S_TYPE_INTERNALVOID;
|
||||
my $filler = $layerm->config->fill_pattern;
|
||||
my $density = $fill_density;
|
||||
my $flow = ($surface->surface_type == S_TYPE_TOP)
|
||||
? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)
|
||||
: $surface->is_solid
|
||||
? $solid_infill_flow
|
||||
: $infill_flow;
|
||||
my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL
|
||||
: $surface->is_solid ? FLOW_ROLE_SOLID_INFILL
|
||||
: FLOW_ROLE_INFILL;
|
||||
my $is_bridge = $layerm->id > 0 && $surface->is_bridge;
|
||||
my $is_solid = $surface->is_solid;
|
||||
|
||||
|
@ -196,7 +194,6 @@ sub make_fill {
|
|||
$filler = $layerm->config->solid_fill_pattern;
|
||||
if ($is_bridge) {
|
||||
$filler = 'rectilinear';
|
||||
$flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 1);
|
||||
} elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) {
|
||||
$filler = 'rectilinear';
|
||||
}
|
||||
|
@ -204,19 +201,28 @@ sub make_fill {
|
|||
next SURFACE unless $density > 0;
|
||||
}
|
||||
|
||||
my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness;
|
||||
my $flow = $layerm->region->flow(
|
||||
$role,
|
||||
$h,
|
||||
$is_bridge,
|
||||
$layerm->id == 0,
|
||||
undef,
|
||||
$layerm->object,
|
||||
);
|
||||
|
||||
my $f = $self->filler($filler);
|
||||
$f->layer_id($layerm->id);
|
||||
$f->angle($layerm->config->fill_angle);
|
||||
$f->angle(deg2rad($layerm->config->fill_angle));
|
||||
my ($params, @polylines) = $f->fill_surface(
|
||||
$surface,
|
||||
density => $density/100,
|
||||
flow => $flow,
|
||||
density => $density/100,
|
||||
flow => $flow,
|
||||
layer_height => $h,
|
||||
);
|
||||
next unless @polylines;
|
||||
|
||||
my $h = $surface->thickness;
|
||||
$h = $layerm->height if $h == -1;
|
||||
my $mm3_per_mm = $params->{flow}->mm3_per_mm($h);
|
||||
my $mm3_per_mm = $flow->mm3_per_mm;
|
||||
|
||||
# save into layer
|
||||
push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new;
|
||||
|
@ -225,23 +231,23 @@ sub make_fill {
|
|||
$collection->append(
|
||||
map Slic3r::ExtrusionPath->new(
|
||||
polyline => $_,
|
||||
role => ($surface->surface_type == S_TYPE_INTERNALBRIDGE
|
||||
? EXTR_ROLE_INTERNALBRIDGE
|
||||
: $is_bridge
|
||||
role => ($is_bridge
|
||||
? EXTR_ROLE_BRIDGE
|
||||
: $is_solid
|
||||
? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
|
||||
: EXTR_ROLE_FILL),
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $h,
|
||||
), @polylines,
|
||||
);
|
||||
push @fills_ordering_points, $polylines[0]->first_point;
|
||||
}
|
||||
|
||||
# add thin fill regions
|
||||
if ($layerm->thin_fills->count > 0) {
|
||||
push @fills, Slic3r::ExtrusionPath::Collection->new(@{$layerm->thin_fills});
|
||||
push @fills_ordering_points, $fills[-1]->first_point;
|
||||
foreach my $thin_fill (@{$layerm->thin_fills}) {
|
||||
push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill);
|
||||
push @fills_ordering_points, $thin_fill->first_point;
|
||||
}
|
||||
|
||||
# organize infill paths using a nearest-neighbor search
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package Slic3r::Fill::Base;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::Geometry qw(PI);
|
||||
use Slic3r::Geometry qw(PI rad2deg);
|
||||
|
||||
has 'layer_id' => (is => 'rw');
|
||||
has 'angle' => (is => 'rw');
|
||||
has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East
|
||||
has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object
|
||||
|
||||
sub angles () { [0, PI/2] }
|
||||
|
@ -19,28 +19,29 @@ sub infill_direction {
|
|||
}
|
||||
|
||||
# set infill angle
|
||||
my (@rotate, @shift);
|
||||
$rotate[0] = Slic3r::Geometry::deg2rad($self->angle);
|
||||
my (@rotate);
|
||||
$rotate[0] = $self->angle;
|
||||
$rotate[1] = $self->bounding_box
|
||||
? $self->bounding_box->center
|
||||
: $surface->expolygon->bounding_box->center;
|
||||
@shift = @{$rotate[1]};
|
||||
my $shift = $rotate[1]->clone;
|
||||
|
||||
if (defined $self->layer_id) {
|
||||
# alternate fill direction
|
||||
my $layer_num = $self->layer_id / $surface->thickness_layers;
|
||||
my $angle = $self->angles->[$layer_num % @{$self->angles}];
|
||||
$rotate[0] = Slic3r::Geometry::deg2rad($self->angle) + $angle if $angle;
|
||||
$rotate[0] = $self->angle + $angle if $angle;
|
||||
}
|
||||
|
||||
# use bridge angle
|
||||
if ($surface->bridge_angle != -1) {
|
||||
Slic3r::debugf "Filling bridge with angle %d\n", $surface->bridge_angle;
|
||||
$rotate[0] = Slic3r::Geometry::deg2rad($surface->bridge_angle);
|
||||
if ($surface->bridge_angle >= 0) {
|
||||
Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle);
|
||||
$rotate[0] = $surface->bridge_angle;
|
||||
}
|
||||
|
||||
@shift = @{ +(Slic3r::Geometry::rotate_points(@rotate, \@shift))[0] };
|
||||
return [\@rotate, \@shift];
|
||||
$rotate[0] += PI/2;
|
||||
$shift->rotate(@rotate);
|
||||
return [\@rotate, $shift];
|
||||
}
|
||||
|
||||
# this method accepts any object that implements rotate() and translate()
|
||||
|
@ -49,18 +50,21 @@ sub rotate_points {
|
|||
my ($expolygon, $rotate_vector) = @_;
|
||||
|
||||
# rotate points
|
||||
$expolygon->rotate(@{$rotate_vector->[0]});
|
||||
$expolygon->translate(@{$rotate_vector->[1]});
|
||||
my ($rotate, $shift) = @$rotate_vector;
|
||||
$rotate = [ -$rotate->[0], $rotate->[1] ];
|
||||
$expolygon->rotate(@$rotate);
|
||||
$expolygon->translate(@$shift);
|
||||
}
|
||||
|
||||
sub rotate_points_back {
|
||||
my $self = shift;
|
||||
my ($paths, $rotate_vector) = @_;
|
||||
my @rotate = (-$rotate_vector->[0][0], $rotate_vector->[0][1]);
|
||||
my $shift = [ map -$_, @{$rotate_vector->[1]} ];
|
||||
|
||||
my ($rotate, $shift) = @$rotate_vector;
|
||||
$shift = [ map -$_, @$shift ];
|
||||
|
||||
$_->translate(@$shift) for @$paths;
|
||||
$_->rotate(@rotate) for @$paths;
|
||||
$_->rotate(@$rotate) for @$paths;
|
||||
}
|
||||
|
||||
sub adjust_solid_spacing {
|
||||
|
|
|
@ -28,7 +28,7 @@ sub fill_surface {
|
|||
$flow = Slic3r::Flow->new_from_spacing(
|
||||
spacing => unscale($distance),
|
||||
nozzle_diameter => $flow->nozzle_diameter,
|
||||
layer_height => $surface->thickness,
|
||||
layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
|
||||
bridge => $flow->bridge,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ sub fill_surface {
|
|||
$flow = Slic3r::Flow->new_from_spacing(
|
||||
spacing => unscale($line_spacing),
|
||||
nozzle_diameter => $flow->nozzle_diameter,
|
||||
layer_height => $surface->thickness,
|
||||
layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
|
||||
bridge => $flow->bridge,
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,8 @@ use warnings;
|
|||
|
||||
use parent qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(FLOW_ROLE_PERIMETER FLOW_ROLE_INFILL FLOW_ROLE_SOLID_INFILL
|
||||
our @EXPORT_OK = qw(FLOW_ROLE_EXTERNAL_PERIMETER FLOW_ROLE_PERIMETER FLOW_ROLE_INFILL
|
||||
FLOW_ROLE_SOLID_INFILL
|
||||
FLOW_ROLE_TOP_SOLID_INFILL FLOW_ROLE_SUPPORT_MATERIAL
|
||||
FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
|
|
@ -35,8 +35,8 @@ sub write_file {
|
|||
printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
|
||||
printf $fh qq{<amf unit="millimeter">\n};
|
||||
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
|
||||
for my $material_id (sort keys %{ $model->materials }) {
|
||||
my $material = $model->materials->{$material_id};
|
||||
for my $material_id (sort @{ $model->material_names }) {
|
||||
my $material = $model->get_material($material_id);
|
||||
printf $fh qq{ <material id="%s">\n}, $material_id;
|
||||
for (keys %{$material->attributes}) {
|
||||
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
|
||||
|
|
|
@ -126,7 +126,7 @@ sub end_document {
|
|||
foreach my $instance (@{ $self->{_instances}{$object_id} }) {
|
||||
$self->{_model}->objects->[$new_object_id]->add_instance(
|
||||
rotation => $instance->{rz} || 0,
|
||||
offset => [ $instance->{deltax} || 0, $instance->{deltay} || 0 ],
|
||||
offset => Slic3r::Pointf->new($instance->{deltax} || 0, $instance->{deltay} || 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,13 +25,15 @@ sub read_file {
|
|||
|
||||
sub write_file {
|
||||
my $self = shift;
|
||||
my ($file, $model, %params) = @_;
|
||||
my ($file, $mesh, %params) = @_;
|
||||
|
||||
$mesh = $mesh->mesh if $mesh->isa('Slic3r::Model');
|
||||
|
||||
my $path = Slic3r::encode_path($file);
|
||||
|
||||
$params{binary}
|
||||
? $model->mesh->write_binary($path)
|
||||
: $model->mesh->write_ascii($path);
|
||||
? $mesh->write_binary($path)
|
||||
: $mesh->write_ascii($path);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -2,13 +2,14 @@ package Slic3r::GCode;
|
|||
use Moo;
|
||||
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex offset_ex);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new });
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new });
|
||||
has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new });
|
||||
has 'standby_points' => (is => 'rw');
|
||||
has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
|
||||
|
@ -16,20 +17,15 @@ has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one e
|
|||
has 'layer_count' => (is => 'ro', required => 1 );
|
||||
has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter
|
||||
has 'layer' => (is => 'rw');
|
||||
has 'region' => (is => 'rw');
|
||||
has '_layer_islands' => (is => 'rw');
|
||||
has '_upper_layer_islands' => (is => 'rw');
|
||||
has '_layer_overhangs' => (is => 'ro', default => sub { Slic3r::ExPolygon::Collection->new });
|
||||
has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos
|
||||
has 'shift_x' => (is => 'rw', default => sub {0} );
|
||||
has 'shift_y' => (is => 'rw', default => sub {0} );
|
||||
has 'z' => (is => 'rw');
|
||||
has 'speed' => (is => 'rw');
|
||||
has '_extrusion_axis' => (is => 'rw');
|
||||
has '_retract_lift' => (is => 'rw');
|
||||
has 'extruders' => (is => 'ro', default => sub {{}});
|
||||
has 'multiple_extruders' => (is => 'rw', default => sub {0});
|
||||
has 'extruder' => (is => 'rw');
|
||||
has 'speeds' => (is => 'lazy'); # mm/min
|
||||
has 'external_mp' => (is => 'rw');
|
||||
has 'layer_mp' => (is => 'rw');
|
||||
has 'new_object' => (is => 'rw', default => sub {0});
|
||||
|
@ -37,23 +33,14 @@ has 'straight_once' => (is => 'rw', default => sub {1});
|
|||
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
|
||||
has 'lifted' => (is => 'rw', default => sub {0} );
|
||||
has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
|
||||
has 'last_speed' => (is => 'rw', default => sub {""});
|
||||
has 'last_f' => (is => 'rw', default => sub {""});
|
||||
has 'last_fan_speed' => (is => 'rw', default => sub {0});
|
||||
has 'wipe_path' => (is => 'rw');
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_extrusion_axis($self->print_config->get_extrusion_axis);
|
||||
$self->_retract_lift($self->print_config->retract_lift->[0]);
|
||||
}
|
||||
|
||||
sub set_extruders {
|
||||
my ($self, $extruder_ids) = @_;
|
||||
my ($self, $extruder_ids, $print_config) = @_;
|
||||
|
||||
foreach my $i (@$extruder_ids) {
|
||||
$self->extruders->{$i} = my $e = Slic3r::Extruder->new_from_config($self->print_config, $i);
|
||||
$self->extruders->{$i} = my $e = Slic3r::Extruder->new($i, $print_config);
|
||||
$self->enable_wipe(1) if $e->wipe;
|
||||
}
|
||||
|
||||
|
@ -63,30 +50,6 @@ sub set_extruders {
|
|||
$self->multiple_extruders(max(@$extruder_ids) > 0);
|
||||
}
|
||||
|
||||
sub _build_speeds {
|
||||
my $self = shift;
|
||||
return {
|
||||
map { $_ => 60 * $self->print_config->get_value("${_}_speed") }
|
||||
qw(travel perimeter small_perimeter external_perimeter infill
|
||||
solid_infill top_solid_infill bridge gap_fill retract),
|
||||
};
|
||||
}
|
||||
|
||||
# assign speeds to roles
|
||||
my %role_speeds = (
|
||||
&EXTR_ROLE_PERIMETER => 'perimeter',
|
||||
&EXTR_ROLE_EXTERNAL_PERIMETER => 'external_perimeter',
|
||||
&EXTR_ROLE_OVERHANG_PERIMETER => 'bridge',
|
||||
&EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 'perimeter',
|
||||
&EXTR_ROLE_FILL => 'infill',
|
||||
&EXTR_ROLE_SOLIDFILL => 'solid_infill',
|
||||
&EXTR_ROLE_TOPSOLIDFILL => 'top_solid_infill',
|
||||
&EXTR_ROLE_BRIDGE => 'bridge',
|
||||
&EXTR_ROLE_INTERNALBRIDGE => 'bridge',
|
||||
&EXTR_ROLE_SKIRT => 'perimeter',
|
||||
&EXTR_ROLE_GAPFILL => 'gap_fill',
|
||||
);
|
||||
|
||||
sub set_shift {
|
||||
my ($self, @shift) = @_;
|
||||
|
||||
|
@ -111,30 +74,24 @@ sub change_layer {
|
|||
# avoid computing islands and overhangs if they're not needed
|
||||
$self->_layer_islands($layer->islands);
|
||||
$self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []);
|
||||
$self->_layer_overhangs->clear;
|
||||
if ($layer->id > 0 && ($self->print_config->overhangs || $self->print_config->start_perimeters_at_non_overhang)) {
|
||||
$self->_layer_overhangs->append(
|
||||
# clone ExPolygons because they come from Surface objects but will be used outside here
|
||||
map $_->expolygon, map @{$_->slices->filter_by_type(S_TYPE_BOTTOMBRIDGE)}, @{$layer->regions}
|
||||
);
|
||||
}
|
||||
if ($self->print_config->avoid_crossing_perimeters) {
|
||||
if ($self->config->avoid_crossing_perimeters) {
|
||||
$self->layer_mp(Slic3r::GCode::MotionPlanner->new(
|
||||
islands => union_ex([ map @$_, @{$layer->slices} ], 1),
|
||||
));
|
||||
}
|
||||
|
||||
my $gcode = "";
|
||||
if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
# TODO: cap this to 99% and add an explicit M73 P100 in the end G-code
|
||||
$gcode .= sprintf "M73 P%s%s\n",
|
||||
int(99 * ($self->_layer_index / ($self->layer_count - 1))),
|
||||
($self->print_config->gcode_comments ? ' ; update progress' : '');
|
||||
($self->config->gcode_comments ? ' ; update progress' : '');
|
||||
}
|
||||
if ($self->print_config->first_layer_acceleration) {
|
||||
if ($self->config->first_layer_acceleration) {
|
||||
if ($layer->id == 0) {
|
||||
$gcode .= $self->set_acceleration($self->print_config->first_layer_acceleration);
|
||||
$gcode .= $self->set_acceleration($self->config->first_layer_acceleration);
|
||||
} elsif ($layer->id == 1) {
|
||||
$gcode .= $self->set_acceleration($self->print_config->default_acceleration);
|
||||
$gcode .= $self->set_acceleration($self->config->default_acceleration);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +105,7 @@ sub move_z {
|
|||
|
||||
my $gcode = "";
|
||||
|
||||
$z += $self->print_config->z_offset;
|
||||
$z += $self->config->z_offset;
|
||||
my $current_z = $self->z;
|
||||
my $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef;
|
||||
|
||||
|
@ -165,8 +122,7 @@ sub move_z {
|
|||
$current_z = $self->z; # update current z in case retract() changed it
|
||||
$nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef;
|
||||
}
|
||||
$self->speed('travel');
|
||||
$gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')'))
|
||||
$gcode .= $self->G0(undef, $z, 0, $self->config->travel_speed*60, $comment || ('move to next layer (' . $self->layer->id . ')'))
|
||||
if !defined $current_z || abs($z - $nominal_z) > epsilon;
|
||||
} elsif ($z < $current_z) {
|
||||
# we're moving above the current nominal layer height and below the current actual one.
|
||||
|
@ -187,7 +143,7 @@ sub extrude {
|
|||
}
|
||||
|
||||
sub extrude_loop {
|
||||
my ($self, $loop, $description) = @_;
|
||||
my ($self, $loop, $description, $speed) = @_;
|
||||
|
||||
# make a copy; don't modify the orientation of the original loop object otherwise
|
||||
# next copies (if any) would not detect the correct orientation
|
||||
|
@ -195,111 +151,88 @@ sub extrude_loop {
|
|||
|
||||
# extrude all loops ccw
|
||||
my $was_clockwise = $loop->make_counter_clockwise;
|
||||
my $polygon = $loop->polygon;
|
||||
|
||||
# find candidate starting points
|
||||
# start looking for concave vertices not being overhangs
|
||||
my @concave = ();
|
||||
if ($self->print_config->start_perimeters_at_concave_points) {
|
||||
@concave = $polygon->concave_points;
|
||||
}
|
||||
my @candidates = ();
|
||||
if ($self->print_config->start_perimeters_at_non_overhang) {
|
||||
@candidates = grep !$self->_layer_overhangs->contains_point($_), @concave;
|
||||
}
|
||||
if (!@candidates) {
|
||||
# if none, look for any concave vertex
|
||||
@candidates = @concave;
|
||||
if (!@candidates) {
|
||||
# if none, look for any non-overhang vertex
|
||||
if ($self->print_config->start_perimeters_at_non_overhang) {
|
||||
@candidates = grep !$self->_layer_overhangs->contains_point($_), @$polygon;
|
||||
}
|
||||
if (!@candidates) {
|
||||
# if none, all points are valid candidates
|
||||
@candidates = @{$polygon};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# find the point of the loop that is closest to the current extruder position
|
||||
# or randomize if requested
|
||||
my $last_pos = $self->last_pos;
|
||||
if ($self->print_config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) {
|
||||
$last_pos = Slic3r::Point->new(scale $self->print_config->print_center->[X], scale $self->print_config->bed_size->[Y]);
|
||||
$last_pos->rotate(rand(2*PI), $self->print_config->print_center);
|
||||
if ($self->config->spiral_vase) {
|
||||
$loop->split_at($last_pos);
|
||||
} elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') {
|
||||
my $polygon = $loop->polygon;
|
||||
my @candidates = @{$polygon->concave_points(PI*4/3)};
|
||||
@candidates = @{$polygon->convex_points(PI*2/3)} if !@candidates;
|
||||
@candidates = @{$polygon} if !@candidates;
|
||||
|
||||
my @non_overhang = grep !$loop->has_overhang_point($_), @candidates;
|
||||
@candidates = @non_overhang if @non_overhang;
|
||||
|
||||
if ($self->config->seam_position eq 'nearest') {
|
||||
$loop->split_at_vertex($last_pos->nearest_point(\@candidates));
|
||||
} elsif ($self->config->seam_position eq 'aligned') {
|
||||
my $obj_ptr = $self->layer->object->ptr;
|
||||
if (defined $self->layer && defined $self->_seam_position->{$obj_ptr}) {
|
||||
$last_pos = $self->_seam_position->{$obj_ptr};
|
||||
}
|
||||
my $point = $self->_seam_position->{$obj_ptr} = $last_pos->nearest_point(\@candidates);
|
||||
$loop->split_at_vertex($point);
|
||||
}
|
||||
} elsif ($self->config->seam_position eq 'random') {
|
||||
if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
|
||||
my $polygon = $loop->polygon;
|
||||
my $centroid = $polygon->centroid;
|
||||
$last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #))
|
||||
$last_pos->rotate(rand(2*PI), $centroid);
|
||||
}
|
||||
$loop->split_at($last_pos);
|
||||
}
|
||||
|
||||
# split the loop at the starting point and make a path
|
||||
my $start_at = $last_pos->nearest_point(\@candidates);
|
||||
my $extrusion_path = $loop->split_at($start_at);
|
||||
|
||||
# clip the path to avoid the extruder to get exactly on the first point of the loop;
|
||||
# if polyline was shorter than the clipping distance we'd get a null polyline, so
|
||||
# we discard it in that case
|
||||
$extrusion_path->clip_end(scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER)
|
||||
if $self->enable_loop_clipping;
|
||||
return '' if !@{$extrusion_path->polyline};
|
||||
my $clip_length = $self->enable_loop_clipping
|
||||
? scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
|
||||
: 0;
|
||||
|
||||
my @paths = ();
|
||||
# detect overhanging/bridging perimeters
|
||||
if ($self->layer->print->config->overhangs && $extrusion_path->is_perimeter && $self->_layer_overhangs->count > 0) {
|
||||
# get non-overhang paths by subtracting overhangs from the loop
|
||||
push @paths,
|
||||
map $_->clone,
|
||||
@{$extrusion_path->subtract_expolygons($self->_layer_overhangs)};
|
||||
|
||||
# get overhang paths by intersecting overhangs with the loop
|
||||
foreach my $path (@{$extrusion_path->intersect_expolygons($self->_layer_overhangs)}) {
|
||||
$path = $path->clone;
|
||||
$path->role(EXTR_ROLE_OVERHANG_PERIMETER);
|
||||
$path->mm3_per_mm($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1)->mm3_per_mm(-1));
|
||||
push @paths, $path;
|
||||
}
|
||||
|
||||
# reapply the nearest point search for starting point
|
||||
# (clone because the collection gets DESTROY'ed)
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths);
|
||||
@paths = map $_->clone, @{$collection->chained_path_from($start_at, 1)};
|
||||
} else {
|
||||
push @paths, $extrusion_path;
|
||||
}
|
||||
# get paths
|
||||
my @paths = @{$loop->clip_end($clip_length)};
|
||||
return '' if !@paths;
|
||||
|
||||
# apply the small perimeter speed
|
||||
my %params = ();
|
||||
if ($extrusion_path->is_perimeter && abs($extrusion_path->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) {
|
||||
$params{speed} = 'small_perimeter';
|
||||
if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) {
|
||||
$speed //= $self->config->get_abs_value('small_perimeter_speed');
|
||||
}
|
||||
$speed //= -1;
|
||||
|
||||
# extrude along the path
|
||||
my $gcode = join '', map $self->extrude_path($_, $description, %params), @paths;
|
||||
$self->wipe_path($extrusion_path->polyline->clone) if $self->enable_wipe;
|
||||
my $gcode = join '', map $self->extrude_path($_, $description, $speed), @paths;
|
||||
$self->wipe_path($paths[-1]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path
|
||||
|
||||
# make a little move inwards before leaving loop
|
||||
if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) {
|
||||
if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) {
|
||||
my $last_path_polyline = $paths[-1]->polyline;
|
||||
# detect angle between last and first segment
|
||||
# the side depends on the original winding order of the polygon (left for contours, right for holes)
|
||||
my @points = $was_clockwise ? (-2, 1) : (1, -2);
|
||||
my $angle = Slic3r::Geometry::angle3points(@{$extrusion_path->polyline}[0, @points]) / 3;
|
||||
my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
|
||||
$angle *= -1 if $was_clockwise;
|
||||
|
||||
# create the destination point along the first segment and rotate it
|
||||
# we make sure we don't exceed the segment length because we don't know
|
||||
# the rotation of the second segment so we might cross the object boundary
|
||||
my $first_segment = Slic3r::Line->new(@{$extrusion_path->polyline}[0,1]);
|
||||
my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]);
|
||||
my $distance = min(scale($self->extruder->nozzle_diameter), $first_segment->length);
|
||||
my $point = $first_segment->point_at($distance);
|
||||
$point->rotate($angle, $extrusion_path->first_point);
|
||||
$point->rotate($angle, $last_path_polyline->first_point);
|
||||
|
||||
# generate the travel move
|
||||
$gcode .= $self->travel_to($point, $loop->role, "move inwards before travel");
|
||||
$gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel");
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub extrude_path {
|
||||
my ($self, $path, $description, %params) = @_;
|
||||
my ($self, $path, $description, $speed) = @_;
|
||||
|
||||
$path->simplify(&Slic3r::SCALED_RESOLUTION);
|
||||
|
||||
|
@ -316,56 +249,57 @@ sub extrude_path {
|
|||
|
||||
# adjust acceleration
|
||||
my $acceleration;
|
||||
if (!$self->print_config->first_layer_acceleration || $self->layer->id != 0) {
|
||||
if ($self->print_config->perimeter_acceleration && $path->is_perimeter) {
|
||||
$acceleration = $self->print_config->perimeter_acceleration;
|
||||
} elsif ($self->print_config->infill_acceleration && $path->is_fill) {
|
||||
$acceleration = $self->print_config->infill_acceleration;
|
||||
} elsif ($self->print_config->infill_acceleration && $path->is_bridge) {
|
||||
$acceleration = $self->print_config->bridge_acceleration;
|
||||
if (!$self->config->first_layer_acceleration || $self->layer->id != 0) {
|
||||
if ($self->config->perimeter_acceleration && $path->is_perimeter) {
|
||||
$acceleration = $self->config->perimeter_acceleration;
|
||||
} elsif ($self->config->infill_acceleration && $path->is_fill) {
|
||||
$acceleration = $self->config->infill_acceleration;
|
||||
} elsif ($self->config->infill_acceleration && $path->is_bridge) {
|
||||
$acceleration = $self->config->bridge_acceleration;
|
||||
}
|
||||
$gcode .= $self->set_acceleration($acceleration) if $acceleration;
|
||||
}
|
||||
|
||||
# calculate extrusion length per distance unit
|
||||
my $e = $self->extruder->e_per_mm3 * $path->mm3_per_mm;
|
||||
$e = 0 if !$self->_extrusion_axis;
|
||||
$e = 0 if !$self->config->get_extrusion_axis;
|
||||
|
||||
# set speed
|
||||
$self->speed( $params{speed} || $role_speeds{$path->role} || die "Unknown role: " . $path->role );
|
||||
my $F = $self->speeds->{$self->speed} // $self->speed;
|
||||
my $F;
|
||||
if ($path->role == EXTR_ROLE_PERIMETER) {
|
||||
$F = $self->config->get_abs_value('perimeter_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) {
|
||||
$F = $self->config->get_abs_value('external_perimeter_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) {
|
||||
$F = $self->config->get_abs_value('bridge_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_FILL) {
|
||||
$F = $self->config->get_abs_value('infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_SOLIDFILL) {
|
||||
$F = $self->config->get_abs_value('solid_infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) {
|
||||
$F = $self->config->get_abs_value('top_solid_infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_GAPFILL) {
|
||||
$F = $self->config->get_abs_value('gap_fill_speed');
|
||||
} else {
|
||||
$F = $speed // -1;
|
||||
die "Invalid speed" if $F < 0; # $speed == -1
|
||||
}
|
||||
$F *= 60; # convert mm/sec to mm/min
|
||||
|
||||
if ($self->layer->id == 0) {
|
||||
$F = $self->print_config->get_abs_value_over('first_layer_speed', $F/60) * 60;
|
||||
$F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60;
|
||||
}
|
||||
|
||||
# extrude arc or line
|
||||
$gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge;
|
||||
my $path_length = 0;
|
||||
my $path_length = unscale $path->length;
|
||||
{
|
||||
my $local_F = $F;
|
||||
foreach my $line (@{$path->lines}) {
|
||||
$path_length += my $line_length = unscale $line->length;
|
||||
|
||||
# calculate extrusion length for this line
|
||||
my $E = 0;
|
||||
$E = $self->extruder->extrude($e * $line_length) if $e;
|
||||
|
||||
# compose G-code line
|
||||
my $point = $line->b;
|
||||
$gcode .= sprintf "G1 X%.3f Y%.3f",
|
||||
($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X],
|
||||
($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #**
|
||||
$gcode .= sprintf(" %s%.5f", $self->_extrusion_axis, $E)
|
||||
if $E;
|
||||
$gcode .= " F$local_F"
|
||||
if $local_F;
|
||||
$gcode .= " ; $description"
|
||||
if $self->print_config->gcode_comments;
|
||||
$gcode .= "\n";
|
||||
|
||||
# only include F in the first line
|
||||
$local_F = 0;
|
||||
}
|
||||
$gcode .= $path->gcode($self->extruder, $e, $F,
|
||||
$self->shift_x - $self->extruder->extruder_offset->x,
|
||||
$self->shift_y - $self->extruder->extruder_offset->y, #,,
|
||||
$self->config->get_extrusion_axis,
|
||||
$self->config->gcode_comments ? " ; $description" : "");
|
||||
|
||||
if ($self->enable_wipe) {
|
||||
$self->wipe_path($path->polyline->clone);
|
||||
$self->wipe_path->reverse;
|
||||
|
@ -374,14 +308,14 @@ sub extrude_path {
|
|||
$gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge;
|
||||
$self->last_pos($path->last_point);
|
||||
|
||||
if ($self->print_config->cooling) {
|
||||
if ($self->config->cooling) {
|
||||
my $path_time = $path_length / $F * 60;
|
||||
$self->elapsed_time($self->elapsed_time + $path_time);
|
||||
}
|
||||
|
||||
# reset acceleration
|
||||
$gcode .= $self->set_acceleration($self->print_config->default_acceleration)
|
||||
if $acceleration && $self->print_config->default_acceleration;
|
||||
$gcode .= $self->set_acceleration($self->config->default_acceleration)
|
||||
if $acceleration && $self->config->default_acceleration;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
@ -400,19 +334,17 @@ sub travel_to {
|
|||
# skip retraction if the travel move is contained in an island in the current layer
|
||||
# *and* in an island in the upper layer (so that the ooze will not be visible)
|
||||
if ($travel->length < scale $self->extruder->retract_before_travel
|
||||
|| ($self->print_config->only_retract_when_crossing_perimeters
|
||||
|| ($self->config->only_retract_when_crossing_perimeters
|
||||
&& (first { $_->contains_line($travel) } @{$self->_upper_layer_islands})
|
||||
&& (first { $_->contains_line($travel) } @{$self->_layer_islands}))
|
||||
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && (first { $_->contains_line($travel) } @{$self->layer->support_islands}))
|
||||
) {
|
||||
$self->straight_once(0);
|
||||
$self->speed('travel');
|
||||
$gcode .= $self->G0($point, undef, 0, $comment || "");
|
||||
} elsif (!$self->print_config->avoid_crossing_perimeters || $self->straight_once) {
|
||||
$gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || "");
|
||||
} elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) {
|
||||
$self->straight_once(0);
|
||||
$gcode .= $self->retract;
|
||||
$self->speed('travel');
|
||||
$gcode .= $self->G0($point, undef, 0, $comment || "");
|
||||
$gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || "");
|
||||
} else {
|
||||
if ($self->new_object) {
|
||||
$self->new_object(0);
|
||||
|
@ -441,7 +373,7 @@ sub _plan {
|
|||
my @travel = @{$mp->shortest_path($self->last_pos, $point)->lines};
|
||||
|
||||
# if the path is not contained in a single island we need to retract
|
||||
my $need_retract = !$self->print_config->only_retract_when_crossing_perimeters;
|
||||
my $need_retract = !$self->config->only_retract_when_crossing_perimeters;
|
||||
if (!$need_retract) {
|
||||
$need_retract = 1;
|
||||
foreach my $island (@{$self->_upper_layer_islands}) {
|
||||
|
@ -457,9 +389,8 @@ sub _plan {
|
|||
$gcode .= $self->retract if $need_retract;
|
||||
|
||||
# append the actual path and return
|
||||
$self->speed('travel');
|
||||
# use G1 because we rely on paths being straight (G0 may make round paths)
|
||||
$gcode .= join '', map $self->G1($_->b, undef, 0, $comment || ""), @travel;
|
||||
$gcode .= join '', map $self->G1($_->b, undef, 0, $self->config->travel_speed*60, $comment || ""), @travel;
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
|
@ -481,60 +412,55 @@ sub retract {
|
|||
if ($self->extruder->wipe && $self->wipe_path) {
|
||||
my @points = @{$self->wipe_path};
|
||||
$wipe_path = Slic3r::Polyline->new($self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}]);
|
||||
$wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->print_config->travel_speed));
|
||||
$wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->config->travel_speed));
|
||||
}
|
||||
|
||||
# prepare moves
|
||||
my $retract = [undef, undef, -$length, $comment];
|
||||
my $lift = ($self->_retract_lift == 0 || defined $params{move_z}) && !$self->lifted
|
||||
my $retract = [undef, undef, -$length, $self->extruder->retract_speed_mm_min, $comment];
|
||||
my $lift = ($self->config->retract_lift->[0] == 0 || defined $params{move_z}) && !$self->lifted
|
||||
? undef
|
||||
: [undef, $self->z + $self->_retract_lift, 0, 'lift plate during travel'];
|
||||
: [undef, $self->z + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'lift plate during travel'];
|
||||
|
||||
# check that we have a positive wipe length
|
||||
if ($wipe_path) {
|
||||
$self->speed($self->speeds->{travel} * 0.8);
|
||||
|
||||
# subdivide the retraction
|
||||
my $retracted = 0;
|
||||
foreach my $line (@{$wipe_path->lines}) {
|
||||
my $segment_length = $line->length;
|
||||
# reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
|
||||
# due to rounding
|
||||
my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->print_config->travel_speed)) * 0.95;
|
||||
my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->config->travel_speed)) * 0.95;
|
||||
$retracted += $e;
|
||||
$gcode .= $self->G1($line->b, undef, $e, $retract->[3] . ";_WIPE");
|
||||
$gcode .= $self->G1($line->b, undef, $e, $self->config->travel_speed*60*0.8, $retract->[3] . ";_WIPE");
|
||||
}
|
||||
if ($retracted > $retract->[2]) {
|
||||
# if we retracted less than we had to, retract the remainder
|
||||
# TODO: add regression test
|
||||
$self->speed('retract');
|
||||
$gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $comment);
|
||||
$gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $self->extruder->retract_speed_mm_min, $comment);
|
||||
}
|
||||
} elsif ($self->print_config->use_firmware_retraction) {
|
||||
} elsif ($self->config->use_firmware_retraction) {
|
||||
$gcode .= "G10 ; retract\n";
|
||||
} else {
|
||||
$self->speed('retract');
|
||||
$gcode .= $self->G1(@$retract);
|
||||
}
|
||||
if (!$self->lifted) {
|
||||
$self->speed('travel');
|
||||
if (defined $params{move_z} && $self->_retract_lift > 0) {
|
||||
my $travel = [undef, $params{move_z} + $self->_retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift'];
|
||||
if (defined $params{move_z} && $self->config->retract_lift->[0] > 0) {
|
||||
my $travel = [undef, $params{move_z} + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'move to next layer (' . $self->layer->id . ') and lift'];
|
||||
$gcode .= $self->G0(@$travel);
|
||||
$self->lifted($self->_retract_lift);
|
||||
$self->lifted($self->config->retract_lift->[0]);
|
||||
} elsif ($lift) {
|
||||
$gcode .= $self->G1(@$lift);
|
||||
}
|
||||
}
|
||||
$self->extruder->retracted($self->extruder->retracted + $length);
|
||||
$self->extruder->restart_extra($restart_extra);
|
||||
$self->lifted($self->_retract_lift) if $lift;
|
||||
$self->extruder->set_retracted($self->extruder->retracted + $length);
|
||||
$self->extruder->set_restart_extra($restart_extra);
|
||||
$self->lifted($self->config->retract_lift->[0]) if $lift;
|
||||
|
||||
# reset extrusion distance during retracts
|
||||
# this makes sure we leave sufficient precision in the firmware
|
||||
$gcode .= $self->reset_e;
|
||||
|
||||
$gcode .= "M103 ; extruder off\n" if $self->print_config->gcode_flavor eq 'makerware';
|
||||
$gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware';
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
@ -543,30 +469,28 @@ sub unretract {
|
|||
my ($self) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= "M101 ; extruder on\n" if $self->print_config->gcode_flavor eq 'makerware';
|
||||
$gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware';
|
||||
|
||||
if ($self->lifted) {
|
||||
$self->speed('travel');
|
||||
$gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z');
|
||||
$gcode .= $self->G0(undef, $self->z - $self->lifted, 0, $self->config->travel_speed*60, 'restore layer Z');
|
||||
$self->lifted(0);
|
||||
}
|
||||
|
||||
my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra;
|
||||
if ($to_unretract) {
|
||||
$self->speed('retract');
|
||||
if ($self->print_config->use_firmware_retraction) {
|
||||
if ($self->config->use_firmware_retraction) {
|
||||
$gcode .= "G11 ; unretract\n";
|
||||
} elsif ($self->_extrusion_axis) {
|
||||
} elsif ($self->config->get_extrusion_axis) {
|
||||
# use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
$gcode .= sprintf "G1 %s%.5f F%.3f",
|
||||
$self->_extrusion_axis,
|
||||
$self->config->get_extrusion_axis,
|
||||
$self->extruder->extrude($to_unretract),
|
||||
$self->extruder->retract_speed_mm_min;
|
||||
$gcode .= " ; compensate retraction" if $self->print_config->gcode_comments;
|
||||
$gcode .= " ; compensate retraction" if $self->config->gcode_comments;
|
||||
$gcode .= "\n";
|
||||
}
|
||||
$self->extruder->retracted(0);
|
||||
$self->extruder->restart_extra(0);
|
||||
$self->extruder->set_retracted(0);
|
||||
$self->extruder->set_restart_extra(0);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
|
@ -574,11 +498,11 @@ sub unretract {
|
|||
|
||||
sub reset_e {
|
||||
my ($self) = @_;
|
||||
return "" if $self->print_config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/;
|
||||
return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/;
|
||||
|
||||
$self->extruder->E(0) if $self->extruder;
|
||||
return sprintf "G92 %s0%s\n", $self->_extrusion_axis, ($self->print_config->gcode_comments ? ' ; reset extrusion distance' : '')
|
||||
if $self->_extrusion_axis && !$self->print_config->use_relative_e_distances;
|
||||
$self->extruder->set_E(0) if $self->extruder;
|
||||
return sprintf "G92 %s0%s\n", $self->config->get_extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '')
|
||||
if $self->config->get_extrusion_axis && !$self->config->use_relative_e_distances;
|
||||
}
|
||||
|
||||
sub set_acceleration {
|
||||
|
@ -586,12 +510,12 @@ sub set_acceleration {
|
|||
return "" if !$acceleration;
|
||||
|
||||
return sprintf "M204 S%s%s\n",
|
||||
$acceleration, ($self->print_config->gcode_comments ? ' ; adjust acceleration' : '');
|
||||
$acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : '');
|
||||
}
|
||||
|
||||
sub G0 {
|
||||
my $self = shift;
|
||||
return $self->G1(@_) if !($self->print_config->g0 || $self->print_config->gcode_flavor eq 'mach3');
|
||||
return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3');
|
||||
return $self->_G0_G1("G0", @_);
|
||||
}
|
||||
|
||||
|
@ -601,12 +525,12 @@ sub G1 {
|
|||
}
|
||||
|
||||
sub _G0_G1 {
|
||||
my ($self, $gcode, $point, $z, $e, $comment) = @_;
|
||||
my ($self, $gcode, $point, $z, $e, $F, $comment) = @_;
|
||||
|
||||
if ($point) {
|
||||
$gcode .= sprintf " X%.3f Y%.3f",
|
||||
($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X],
|
||||
($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #**
|
||||
($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->x,
|
||||
($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->y; #**
|
||||
$self->last_pos($point->clone);
|
||||
}
|
||||
if (defined $z && (!defined $self->z || $z != $self->z)) {
|
||||
|
@ -614,25 +538,20 @@ sub _G0_G1 {
|
|||
$gcode .= sprintf " Z%.3f", $z;
|
||||
}
|
||||
|
||||
return $self->_Gx($gcode, $e, $comment);
|
||||
return $self->_Gx($gcode, $e, $F, $comment);
|
||||
}
|
||||
|
||||
sub _Gx {
|
||||
my ($self, $gcode, $e, $comment) = @_;
|
||||
my ($self, $gcode, $e, $F, $comment) = @_;
|
||||
|
||||
my $F = $self->speed eq 'retract'
|
||||
? ($self->extruder->retract_speed_mm_min)
|
||||
: $self->speeds->{$self->speed} // $self->speed;
|
||||
$self->last_speed($self->speed);
|
||||
$self->last_f($F);
|
||||
$gcode .= sprintf " F%.3f", $F;
|
||||
|
||||
# output extrusion distance
|
||||
if ($e && $self->_extrusion_axis) {
|
||||
$gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $self->extruder->extrude($e);
|
||||
if ($e && $self->config->get_extrusion_axis) {
|
||||
$gcode .= sprintf " %s%.5f", $self->config->get_extrusion_axis, $self->extruder->extrude($e);
|
||||
}
|
||||
|
||||
$gcode .= " ; $comment" if $comment && $self->print_config->gcode_comments;
|
||||
$gcode .= " ; $comment" if $comment && $self->config->gcode_comments;
|
||||
return "$gcode\n";
|
||||
}
|
||||
|
||||
|
@ -653,8 +572,8 @@ sub set_extruder {
|
|||
$gcode .= $self->retract(toolchange => 1) if defined $self->extruder;
|
||||
|
||||
# append custom toolchange G-code
|
||||
if (defined $self->extruder && $self->print_config->toolchange_gcode) {
|
||||
$gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->print_config->toolchange_gcode, {
|
||||
if (defined $self->extruder && $self->config->toolchange_gcode) {
|
||||
$gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, {
|
||||
previous_extruder => $self->extruder->id,
|
||||
next_extruder => $extruder_id,
|
||||
});
|
||||
|
@ -663,30 +582,38 @@ sub set_extruder {
|
|||
# set the current extruder to the standby temperature
|
||||
if ($self->standby_points && defined $self->extruder) {
|
||||
# move to the nearest standby point
|
||||
$gcode .= $self->travel_to($self->last_pos->nearest_point($self->standby_points));
|
||||
{
|
||||
my $last_pos = $self->last_pos->clone;
|
||||
$last_pos->translate(scale +$self->shift_x, scale +$self->shift_y);
|
||||
my $standby_point = $last_pos->nearest_point($self->standby_points);
|
||||
$standby_point->translate(scale -$self->shift_x, scale -$self->shift_y);
|
||||
$gcode .= $self->travel_to($standby_point);
|
||||
}
|
||||
|
||||
my $temp = defined $self->layer && $self->layer->id == 0
|
||||
? $self->extruder->first_layer_temperature
|
||||
: $self->extruder->temperature;
|
||||
# we assume that heating is always slower than cooling, so no need to block
|
||||
$gcode .= $self->set_temperature($temp + $self->print_config->standby_temperature_delta, 0);
|
||||
if ($self->config->standby_temperature_delta != 0) {
|
||||
my $temp = defined $self->layer && $self->layer->id == 0
|
||||
? $self->extruder->first_layer_temperature
|
||||
: $self->extruder->temperature;
|
||||
# we assume that heating is always slower than cooling, so no need to block
|
||||
$gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0);
|
||||
}
|
||||
}
|
||||
|
||||
# set the new extruder
|
||||
$self->extruder($self->extruders->{$extruder_id});
|
||||
$gcode .= sprintf "%s%d%s\n",
|
||||
($self->print_config->gcode_flavor eq 'makerware'
|
||||
($self->config->gcode_flavor eq 'makerware'
|
||||
? 'M135 T'
|
||||
: $self->print_config->gcode_flavor eq 'sailfish'
|
||||
? 'M108 T'
|
||||
: $self->config->gcode_flavor eq 'sailfish'
|
||||
? 'M135 T' # M108 T
|
||||
: 'T'),
|
||||
$extruder_id,
|
||||
($self->print_config->gcode_comments ? ' ; change extruder' : '');
|
||||
($self->config->gcode_comments ? ' ; change extruder' : '');
|
||||
|
||||
$gcode .= $self->reset_e;
|
||||
|
||||
# set the new extruder to the operating temperature
|
||||
if ($self->print_config->ooze_prevention) {
|
||||
if ($self->config->ooze_prevention && $self->config->standby_temperature_delta != 0) {
|
||||
my $temp = defined $self->layer && $self->layer->id == 0
|
||||
? $self->extruder->first_layer_temperature
|
||||
: $self->extruder->temperature;
|
||||
|
@ -702,18 +629,18 @@ sub set_fan {
|
|||
if ($self->last_fan_speed != $speed || $dont_save) {
|
||||
$self->last_fan_speed($speed) if !$dont_save;
|
||||
if ($speed == 0) {
|
||||
my $code = $self->print_config->gcode_flavor eq 'teacup'
|
||||
my $code = $self->config->gcode_flavor eq 'teacup'
|
||||
? 'M106 S0'
|
||||
: $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/
|
||||
: $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/
|
||||
? 'M127'
|
||||
: 'M107';
|
||||
return sprintf "$code%s\n", ($self->print_config->gcode_comments ? ' ; disable fan' : '');
|
||||
return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : '');
|
||||
} else {
|
||||
if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
return sprintf "M126%s\n", ($self->print_config->gcode_comments ? ' ; enable fan' : '');
|
||||
if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : '');
|
||||
} else {
|
||||
return sprintf "M106 %s%d%s\n", ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
|
||||
(255 * $speed / 100), ($self->print_config->gcode_comments ? ' ; enable fan' : '');
|
||||
return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
|
||||
(255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -723,17 +650,17 @@ sub set_fan {
|
|||
sub set_temperature {
|
||||
my ($self, $temperature, $wait, $tool) = @_;
|
||||
|
||||
return "" if $wait && $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/;
|
||||
return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/;
|
||||
|
||||
my ($code, $comment) = ($wait && $self->print_config->gcode_flavor ne 'teacup')
|
||||
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
|
||||
? ('M109', 'wait for temperature to be reached')
|
||||
: ('M104', 'set temperature');
|
||||
my $gcode = sprintf "$code %s%d %s; $comment\n",
|
||||
($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
|
||||
(defined $tool && ($self->multiple_extruders || $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : "";
|
||||
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
|
||||
(defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : "";
|
||||
|
||||
$gcode .= "M116 ; wait for temperature to be reached\n"
|
||||
if $self->print_config->gcode_flavor eq 'teacup' && $wait;
|
||||
if $self->config->gcode_flavor eq 'teacup' && $wait;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
@ -741,14 +668,14 @@ sub set_temperature {
|
|||
sub set_bed_temperature {
|
||||
my ($self, $temperature, $wait) = @_;
|
||||
|
||||
my ($code, $comment) = ($wait && $self->print_config->gcode_flavor ne 'teacup')
|
||||
? (($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
|
||||
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
|
||||
? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
|
||||
: ('M140', 'set bed temperature');
|
||||
my $gcode = sprintf "$code %s%d ; $comment\n",
|
||||
($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
|
||||
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
|
||||
|
||||
$gcode .= "M116 ; wait for bed temperature to be reached\n"
|
||||
if $self->print_config->gcode_flavor eq 'teacup' && $wait;
|
||||
if $self->config->gcode_flavor eq 'teacup' && $wait;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
|
|
@ -1,133 +1,242 @@
|
|||
package Slic3r::GCode::ArcFitting;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::Geometry qw(X Y PI scale unscale deg2rad);
|
||||
use Slic3r::Geometry qw(X Y PI scale unscale epsilon scaled_epsilon deg2rad angle3points);
|
||||
|
||||
extends 'Slic3r::GCode::Reader';
|
||||
has 'config' => (is => 'ro', required => 1);
|
||||
has 'max_angle' => (is => 'rw', default => sub { deg2rad(15) });
|
||||
has 'len_epsilon' => (is => 'rw', default => sub { scale 10 });
|
||||
has 'parallel_degrees_limit' => (is => 'rw', default => sub { abs(deg2rad(3)) });
|
||||
has 'config' => (is => 'ro', required => 0);
|
||||
has 'min_segments' => (is => 'rw', default => sub { 2 });
|
||||
has 'min_total_angle' => (is => 'rw', default => sub { deg2rad(30) });
|
||||
has 'max_relative_angle' => (is => 'rw', default => sub { deg2rad(15) });
|
||||
has 'len_epsilon' => (is => 'rw', default => sub { scale 0.2 });
|
||||
has 'angle_epsilon' => (is => 'rw', default => sub { abs(deg2rad(10)) });
|
||||
has '_extrusion_axis' => (is => 'lazy');
|
||||
has '_path' => (is => 'rw');
|
||||
has '_cur_F' => (is => 'rw');
|
||||
has '_cur_E' => (is => 'rw');
|
||||
has '_cur_E0' => (is => 'rw');
|
||||
has '_comment' => (is => 'rw');
|
||||
|
||||
sub _build__extrusion_axis {
|
||||
my ($self) = @_;
|
||||
return $self->config ? $self->config->get_extrusion_axis : 'E';
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my ($gcode) = @_;
|
||||
|
||||
my $new_gcode = "";
|
||||
my $buffer = "";
|
||||
my @cur_path = ();
|
||||
my $cur_len = 0;
|
||||
my $cur_relative_angle = 0;
|
||||
die "Arc fitting is not available (incomplete feature)\n";
|
||||
die "Arc fitting doesn't support extrusion axis not being E\n" if $self->_extrusion_axis ne 'E';
|
||||
|
||||
my $new_gcode = "";
|
||||
|
||||
$self->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
||||
# this is an extrusion segment
|
||||
|
||||
if (@cur_path >= 2) {
|
||||
if ($cur_path[-1]->distance_to($point) > $self->len_epsilon) {
|
||||
# if the last distance is not compatible with the current arc, flush it
|
||||
$new_gcode .= $self->flush_path(\@cur_path, \$buffer);
|
||||
} elsif (@cur_path >= 3) {
|
||||
my $rel_angle = relative_angle(@cur_path[-2,-1], $point);
|
||||
if (($cur_relative_angle != 0 && abs($rel_angle - $cur_relative_angle) > $self->parallel_degrees_limit) # relative angle is too different from the previous one
|
||||
|| abs($rel_angle) < $self->parallel_degrees_limit # relative angle is almost parallel
|
||||
|| $rel_angle > $self->max_angle) { # relative angle is excessive (too sharp)
|
||||
# in these cases, $point does not really look like an additional point of the current arc
|
||||
$new_gcode .= $self->flush_path(\@cur_path, \$buffer);
|
||||
}
|
||||
}
|
||||
# get segment
|
||||
my $line = Slic3r::Line->new(
|
||||
Slic3r::Point->new_scale($self->X, $self->Y),
|
||||
Slic3r::Point->new_scale($args->{X}, $args->{Y}),
|
||||
);
|
||||
|
||||
# get segment speed
|
||||
my $F = $args->{F} // $reader->F;
|
||||
|
||||
# get extrusion per unscaled distance unit
|
||||
my $e = $info->{dist_E} / unscale($line->length);
|
||||
|
||||
if ($self->_path && $F == $self->_cur_F && abs($e - $self->_cur_E) < epsilon) {
|
||||
# if speed and extrusion per unit are the same as the previous segments,
|
||||
# append this segment to path
|
||||
$self->_path->append($line->b);
|
||||
} elsif ($self->_path) {
|
||||
# segment can't be appended to previous path, so we flush the previous one
|
||||
# and start over
|
||||
$new_gcode .= $self->path_to_gcode;
|
||||
$self->_path(undef);
|
||||
}
|
||||
|
||||
if (@cur_path == 0) {
|
||||
# we're starting a path, so let's prepend the previous position
|
||||
push @cur_path, Slic3r::Point->new_scale($self->X, $self->Y), $point;
|
||||
$buffer .= $info->{raw} . "\n";
|
||||
$cur_len = $cur_path[0]->distance_to($cur_path[1]);
|
||||
} else {
|
||||
push @cur_path, $point;
|
||||
$buffer .= $info->{raw} . "\n";
|
||||
if (@cur_path == 3) {
|
||||
# we have two segments, time to compute a reference angle
|
||||
$cur_relative_angle = relative_angle(@cur_path[0,1,2]);
|
||||
}
|
||||
if (!$self->_path) {
|
||||
# if this is the first segment of a path, start it from scratch
|
||||
$self->_path(Slic3r::Polyline->new(@$line));
|
||||
$self->_cur_F($F);
|
||||
$self->_cur_E($e);
|
||||
$self->_cur_E0($self->E);
|
||||
$self->_comment($info->{comment});
|
||||
}
|
||||
} else {
|
||||
$new_gcode .= $self->flush_path(\@cur_path, \$buffer);
|
||||
# if we have a path, we flush it and go on
|
||||
$new_gcode .= $self->path_to_gcode if $self->_path;
|
||||
$new_gcode .= $info->{raw} . "\n";
|
||||
$self->_path(undef);
|
||||
}
|
||||
});
|
||||
|
||||
$new_gcode .= $self->flush_path(\@cur_path, \$buffer);
|
||||
$new_gcode .= $self->path_to_gcode if $self->_path;
|
||||
return $new_gcode;
|
||||
}
|
||||
|
||||
sub flush_path {
|
||||
my ($self, $cur_path, $buffer) = @_;
|
||||
sub path_to_gcode {
|
||||
my ($self) = @_;
|
||||
|
||||
my @chunks = $self->detect_arcs($self->_path);
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
if (@$cur_path >= 3) {
|
||||
# if we have enough points, then we have an arc
|
||||
$$buffer =~ s/^/;/mg;
|
||||
$gcode = "; these moves were replaced by an arc:\n" . $$buffer;
|
||||
|
||||
my $orientation = $cur_path->[2]->ccw(@$cur_path[0,1]) ? 'ccw' : 'cw';
|
||||
|
||||
# to find the center, we intersect the perpendicular lines
|
||||
# passing by midpoints of $s1 and last segment
|
||||
# a better method would be to draw all the perpendicular lines
|
||||
# and find the centroid of the enclosed polygon, or to
|
||||
# intersect multiple lines and find the centroid of the convex hull
|
||||
# around the intersections
|
||||
my $arc_center;
|
||||
{
|
||||
my $s1_mid = Slic3r::Line->new(@$cur_path[0,1])->midpoint;
|
||||
my $last_mid = Slic3r::Line->new(@$cur_path[-2,-1])->midpoint;
|
||||
my $rotation_angle = PI/2 * ($orientation eq 'ccw' ? -1 : 1);
|
||||
my $ray1 = Slic3r::Line->new($s1_mid, $cur_path->[1]->clone->rotate($rotation_angle, $s1_mid));
|
||||
my $last_ray = Slic3r::Line->new($last_mid, $cur_path->[-1]->clone->rotate($rotation_angle, $last_mid));
|
||||
$arc_center = $ray1->intersection($last_ray, 0) or next POINT;
|
||||
my $E = $self->_cur_E0;
|
||||
foreach my $chunk (@chunks) {
|
||||
if ($chunk->isa('Slic3r::Polyline')) {
|
||||
my @lines = @{$chunk->lines};
|
||||
|
||||
$gcode .= sprintf "G1 F%s\n", $self->_cur_F;
|
||||
foreach my $line (@lines) {
|
||||
$E += $self->_cur_E * unscale($line->length);
|
||||
$gcode .= sprintf "G1 X%.3f Y%.3f %s%.5f",
|
||||
(map unscale($_), @{$line->b}),
|
||||
$self->_extrusion_axis, $E;
|
||||
$gcode .= sprintf " ; %s", $self->_comment if $self->_comment;
|
||||
$gcode .= "\n";
|
||||
}
|
||||
} elsif ($chunk->isa('Slic3r::GCode::ArcFitting::Arc')) {
|
||||
$gcode .= !$chunk->is_ccw ? "G2" : "G3";
|
||||
$gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$chunk->end}; # destination point
|
||||
|
||||
# XY distance of the center from the start position
|
||||
$gcode .= sprintf " I%.3f", unscale($chunk->center->[X] - $chunk->start->[X]);
|
||||
$gcode .= sprintf " J%.3f", unscale($chunk->center->[Y] - $chunk->start->[Y]);
|
||||
|
||||
$E += $self->_cur_E * unscale($chunk->length);
|
||||
$gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $E;
|
||||
|
||||
$gcode .= sprintf " F%s\n", $self->_cur_F;
|
||||
}
|
||||
my $radius = $arc_center->distance_to($cur_path->[0]);
|
||||
my $total_angle = Slic3r::Geometry::angle3points($arc_center, @$cur_path[0,-1]);
|
||||
my $length = $orientation eq 'ccw'
|
||||
? $radius * $total_angle
|
||||
: $radius * (2*PI - $total_angle);
|
||||
|
||||
# compose G-code line
|
||||
$gcode .= $orientation eq 'cw' ? "G2" : "G3";
|
||||
$gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$cur_path->[-1]}; # destination point
|
||||
|
||||
# XY distance of the center from the start position
|
||||
$gcode .= sprintf " I%.3f J%.3f", map { unscale($arc_center->[$_] - $cur_path->[0][$_]) } (X,Y);
|
||||
|
||||
my $E = 0; # TODO: compute E using $length
|
||||
$gcode .= sprintf(" %s%.5f", $self->config->get_extrusion_axis, $E)
|
||||
if $E;
|
||||
|
||||
my $F = 0; # TODO: extract F from original moves
|
||||
$gcode .= " F$F\n";
|
||||
} else {
|
||||
$gcode = $$buffer;
|
||||
}
|
||||
|
||||
$$buffer = "";
|
||||
splice @$cur_path, 0, $#$cur_path; # keep last point as starting position for next path
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub relative_angle {
|
||||
my ($p1, $p2, $p3) = @_;
|
||||
sub detect_arcs {
|
||||
my ($self, $path) = @_;
|
||||
|
||||
my $s1 = Slic3r::Line->new($p1, $p2);
|
||||
my $s2 = Slic3r::Line->new($p2, $p3);
|
||||
my $s1_angle = $s1->atan;
|
||||
my $s2_angle = $s2->atan;
|
||||
$s1_angle += 2*PI if $s1_angle < 0;
|
||||
$s2_angle += 2*PI if $s2_angle < 0;
|
||||
return $s2_angle - $s1_angle;
|
||||
my @chunks = ();
|
||||
my @arc_points = ();
|
||||
my $polyline = undef;
|
||||
my $arc_start = undef;
|
||||
|
||||
my @points = @$path;
|
||||
for (my $i = 1; $i <= $#points; ++$i) {
|
||||
my $end = undef;
|
||||
|
||||
# we need at least three points to check whether they form an arc
|
||||
if ($i < $#points) {
|
||||
my $len = $points[$i-1]->distance_to($points[$i]);
|
||||
my $rel_angle = PI - angle3points(@points[$i, $i-1, $i+1]);
|
||||
if (abs($rel_angle) <= $self->max_relative_angle) {
|
||||
for (my $j = $i+1; $j <= $#points; ++$j) {
|
||||
# check whether @points[($i-1)..$j] form an arc
|
||||
last if abs($points[$j-1]->distance_to($points[$j]) - $len) > $self->len_epsilon;
|
||||
last if abs(PI - angle3points(@points[$j-1, $j-2, $j]) - $rel_angle) > $self->angle_epsilon;
|
||||
|
||||
$end = $j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $end && ($end - $i + 1) >= $self->min_segments) {
|
||||
my $arc = polyline_to_arc(Slic3r::Polyline->new(@points[($i-1)..$end]));
|
||||
|
||||
if (1||$arc->angle >= $self->min_total_angle) {
|
||||
push @chunks, $arc;
|
||||
|
||||
# continue scanning after arc points
|
||||
$i = $end;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# if last chunk was a polyline, append to it
|
||||
if (@chunks && $chunks[-1]->isa('Slic3r::Polyline')) {
|
||||
$chunks[-1]->append($points[$i]);
|
||||
} else {
|
||||
push @chunks, Slic3r::Polyline->new(@points[($i-1)..$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return @chunks;
|
||||
}
|
||||
|
||||
sub polyline_to_arc {
|
||||
my ($polyline) = @_;
|
||||
|
||||
my @points = @$polyline;
|
||||
|
||||
my $is_ccw = $points[2]->ccw(@points[0,1]) > 0;
|
||||
|
||||
# to find the center, we intersect the perpendicular lines
|
||||
# passing by first and last vertex;
|
||||
# a better method would be to draw all the perpendicular lines
|
||||
# and find the centroid of the enclosed polygon, or to
|
||||
# intersect multiple lines and find the centroid of the convex hull
|
||||
# around the intersections
|
||||
my $arc_center;
|
||||
{
|
||||
my $first_ray = Slic3r::Line->new(@points[0,1]);
|
||||
$first_ray->rotate(PI/2 * ($is_ccw ? 1 : -1), $points[0]);
|
||||
|
||||
my $last_ray = Slic3r::Line->new(@points[-2,-1]);
|
||||
$last_ray->rotate(PI/2 * ($is_ccw ? -1 : 1), $points[-1]);
|
||||
|
||||
# require non-parallel rays in order to compute an accurate center
|
||||
return if abs($first_ray->atan2_ - $last_ray->atan2_) < deg2rad(30);
|
||||
|
||||
$arc_center = $first_ray->intersection($last_ray, 0) or return;
|
||||
}
|
||||
|
||||
# angle measured in ccw orientation
|
||||
my $abs_angle = Slic3r::Geometry::angle3points($arc_center, @points[0,-1]);
|
||||
|
||||
my $rel_angle = $is_ccw
|
||||
? $abs_angle
|
||||
: (2*PI - $abs_angle);
|
||||
|
||||
my $arc = Slic3r::GCode::ArcFitting::Arc->new(
|
||||
start => $points[0]->clone,
|
||||
end => $points[-1]->clone,
|
||||
center => $arc_center,
|
||||
is_ccw => $is_ccw || 0,
|
||||
angle => $rel_angle,
|
||||
);
|
||||
|
||||
if (0) {
|
||||
printf "points = %d, path length = %f, arc angle = %f, arc length = %f\n",
|
||||
scalar(@points),
|
||||
unscale(Slic3r::Polyline->new(@points)->length),
|
||||
Slic3r::Geometry::rad2deg($rel_angle),
|
||||
unscale($arc->length);
|
||||
}
|
||||
|
||||
return $arc;
|
||||
}
|
||||
|
||||
package Slic3r::GCode::ArcFitting::Arc;
|
||||
use Moo;
|
||||
|
||||
has 'start' => (is => 'ro', required => 1);
|
||||
has 'end' => (is => 'ro', required => 1);
|
||||
has 'center' => (is => 'ro', required => 1);
|
||||
has 'is_ccw' => (is => 'ro', required => 1);
|
||||
has 'angle' => (is => 'ro', required => 1);
|
||||
|
||||
sub radius {
|
||||
my ($self) = @_;
|
||||
return $self->start->distance_to($self->center);
|
||||
}
|
||||
|
||||
sub length {
|
||||
my ($self) = @_;
|
||||
return $self->radius * $self->angle;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -46,6 +46,7 @@ sub process_layer {
|
|||
my $gcode = "";
|
||||
|
||||
my $object = $layer->object;
|
||||
$self->gcodegen->config->apply_object_config($object->config);
|
||||
|
||||
# check whether we're going to apply spiralvase logic
|
||||
if (defined $self->spiralvase) {
|
||||
|
@ -53,6 +54,8 @@ sub process_layer {
|
|||
($layer->id > 0 || $self->print->config->brim_width == 0)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
|
||||
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
|
||||
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -90,9 +93,10 @@ sub process_layer {
|
|||
# when printing layers > 0 ignore 'min_skirt_length' and
|
||||
# just use the 'skirts' setting; also just use the current extruder
|
||||
last if ($layer->id > 0) && ($i >= $self->print->config->skirts);
|
||||
$gcode .= $self->gcodegen->set_extruder(($i/@extruder_ids) % @extruder_ids)
|
||||
my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids];
|
||||
$gcode .= $self->gcodegen->set_extruder($extruder_id)
|
||||
if $layer->id == 0;
|
||||
$gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt');
|
||||
$gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt', $object->config->support_material_speed);
|
||||
}
|
||||
}
|
||||
$self->skirt_done->{$layer->print_z} = 1;
|
||||
|
@ -103,7 +107,8 @@ sub process_layer {
|
|||
if (!$self->brim_done) {
|
||||
$gcode .= $self->gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1);
|
||||
$self->gcodegen->set_shift(@{$self->shift});
|
||||
$gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim};
|
||||
$gcode .= $self->gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
|
||||
for @{$self->print->brim};
|
||||
$self->brim_done(1);
|
||||
$self->gcodegen->straight_once(1);
|
||||
}
|
||||
|
@ -119,14 +124,12 @@ sub process_layer {
|
|||
if ($layer->isa('Slic3r::Layer::Support')) {
|
||||
if ($layer->support_interface_fills->count > 0) {
|
||||
$gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1);
|
||||
my %params = (speed => $object->config->support_material_speed*60);
|
||||
$gcode .= $self->gcodegen->extrude_path($_, 'support material interface', %params)
|
||||
$gcode .= $self->gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed'))
|
||||
for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)};
|
||||
}
|
||||
if ($layer->support_fills->count > 0) {
|
||||
$gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1);
|
||||
my %params = (speed => $object->config->support_material_speed*60);
|
||||
$gcode .= $self->gcodegen->extrude_path($_, 'support material', %params)
|
||||
$gcode .= $self->gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed'))
|
||||
for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)};
|
||||
}
|
||||
}
|
||||
|
@ -142,47 +145,43 @@ sub process_layer {
|
|||
foreach my $region_id (@region_ids) {
|
||||
my $layerm = $layer->regions->[$region_id] or next;
|
||||
my $region = $self->print->regions->[$region_id];
|
||||
$self->gcodegen->region($region);
|
||||
$self->gcodegen->config->apply_region_config($region->config);
|
||||
|
||||
my @islands = ();
|
||||
if ($self->print->config->avoid_crossing_perimeters) {
|
||||
push @islands, { perimeters => [], fills => [] }
|
||||
for 1 .. (@{$layer->slices} || 1); # make sure we have at least one island hash to avoid failure of the -1 subscript below
|
||||
PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) {
|
||||
for my $i (0 .. $#{$layer->slices}-1) {
|
||||
if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
|
||||
push @{ $islands[$i]{perimeters} }, $perimeter;
|
||||
next PERIMETER;
|
||||
}
|
||||
# group extrusions by island
|
||||
my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters
|
||||
my @infill_by_island = map [], 0..$#{$layer->slices}; # slice idx => @fills
|
||||
|
||||
# NOTE: we assume $layer->slices was already ordered with chained_path()!
|
||||
|
||||
PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) {
|
||||
for my $i (0 .. $#{$layer->slices}-1) {
|
||||
if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
|
||||
push @{ $perimeters_by_island[$i] }, $perimeter;
|
||||
next PERIMETER;
|
||||
}
|
||||
push @{ $islands[-1]{perimeters} }, $perimeter; # optimization
|
||||
}
|
||||
FILL: foreach my $fill (@{$layerm->fills}) {
|
||||
for my $i (0 .. $#{$layer->slices}-1) {
|
||||
if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) {
|
||||
push @{ $islands[$i]{fills} }, $fill;
|
||||
next FILL;
|
||||
}
|
||||
push @{ $perimeters_by_island[-1] }, $perimeter; # optimization
|
||||
}
|
||||
FILL: foreach my $fill (@{$layerm->fills}) {
|
||||
for my $i (0 .. $#{$layer->slices}-1) {
|
||||
if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) {
|
||||
push @{ $infill_by_island[$i] }, $fill;
|
||||
next FILL;
|
||||
}
|
||||
push @{ $islands[-1]{fills} }, $fill; # optimization
|
||||
}
|
||||
} else {
|
||||
push @islands, {
|
||||
perimeters => $layerm->perimeters,
|
||||
fills => $layerm->fills,
|
||||
};
|
||||
push @{ $infill_by_island[-1] }, $fill; # optimization
|
||||
}
|
||||
|
||||
foreach my $island (@islands) {
|
||||
for my $i (0 .. $#{$layer->slices}) {
|
||||
# give priority to infill if we were already using its extruder and it wouldn't
|
||||
# be good for perimeters
|
||||
if ($self->print->config->infill_first
|
||||
|| ($self->gcodegen->multiple_extruders && $region->config->infill_extruder-1 == $self->gcodegen->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) {
|
||||
$gcode .= $self->_extrude_infill($island, $region);
|
||||
$gcode .= $self->_extrude_perimeters($island, $region);
|
||||
$gcode .= $self->_extrude_infill($infill_by_island[$i], $region);
|
||||
$gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region);
|
||||
} else {
|
||||
$gcode .= $self->_extrude_perimeters($island, $region);
|
||||
$gcode .= $self->_extrude_infill($island, $region);
|
||||
$gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region);
|
||||
$gcode .= $self->_extrude_infill($infill_by_island[$i], $region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,25 +206,25 @@ sub process_layer {
|
|||
|
||||
sub _extrude_perimeters {
|
||||
my $self = shift;
|
||||
my ($island, $region) = @_;
|
||||
my ($island_perimeters, $region) = @_;
|
||||
|
||||
return "" if !@{ $island->{perimeters} };
|
||||
return "" if !@$island_perimeters;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= $self->gcodegen->set_extruder($region->config->perimeter_extruder-1);
|
||||
$gcode .= $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} };
|
||||
$gcode .= $self->gcodegen->extrude($_, 'perimeter') for @$island_perimeters;
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _extrude_infill {
|
||||
my $self = shift;
|
||||
my ($island, $region) = @_;
|
||||
my ($island_fills, $region) = @_;
|
||||
|
||||
return "" if !@{ $island->{fills} };
|
||||
return "" if !@$island_fills;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= $self->gcodegen->set_extruder($region->config->infill_extruder-1);
|
||||
for my $fill (@{ $island->{fills} }) {
|
||||
for my $fill (@$island_fills) {
|
||||
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
|
||||
$gcode .= $self->gcodegen->extrude($_, 'fill')
|
||||
for @{$fill->chained_path_from($self->gcodegen->last_pos, 0)};
|
||||
|
|
|
@ -175,13 +175,13 @@ sub _add_point_to_space {
|
|||
|
||||
# find candidates by checking visibility from $from to them
|
||||
foreach my $idx (0..$#{$space->nodes}) {
|
||||
my $line = Slic3r::Polyline->new($point, $space->nodes->[$idx]);
|
||||
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
|
||||
# if $point is inside an island, it is visible from $idx when island contains their line
|
||||
# if $point is outside an island, it is visible from $idx when their line does not cross any island
|
||||
if (
|
||||
($inside && defined first { $_->contains_line($line) } @{$self->_inner})
|
||||
|| (!$inside && !@{intersection_pl(
|
||||
[ $line ],
|
||||
[ $line->as_polyline ],
|
||||
[ map @$_, @{$self->islands} ],
|
||||
)})
|
||||
) {
|
||||
|
|
|
@ -1,53 +1,62 @@
|
|||
package Slic3r::GCode::PlaceholderParser;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
has '_single' => (is => 'ro', default => sub { {} });
|
||||
has '_multiple' => (is => 'ro', default => sub { {} });
|
||||
sub new {
|
||||
# TODO: move this code to C++ constructor, remove this method
|
||||
my ($class) = @_;
|
||||
my $self = $class->_new;
|
||||
$self->apply_env_variables;
|
||||
$self->update_timestamp;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub BUILD {
|
||||
sub apply_env_variables {
|
||||
my ($self) = @_;
|
||||
|
||||
my $s = $self->_single;
|
||||
|
||||
# environment variables
|
||||
$s->{$_} = $ENV{$_} for grep /^SLIC3R_/, keys %ENV;
|
||||
|
||||
# timestamp
|
||||
$self->_single_set($_, $ENV{$_}) for grep /^SLIC3R_/, keys %ENV;
|
||||
}
|
||||
|
||||
sub update_timestamp {
|
||||
my ($self) = @_;
|
||||
|
||||
my @lt = localtime; $lt[5] += 1900; $lt[4] += 1;
|
||||
$s->{timestamp} = sprintf '%04d%02d%02d-%02d%02d%02d', @lt[5,4,3,2,1,0];
|
||||
$s->{year} = $lt[5];
|
||||
$s->{month} = $lt[4];
|
||||
$s->{day} = $lt[3];
|
||||
$s->{hour} = $lt[2];
|
||||
$s->{minute} = $lt[1];
|
||||
$s->{second} = $lt[0];
|
||||
$s->{version} = $Slic3r::VERSION;
|
||||
$self->_single_set('timestamp', sprintf '%04d%02d%02d-%02d%02d%02d', @lt[5,4,3,2,1,0]);
|
||||
$self->_single_set('year', "$lt[5]");
|
||||
$self->_single_set('month', "$lt[4]");
|
||||
$self->_single_set('day', "$lt[3]");
|
||||
$self->_single_set('hour', "$lt[2]");
|
||||
$self->_single_set('minute', "$lt[1]");
|
||||
$self->_single_set('second', "$lt[0]");
|
||||
$self->_single_set('version', $Slic3r::VERSION);
|
||||
}
|
||||
|
||||
sub apply_config {
|
||||
my ($self, $config) = @_;
|
||||
|
||||
# options with single value
|
||||
my $s = $self->_single;
|
||||
my @opt_keys = grep !$Slic3r::Config::Options->{$_}{multiline}, @{$config->get_keys};
|
||||
$s->{$_} = $config->serialize($_) for @opt_keys;
|
||||
|
||||
$self->_single_set($_, $config->serialize($_)) for @opt_keys;
|
||||
|
||||
# options with multiple values
|
||||
my $m = $self->_multiple;
|
||||
foreach my $opt_key (@opt_keys) {
|
||||
my $value = $config->$opt_key;
|
||||
next unless ref($value) eq 'ARRAY';
|
||||
$m->{"${opt_key}_${_}"} = $value->[$_] for 0..$#$value;
|
||||
# TODO: this is a workaroud for XS string param handling
|
||||
# https://rt.cpan.org/Public/Bug/Display.html?id=94110
|
||||
"$_" for @$value;
|
||||
$self->_multiple_set("${opt_key}_" . $_, $value->[$_]."") for 0..$#$value;
|
||||
$self->_multiple_set($opt_key, $value->[0]."");
|
||||
if ($Slic3r::Config::Options->{$opt_key}{type} eq 'point') {
|
||||
$m->{"${opt_key}_X"} = $value->[0];
|
||||
$m->{"${opt_key}_Y"} = $value->[1];
|
||||
$self->_multiple_set("${opt_key}_X", $value->[0]."");
|
||||
$self->_multiple_set("${opt_key}_Y", $value->[1]."");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# TODO: or this could be an alias
|
||||
sub set {
|
||||
my ($self, $key, $val) = @_;
|
||||
$self->_single->{$key} = $val;
|
||||
$self->_single_set($key, $val);
|
||||
}
|
||||
|
||||
sub process {
|
||||
|
@ -59,12 +68,15 @@ sub process {
|
|||
$string =~ s/\[($regex)\]/$extra->{$1}/eg;
|
||||
}
|
||||
{
|
||||
my $regex = join '|', keys %{$self->_single};
|
||||
$string =~ s/\[($regex)\]/$self->_single->{$1}/eg;
|
||||
my $regex = join '|', @{$self->_single_keys};
|
||||
$string =~ s/\[($regex)\]/$self->_single_get("$1")/eg;
|
||||
}
|
||||
{
|
||||
my $regex = join '|', keys %{$self->_multiple};
|
||||
$string =~ s/\[($regex)\]/$self->_multiple->{$1}/eg;
|
||||
my $regex = join '|', @{$self->_multiple_keys};
|
||||
$string =~ s/\[($regex)\]/$self->_multiple_get("$1")/egx;
|
||||
|
||||
# unhandled indices are populated using the first value
|
||||
$string =~ s/\[($regex)_\d+\]/$self->_multiple_get("$1")/egx;
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
|
|
@ -43,7 +43,7 @@ sub process_layer {
|
|||
}
|
||||
});
|
||||
|
||||
#use XXX; YYY [ $gcode, $layer_height, $z, $total_layer_length ];
|
||||
#use XXX; XXX [ $gcode, $layer_height, $z, $total_layer_length ];
|
||||
# remove layer height from initial Z
|
||||
$z -= $layer_height;
|
||||
|
||||
|
@ -57,16 +57,19 @@ sub process_layer {
|
|||
my $line = $info->{raw};
|
||||
$line =~ s/ Z[.0-9]+/ Z$z/;
|
||||
$new_gcode .= "$line\n";
|
||||
} elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{dist_XY}) {
|
||||
} elsif ($cmd eq 'G1' && !exists($args->{Z}) && $info->{dist_XY}) {
|
||||
# horizontal move
|
||||
my $line = $info->{raw};
|
||||
if ($info->{extruding}) {
|
||||
$z += $info->{dist_XY} * $layer_height / $total_layer_length;
|
||||
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e;
|
||||
$new_gcode .= "$line\n";
|
||||
} else {
|
||||
$new_gcode .= "$line\n";
|
||||
}
|
||||
# skip travel moves: the move to first perimeter point will
|
||||
# cause a visible seam when loops are not aligned in XY; by skipping
|
||||
# it we blend the first loop move in the XY plane (although the smoothness
|
||||
# of such blend depend on how long the first segment is; maybe we should
|
||||
# enforce some minimum length?)
|
||||
} else {
|
||||
$new_gcode .= "$info->{raw}\n";
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ use FindBin;
|
|||
use Slic3r::GUI::AboutDialog;
|
||||
use Slic3r::GUI::ConfigWizard;
|
||||
use Slic3r::GUI::Plater;
|
||||
use Slic3r::GUI::Plater::2D;
|
||||
use Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||
use Slic3r::GUI::Plater::ObjectCutDialog;
|
||||
use Slic3r::GUI::Plater::ObjectPreviewDialog;
|
||||
use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
|
@ -364,6 +366,7 @@ sub check_version {
|
|||
} else {
|
||||
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual};
|
||||
}
|
||||
Slic3r::thread_cleanup();
|
||||
})->detach;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,12 +47,13 @@ sub new {
|
|||
'<html>' .
|
||||
'<body bgcolor="#ffffff" link="#808080">' .
|
||||
'<font color="#808080">' .
|
||||
'Copyright © 2011-2013 Alessandro Ranellucci. All rights reserved. ' .
|
||||
'Copyright © 2011-2014 Alessandro Ranellucci. <br />' .
|
||||
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
|
||||
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
|
||||
'<br /><br /><br />' .
|
||||
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' .
|
||||
'Manual by Gary Hodgson. Inspired by the RepRap community. <br />' .
|
||||
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
|
||||
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Mike Sheldrake and numerous others.' .
|
||||
'</font>' .
|
||||
'</body>' .
|
||||
'</html>';
|
||||
|
|
|
@ -3,7 +3,7 @@ use Moo;
|
|||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl);
|
||||
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
|
||||
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
|
@ -77,7 +77,9 @@ sub BUILD {
|
|||
$self->sizer->Add($grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
|
||||
|
||||
foreach my $line (@{$self->lines}) {
|
||||
if ($line->{widget}) {
|
||||
if ($line->{sizer}) {
|
||||
$self->sizer->Add($line->{sizer}, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
||||
} elsif ($line->{widget}) {
|
||||
my $window = $line->{widget}->GetWindow($self->parent);
|
||||
$self->sizer->Add($window, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
||||
} else {
|
||||
|
@ -169,9 +171,20 @@ sub _build_field {
|
|||
my $opt_key = $opt->{opt_key};
|
||||
$self->_triggers->{$opt_key} = $opt->{on_change} || sub { return 1 };
|
||||
|
||||
my $on_kill_focus = sub {
|
||||
my ($s, $event) = @_;
|
||||
|
||||
# Without this, there will be nasty focus bugs on Windows.
|
||||
# Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
|
||||
# non-command events to allow the default handling to take place."
|
||||
$event->Skip(1);
|
||||
|
||||
$self->on_kill_focus($opt_key);
|
||||
};
|
||||
|
||||
my $field;
|
||||
my $tooltip = $opt->{tooltip};
|
||||
if ($opt->{type} =~ /^(i|f|s|s@|percent)$/) {
|
||||
if ($opt->{type} =~ /^(i|f|s|s@|percent|slider)$/) {
|
||||
my $style = 0;
|
||||
$style = wxTE_MULTILINE if $opt->{multiline};
|
||||
# default width on Windows is too large
|
||||
|
@ -186,23 +199,43 @@ sub _build_field {
|
|||
$field = Wx::SpinCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style, $opt->{min} || 0, $opt->{max} || 2147483647, $opt->{default});
|
||||
$self->_setters->{$opt_key} = sub { $field->SetValue($_[0]) };
|
||||
EVT_SPINCTRL ($self->parent, $field, $on_change);
|
||||
EVT_KILL_FOCUS($field, sub { $self->on_kill_focus($opt_key) });
|
||||
EVT_TEXT ($self->parent, $field, $on_change);
|
||||
EVT_KILL_FOCUS($field, $on_kill_focus);
|
||||
} elsif ($opt->{values}) {
|
||||
$field = Wx::ComboBox->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $opt->{labels} || $opt->{values});
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$field->SetValue($_[0]);
|
||||
};
|
||||
EVT_COMBOBOX($self->parent, $field, sub {
|
||||
$field->SetValue($opt->{values}[ $field->GetSelection ]); # set the text field to the selected value
|
||||
$self->_on_change($opt_key, $on_change);
|
||||
# Without CallAfter, the field text is not populated on Windows.
|
||||
Slic3r::GUI->CallAfter(sub {
|
||||
$field->SetValue($opt->{values}[ $field->GetSelection ]); # set the text field to the selected value
|
||||
$self->_on_change($opt_key, $on_change);
|
||||
});
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, $on_change);
|
||||
EVT_KILL_FOCUS($field, sub { $self->on_kill_focus($opt_key) });
|
||||
EVT_KILL_FOCUS($field, $on_kill_focus);
|
||||
} elsif ($opt->{type} eq 'slider') {
|
||||
my $scale = 10;
|
||||
$field = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
my $slider = Wx::Slider->new($self->parent, -1, ($opt->{default} // $opt->{min})*$scale, ($opt->{min} // 0)*$scale, ($opt->{max} // 100)*$scale, wxDefaultPosition, $size);
|
||||
my $statictext = Wx::StaticText->new($self->parent, -1, $slider->GetValue/$scale);
|
||||
$field->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for $slider, $statictext;
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$field->SetValue($_[0]*$scale);
|
||||
};
|
||||
EVT_SLIDER($self->parent, $slider, sub {
|
||||
my $value = $slider->GetValue/$scale;
|
||||
$statictext->SetLabel($value);
|
||||
$self->_on_change($opt_key, $value);
|
||||
});
|
||||
} else {
|
||||
$field = Wx::TextCtrl->new($self->parent, -1, $opt->{default}, wxDefaultPosition, $size, $style);
|
||||
$self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) };
|
||||
# value supplied to the setter callback might be undef in case user loads a config
|
||||
# that has empty string for multi-value options like 'wipe'
|
||||
$self->_setters->{$opt_key} = sub { $field->ChangeValue($_[0]) if defined $_[0] };
|
||||
EVT_TEXT($self->parent, $field, $on_change);
|
||||
EVT_KILL_FOCUS($field, sub { $self->on_kill_focus($opt_key) });
|
||||
EVT_KILL_FOCUS($field, $on_kill_focus);
|
||||
}
|
||||
$field->Disable if $opt->{readonly};
|
||||
$tooltip .= " (default: " . $opt->{default} . ")" if ($opt->{default});
|
||||
|
@ -230,7 +263,7 @@ sub _build_field {
|
|||
}
|
||||
foreach my $field ($x_field, $y_field) {
|
||||
EVT_TEXT($self->parent, $field, sub { $self->_on_change($opt_key, [ $x_field->GetValue, $y_field->GetValue ]) });
|
||||
EVT_KILL_FOCUS($field, sub { $self->on_kill_focus($opt_key) });
|
||||
EVT_KILL_FOCUS($field, $on_kill_focus);
|
||||
}
|
||||
$self->_setters->{$opt_key} = sub {
|
||||
$x_field->SetValue($_[0][0]);
|
||||
|
@ -421,11 +454,15 @@ sub _set_config {
|
|||
if (defined $index) {
|
||||
my $values = $self->config->$get_m($opt_key);
|
||||
$values->[$index] = $value;
|
||||
|
||||
# ignore set() return value
|
||||
$self->config->set($opt_key, $values);
|
||||
} else {
|
||||
if ($serialized) {
|
||||
if ($serialized) {
|
||||
# ignore set_deserialize() return value
|
||||
return $self->config->set_deserialize($opt_key, $value);
|
||||
} else {
|
||||
} else {
|
||||
# ignore set() return value
|
||||
return $self->config->set($opt_key, $value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,10 @@ use warnings;
|
|||
use utf8;
|
||||
|
||||
use File::Basename qw(basename dirname);
|
||||
use List::Util qw(max sum first);
|
||||
use Slic3r::Geometry::Clipper qw(offset JT_ROUND);
|
||||
use Slic3r::Geometry qw(X Y Z MIN MAX convex_hull scale unscale);
|
||||
use List::Util qw(sum first);
|
||||
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale);
|
||||
use threads::shared qw(shared_clone);
|
||||
use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window);
|
||||
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc :panel :sizer :toolbar :window);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
|
@ -36,9 +35,6 @@ our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType;
|
|||
our $EXPORT_FAILED_EVENT : shared = Wx::NewEventType;
|
||||
|
||||
use constant CANVAS_SIZE => [335,335];
|
||||
use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8'
|
||||
? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
|
||||
: 'Drag your objects here';
|
||||
use constant FILAMENT_CHOOSERS_SPACING => 3;
|
||||
|
||||
my $PreventListEvents = 0;
|
||||
|
@ -54,19 +50,17 @@ sub new {
|
|||
$self->{print} = Slic3r::Print->new;
|
||||
$self->{objects} = [];
|
||||
|
||||
$self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, CANVAS_SIZE, wxTAB_TRAVERSAL);
|
||||
$self->{canvas}->SetBackgroundColour(Wx::wxWHITE);
|
||||
EVT_PAINT($self->{canvas}, \&repaint);
|
||||
EVT_MOUSE_EVENTS($self->{canvas}, \&mouse_event);
|
||||
|
||||
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
|
||||
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
|
||||
$self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID);
|
||||
$self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT);
|
||||
$self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID);
|
||||
$self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
|
||||
$self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
|
||||
$self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
|
||||
$self->{canvas} = Slic3r::GUI::Plater::2D->new($self, CANVAS_SIZE, $self->{objects}, $self->{model}, $self->{config});
|
||||
$self->{canvas}->on_select_object(sub {
|
||||
my ($obj_idx) = @_;
|
||||
$self->select_object($obj_idx);
|
||||
});
|
||||
$self->{canvas}->on_double_click(sub {
|
||||
$self->object_cut_dialog if $self->selected_object;
|
||||
});
|
||||
$self->{canvas}->on_instance_moved(sub {
|
||||
$self->update;
|
||||
});
|
||||
|
||||
# toolbar for object manipulation
|
||||
if (!&Wx::wxMSW) {
|
||||
|
@ -86,7 +80,7 @@ sub new {
|
|||
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), '');
|
||||
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), '');
|
||||
$self->{htoolbar}->AddSeparator;
|
||||
$self->{htoolbar}->AddTool(TB_VIEW, "View", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), '');
|
||||
$self->{htoolbar}->AddTool(TB_VIEW, "View/Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), '');
|
||||
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG), '');
|
||||
} else {
|
||||
my %tbar_buttons = (
|
||||
|
@ -101,7 +95,7 @@ sub new {
|
|||
rotate => "Rotate…",
|
||||
changescale => "Scale…",
|
||||
split => "Split",
|
||||
view => "View",
|
||||
view => "View/Cut…",
|
||||
settings => "Settings…",
|
||||
);
|
||||
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
|
@ -173,7 +167,7 @@ sub new {
|
|||
EVT_TOOL($self, TB_ROTATE, sub { $_[0]->rotate(undef) });
|
||||
EVT_TOOL($self, TB_SCALE, \&changescale);
|
||||
EVT_TOOL($self, TB_SPLIT, \&split_object);
|
||||
EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_preview_dialog });
|
||||
EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_cut_dialog });
|
||||
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
|
||||
} else {
|
||||
EVT_BUTTON($self, $self->{btn_add}, \&add);
|
||||
|
@ -187,7 +181,7 @@ sub new {
|
|||
EVT_BUTTON($self, $self->{btn_changescale}, \&changescale);
|
||||
EVT_BUTTON($self, $self->{btn_rotate}, sub { $_[0]->rotate(undef) });
|
||||
EVT_BUTTON($self, $self->{btn_split}, \&split_object);
|
||||
EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_preview_dialog });
|
||||
EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_cut_dialog });
|
||||
EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
|
||||
}
|
||||
|
||||
|
@ -224,7 +218,7 @@ sub new {
|
|||
$self->on_export_failed;
|
||||
});
|
||||
|
||||
$self->_update_bed_size;
|
||||
$self->{canvas}->update_bed_size;
|
||||
$self->update;
|
||||
|
||||
{
|
||||
|
@ -403,13 +397,13 @@ sub load_model_objects {
|
|||
);
|
||||
push @obj_idx, $#{ $self->{objects} };
|
||||
|
||||
if (!defined $model_object->instances) {
|
||||
if ($model_object->instances_count == 0) {
|
||||
# if object has no defined position(s) we need to rearrange everything after loading
|
||||
$need_arrange = 1;
|
||||
|
||||
# add a default instance and center object around origin
|
||||
$o->center_around_origin;
|
||||
$o->add_instance(offset => [ @{$self->{config}->print_center} ]);
|
||||
$o->add_instance(offset => Slic3r::Pointf->new(@{$self->{config}->print_center}));
|
||||
}
|
||||
|
||||
$self->{print}->auto_assign_extruders($o);
|
||||
|
@ -471,8 +465,8 @@ sub reset {
|
|||
my $self = shift;
|
||||
|
||||
@{$self->{objects}} = ();
|
||||
$self->{model}->delete_all_objects;
|
||||
$self->{print}->delete_all_objects;
|
||||
$self->{model}->clear_objects;
|
||||
$self->{print}->clear_objects;
|
||||
$self->{list}->DeleteAllItems;
|
||||
$self->object_list_changed;
|
||||
|
||||
|
@ -487,7 +481,7 @@ sub increase {
|
|||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
my $last_instance = $model_object->instances->[-1];
|
||||
my $i = $model_object->add_instance(
|
||||
offset => [ map 10+$_, @{$last_instance->offset} ],
|
||||
offset => Slic3r::Pointf->new(map 10+$_, @{$last_instance->offset}),
|
||||
scaling_factor => $last_instance->scaling_factor,
|
||||
rotation => $last_instance->rotation,
|
||||
);
|
||||
|
@ -542,11 +536,10 @@ sub rotate {
|
|||
|
||||
{
|
||||
my $new_angle = $model_instance->rotation + $angle;
|
||||
$_->rotation($new_angle) for @{ $model_object->instances };
|
||||
$_->set_rotation($new_angle) for @{ $model_object->instances };
|
||||
$model_object->update_bounding_box;
|
||||
|
||||
# update print
|
||||
$self->{print}->delete_object($obj_idx);
|
||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||
|
||||
$object->transform_thumbnail($self->{model}, $obj_idx);
|
||||
|
@ -578,11 +571,10 @@ sub changescale {
|
|||
$range->[0] *= $variation;
|
||||
$range->[1] *= $variation;
|
||||
}
|
||||
$_->scaling_factor($scale) for @{ $model_object->instances };
|
||||
$_->set_scaling_factor($scale) for @{ $model_object->instances };
|
||||
$model_object->update_bounding_box;
|
||||
|
||||
# update print
|
||||
$self->{print}->delete_object($obj_idx);
|
||||
$self->{print}->add_model_object($model_object, $obj_idx);
|
||||
|
||||
$object->transform_thumbnail($self->{model}, $obj_idx);
|
||||
|
@ -597,8 +589,8 @@ sub arrange {
|
|||
|
||||
# get the bounding box of the model area shown in the viewport
|
||||
my $bb = Slic3r::Geometry::BoundingBox->new_from_points([
|
||||
Slic3r::Point->new(@{ $self->point_to_model_units([0,0]) }),
|
||||
Slic3r::Point->new(@{ $self->point_to_model_units(CANVAS_SIZE) }),
|
||||
Slic3r::Point->new(@{ $self->{canvas}->point_to_model_units([0,0]) }),
|
||||
Slic3r::Point->new(@{ $self->{canvas}->point_to_model_units(CANVAS_SIZE) }),
|
||||
]);
|
||||
|
||||
eval {
|
||||
|
@ -627,12 +619,6 @@ sub split_object {
|
|||
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part.");
|
||||
return;
|
||||
}
|
||||
|
||||
# remove the original object before spawning the object_loaded event, otherwise
|
||||
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
|
||||
# thumbnail thread returns)
|
||||
$self->remove($obj_idx);
|
||||
$current_object = $obj_idx = undef;
|
||||
|
||||
# create a bogus Model object, we only need to instantiate the new Model::Object objects
|
||||
my $new_model = Slic3r::Model->new;
|
||||
|
@ -654,10 +640,10 @@ sub split_object {
|
|||
for my $instance_idx (0..$#{ $current_model_object->instances }) {
|
||||
my $current_instance = $current_model_object->instances->[$instance_idx];
|
||||
$model_object->add_instance(
|
||||
offset => [
|
||||
offset => Slic3r::Pointf->new(
|
||||
$current_instance->offset->[X] + ($instance_idx * 10),
|
||||
$current_instance->offset->[Y] + ($instance_idx * 10),
|
||||
],
|
||||
),
|
||||
rotation => $current_instance->rotation,
|
||||
scaling_factor => $current_instance->scaling_factor,
|
||||
);
|
||||
|
@ -666,6 +652,12 @@ sub split_object {
|
|||
$model_object->center_around_origin;
|
||||
push @model_objects, $model_object;
|
||||
}
|
||||
|
||||
# remove the original object before spawning the object_loaded event, otherwise
|
||||
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
|
||||
# thumbnail thread returns)
|
||||
$self->remove($obj_idx);
|
||||
$current_object = $obj_idx = undef;
|
||||
|
||||
# load all model objects at once, otherwise the plate would be rearranged after each one
|
||||
# causing original positions not to be kept
|
||||
|
@ -692,6 +684,7 @@ sub export_gcode {
|
|||
$self->{print}->apply_config($config);
|
||||
$self->{print}->validate;
|
||||
};
|
||||
Slic3r::GUI::catch_error($self) and return;
|
||||
|
||||
# apply extra variables
|
||||
{
|
||||
|
@ -778,14 +771,14 @@ sub export_gcode2 {
|
|||
my @warnings = ();
|
||||
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
|
||||
|
||||
$print->status_cb(sub { $params{progressbar}->(@_) });
|
||||
$print->set_status_cb(sub { $params{progressbar}->(@_) });
|
||||
if ($params{export_svg}) {
|
||||
$print->export_svg(output_file => $output_file);
|
||||
} else {
|
||||
$print->process;
|
||||
$print->export_gcode(output_file => $output_file);
|
||||
}
|
||||
$print->status_cb(undef);
|
||||
$print->set_status_cb(undef);
|
||||
Slic3r::GUI::warning_catcher($self, $Slic3r::have_threads ? sub {
|
||||
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $MESSAGE_DIALOG_EVENT, shared_clone([@_])));
|
||||
} : undef)->($_) for @warnings;
|
||||
|
@ -895,14 +888,6 @@ sub on_thumbnail_made {
|
|||
$self->{canvas}->Refresh;
|
||||
}
|
||||
|
||||
sub clean_instance_thumbnails {
|
||||
my ($self) = @_;
|
||||
|
||||
foreach my $object (@{ $self->{objects} }) {
|
||||
@{ $object->instance_thumbnails } = ();
|
||||
}
|
||||
}
|
||||
|
||||
# this method gets called whenever print center is changed or the objects' bounding box changes
|
||||
# (i.e. when an object is added/removed/moved/rotated/scaled)
|
||||
sub update {
|
||||
|
@ -946,183 +931,14 @@ sub on_config_change {
|
|||
$self->Layout;
|
||||
} elsif ($self->{config}->has($opt_key)) {
|
||||
$self->{config}->set($opt_key, $value);
|
||||
$self->_update_bed_size if $opt_key eq 'bed_size';
|
||||
if ($opt_key eq 'bed_size') {
|
||||
$self->{canvas}->update_bed_size;
|
||||
$self->update;
|
||||
}
|
||||
$self->update if $opt_key eq 'print_center';
|
||||
}
|
||||
}
|
||||
|
||||
sub _update_bed_size {
|
||||
my $self = shift;
|
||||
|
||||
# supposing the preview canvas is square, calculate the scaling factor
|
||||
# to constrain print bed area inside preview
|
||||
# when the canvas is not rendered yet, its GetSize() method returns 0,0
|
||||
# scaling_factor is expressed in pixel / mm
|
||||
$self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size });
|
||||
$self->update;
|
||||
}
|
||||
|
||||
# this is called on the canvas
|
||||
sub repaint {
|
||||
my ($self, $event) = @_;
|
||||
my $parent = $self->GetParent;
|
||||
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
my $size = $self->GetSize;
|
||||
my @size = ($size->GetWidth, $size->GetHeight);
|
||||
|
||||
# draw grid
|
||||
$dc->SetPen($parent->{grid_pen});
|
||||
my $step = 10 * $parent->{scaling_factor};
|
||||
for (my $x = $step; $x <= $size[X]; $x += $step) {
|
||||
$dc->DrawLine($x, 0, $x, $size[Y]);
|
||||
}
|
||||
for (my $y = $step; $y <= $size[Y]; $y += $step) {
|
||||
$dc->DrawLine(0, $y, $size[X], $y);
|
||||
}
|
||||
|
||||
# draw print center
|
||||
if (@{$parent->{objects}}) {
|
||||
$dc->SetPen($parent->{print_center_pen});
|
||||
$dc->DrawLine($size[X]/2, 0, $size[X]/2, $size[Y]);
|
||||
$dc->DrawLine(0, $size[Y]/2, $size[X], $size[Y]/2);
|
||||
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
|
||||
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel("X = " . $parent->{config}->print_center->[X], Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM);
|
||||
$dc->DrawRotatedText("Y = " . $parent->{config}->print_center->[Y], 0, $size[Y]/2+15, 90);
|
||||
}
|
||||
|
||||
# draw frame
|
||||
if (0) {
|
||||
$dc->SetPen(wxBLACK_PEN);
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
$dc->DrawRectangle(0, 0, @size);
|
||||
}
|
||||
|
||||
# draw text if plate is empty
|
||||
if (!@{$parent->{objects}}) {
|
||||
$dc->SetTextForeground(Wx::Colour->new(150,50,50));
|
||||
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
|
||||
# draw thumbnails
|
||||
$dc->SetPen(wxBLACK_PEN);
|
||||
$parent->clean_instance_thumbnails;
|
||||
for my $obj_idx (0 .. $#{$parent->{objects}}) {
|
||||
my $object = $parent->{objects}[$obj_idx];
|
||||
my $model_object = $parent->{model}->objects->[$obj_idx];
|
||||
next unless defined $object->thumbnail;
|
||||
for my $instance_idx (0 .. $#{$model_object->instances}) {
|
||||
my $instance = $model_object->instances->[$instance_idx];
|
||||
next if !defined $object->transformed_thumbnail;
|
||||
|
||||
my $thumbnail = $object->transformed_thumbnail->clone; # in scaled model coordinates
|
||||
$thumbnail->translate(map scale($_), @{$instance->offset});
|
||||
|
||||
$object->instance_thumbnails->[$instance_idx] = $thumbnail;
|
||||
|
||||
if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) {
|
||||
$dc->SetBrush($parent->{dragged_brush});
|
||||
} elsif ($object->selected) {
|
||||
$dc->SetBrush($parent->{selected_brush});
|
||||
} else {
|
||||
$dc->SetBrush($parent->{objects_brush});
|
||||
}
|
||||
foreach my $expolygon (@$thumbnail) {
|
||||
foreach my $points (@{$expolygon->pp}) {
|
||||
$dc->DrawPolygon($parent->points_to_pixel($points, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (0) {
|
||||
# draw bounding box for debugging purposes
|
||||
my $bb = $model_object->instance_bounding_box($instance_idx);
|
||||
$bb->scale($parent->{scaling_factor});
|
||||
# no need to translate by instance offset because instance_bounding_box() does that
|
||||
my $points = $bb->polygon->pp;
|
||||
$dc->SetPen($parent->{clearance_pen});
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
$dc->DrawPolygon($parent->_y($points), 0, 0);
|
||||
}
|
||||
|
||||
# if sequential printing is enabled and we have more than one object, draw clearance area
|
||||
if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{model}->objects}) > 1) {
|
||||
my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($parent->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($parent->{clearance_pen});
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
$dc->DrawPolygon($parent->points_to_pixel($clearance, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# draw skirt
|
||||
if (@{$parent->{objects}} && $parent->{config}->skirts) {
|
||||
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$parent->{objects}};
|
||||
if (@points >= 3) {
|
||||
my ($convex_hull) = @{offset([convex_hull(\@points)], scale($parent->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($parent->{skirt_pen});
|
||||
$dc->SetBrush($parent->{transparent_brush});
|
||||
$dc->DrawPolygon($parent->points_to_pixel($convex_hull, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$event->Skip;
|
||||
}
|
||||
|
||||
sub mouse_event {
|
||||
my ($self, $event) = @_;
|
||||
my $parent = $self->GetParent;
|
||||
|
||||
my $point = $event->GetPosition;
|
||||
my $pos = $parent->point_to_model_units([ $point->x, $point->y ]); #]]
|
||||
$pos = Slic3r::Point->new_scale(@$pos);
|
||||
if ($event->ButtonDown(&Wx::wxMOUSE_BTN_LEFT)) {
|
||||
$parent->select_object(undef);
|
||||
for my $obj_idx (0 .. $#{$parent->{objects}}) {
|
||||
my $object = $parent->{objects}->[$obj_idx];
|
||||
for my $instance_idx (0 .. $#{ $object->instance_thumbnails }) {
|
||||
my $thumbnail = $object->instance_thumbnails->[$instance_idx];
|
||||
if (defined first { $_->contour->contains_point($pos) } @$thumbnail) {
|
||||
$parent->select_object($obj_idx);
|
||||
my $instance = $parent->{model}->objects->[$obj_idx]->instances->[$instance_idx];
|
||||
my $instance_origin = [ map scale($_), @{$instance->offset} ];
|
||||
$self->{drag_start_pos} = [ # displacement between the click and the instance origin in scaled model units
|
||||
$pos->x - $instance_origin->[X],
|
||||
$pos->y - $instance_origin->[Y], #-
|
||||
];
|
||||
$self->{drag_object} = [ $obj_idx, $instance_idx ];
|
||||
}
|
||||
}
|
||||
}
|
||||
$parent->Refresh;
|
||||
} elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) {
|
||||
$parent->update;
|
||||
$parent->Refresh;
|
||||
$self->{drag_start_pos} = undef;
|
||||
$self->{drag_object} = undef;
|
||||
$self->SetCursor(wxSTANDARD_CURSOR);
|
||||
} elsif ($event->ButtonDClick) {
|
||||
$parent->object_preview_dialog if $parent->selected_object;
|
||||
} elsif ($event->Dragging) {
|
||||
return if !$self->{drag_start_pos}; # concurrency problems
|
||||
my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
|
||||
my $model_object = $parent->{model}->objects->[$obj_idx];
|
||||
$model_object->instances->[$instance_idx]->offset([
|
||||
unscale($pos->[X] - $self->{drag_start_pos}[X]),
|
||||
unscale($pos->[Y] - $self->{drag_start_pos}[Y]),
|
||||
]);
|
||||
$model_object->update_bounding_box;
|
||||
$parent->Refresh;
|
||||
} elsif ($event->Moving) {
|
||||
my $cursor = wxSTANDARD_CURSOR;
|
||||
if (defined first { $_->contour->contains_point($pos) } map @$_, map @{$_->instance_thumbnails}, @{ $parent->{objects} }) {
|
||||
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
|
||||
}
|
||||
$self->SetCursor($cursor);
|
||||
}
|
||||
}
|
||||
|
||||
sub list_item_deselected {
|
||||
my ($self, $event) = @_;
|
||||
return if $PreventListEvents;
|
||||
|
@ -1146,10 +962,10 @@ sub list_item_activated {
|
|||
my ($self, $event, $obj_idx) = @_;
|
||||
|
||||
$obj_idx //= $event->GetIndex;
|
||||
$self->object_preview_dialog($obj_idx);
|
||||
$self->object_cut_dialog($obj_idx);
|
||||
}
|
||||
|
||||
sub object_preview_dialog {
|
||||
sub object_cut_dialog {
|
||||
my $self = shift;
|
||||
my ($obj_idx) = @_;
|
||||
|
||||
|
@ -1162,11 +978,17 @@ sub object_preview_dialog {
|
|||
return;
|
||||
}
|
||||
|
||||
my $dlg = Slic3r::GUI::Plater::ObjectPreviewDialog->new($self,
|
||||
object => $self->{objects}[$obj_idx],
|
||||
model_object => $self->{model}->objects->[$obj_idx],
|
||||
my $dlg = Slic3r::GUI::Plater::ObjectCutDialog->new($self,
|
||||
object => $self->{objects}[$obj_idx],
|
||||
model_object => $self->{model}->objects->[$obj_idx],
|
||||
);
|
||||
$dlg->ShowModal;
|
||||
|
||||
if (my @new_objects = $dlg->NewModelObjects) {
|
||||
$self->remove($obj_idx);
|
||||
$self->load_model_objects(@new_objects);
|
||||
$self->arrange;
|
||||
}
|
||||
}
|
||||
|
||||
sub object_settings_dialog {
|
||||
|
@ -1306,50 +1128,6 @@ sub statusbar {
|
|||
return $self->skeinpanel->GetParent->{statusbar};
|
||||
}
|
||||
|
||||
# coordinates of the model origin (0,0) in pixels
|
||||
sub model_origin_to_pixel {
|
||||
my ($self) = @_;
|
||||
|
||||
return [
|
||||
CANVAS_SIZE->[X]/2 - ($self->{config}->print_center->[X] * $self->{scaling_factor}),
|
||||
CANVAS_SIZE->[Y]/2 - ($self->{config}->print_center->[Y] * $self->{scaling_factor}),
|
||||
];
|
||||
}
|
||||
|
||||
# convert a model coordinate into a pixel coordinate, assuming preview has square shape
|
||||
sub point_to_pixel {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $canvas_height = $self->{canvas}->GetSize->GetHeight;
|
||||
my $zero = $self->model_origin_to_pixel;
|
||||
return [
|
||||
$point->[X] * $self->{scaling_factor} + $zero->[X],
|
||||
$canvas_height - ($point->[Y] * $self->{scaling_factor} + $zero->[Y]),
|
||||
];
|
||||
}
|
||||
|
||||
sub points_to_pixel {
|
||||
my ($self, $points, $unscale) = @_;
|
||||
|
||||
my $result = [];
|
||||
foreach my $point (@$points) {
|
||||
$point = [ map unscale($_), @$point ] if $unscale;
|
||||
push @$result, $self->point_to_pixel($point);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub point_to_model_units {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $canvas_height = $self->{canvas}->GetSize->GetHeight;
|
||||
my $zero = $self->model_origin_to_pixel;
|
||||
return [
|
||||
($point->[X] - $zero->[X]) / $self->{scaling_factor},
|
||||
(($canvas_height - $point->[Y] - $zero->[Y]) / $self->{scaling_factor}),
|
||||
];
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Plater::DropTarget;
|
||||
use Wx::DND;
|
||||
use base 'Wx::FileDropTarget';
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
package Slic3r::GUI::Plater::2D;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw(max first);
|
||||
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(offset JT_ROUND);
|
||||
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8'
|
||||
? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
|
||||
: 'Drag your objects here';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $size, $objects, $model, $config) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
|
||||
$self->{objects} = $objects;
|
||||
$self->{model} = $model;
|
||||
$self->{config} = $config;
|
||||
$self->{on_select_object} = sub {};
|
||||
$self->{on_double_click} = sub {};
|
||||
$self->{on_instance_moved} = sub {};
|
||||
|
||||
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
|
||||
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
|
||||
$self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID);
|
||||
$self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT);
|
||||
$self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID);
|
||||
$self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
|
||||
$self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
|
||||
$self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
|
||||
|
||||
EVT_PAINT($self, \&repaint);
|
||||
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub on_select_object {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_select_object} = $cb;
|
||||
}
|
||||
|
||||
sub on_double_click {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_double_click} = $cb;
|
||||
}
|
||||
|
||||
sub on_instance_moved {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_instance_moved} = $cb;
|
||||
}
|
||||
|
||||
sub repaint {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
my $size = $self->GetSize;
|
||||
my @size = ($size->GetWidth, $size->GetHeight);
|
||||
|
||||
# draw grid
|
||||
$dc->SetPen($self->{grid_pen});
|
||||
my $step = 10 * $self->{scaling_factor};
|
||||
for (my $x = $step; $x <= $size[X]; $x += $step) {
|
||||
$dc->DrawLine($x, 0, $x, $size[Y]);
|
||||
}
|
||||
for (my $y = $step; $y <= $size[Y]; $y += $step) {
|
||||
$dc->DrawLine(0, $y, $size[X], $y);
|
||||
}
|
||||
|
||||
# draw print center
|
||||
if (@{$self->{objects}}) {
|
||||
$dc->SetPen($self->{print_center_pen});
|
||||
$dc->DrawLine($size[X]/2, 0, $size[X]/2, $size[Y]);
|
||||
$dc->DrawLine(0, $size[Y]/2, $size[X], $size[Y]/2);
|
||||
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
|
||||
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel("X = " . $self->{config}->print_center->[X], Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM);
|
||||
$dc->DrawRotatedText("Y = " . $self->{config}->print_center->[Y], 0, $size[Y]/2+15, 90);
|
||||
}
|
||||
|
||||
# draw frame
|
||||
if (0) {
|
||||
$dc->SetPen(wxBLACK_PEN);
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawRectangle(0, 0, @size);
|
||||
}
|
||||
|
||||
# draw text if plate is empty
|
||||
if (!@{$self->{objects}}) {
|
||||
$dc->SetTextForeground(Wx::Colour->new(150,50,50));
|
||||
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
|
||||
# draw thumbnails
|
||||
$dc->SetPen(wxBLACK_PEN);
|
||||
$self->clean_instance_thumbnails;
|
||||
for my $obj_idx (0 .. $#{$self->{objects}}) {
|
||||
my $object = $self->{objects}[$obj_idx];
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
next unless defined $object->thumbnail;
|
||||
for my $instance_idx (0 .. $#{$model_object->instances}) {
|
||||
my $instance = $model_object->instances->[$instance_idx];
|
||||
next if !defined $object->transformed_thumbnail;
|
||||
|
||||
my $thumbnail = $object->transformed_thumbnail->clone; # in scaled model coordinates
|
||||
$thumbnail->translate(map scale($_), @{$instance->offset});
|
||||
|
||||
$object->instance_thumbnails->[$instance_idx] = $thumbnail;
|
||||
|
||||
if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) {
|
||||
$dc->SetBrush($self->{dragged_brush});
|
||||
} elsif ($object->selected) {
|
||||
$dc->SetBrush($self->{selected_brush});
|
||||
} else {
|
||||
$dc->SetBrush($self->{objects_brush});
|
||||
}
|
||||
foreach my $expolygon (@$thumbnail) {
|
||||
foreach my $points (@{$expolygon->pp}) {
|
||||
$dc->DrawPolygon($self->points_to_pixel($points, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (0) {
|
||||
# draw bounding box for debugging purposes
|
||||
my $bb = $model_object->instance_bounding_box($instance_idx);
|
||||
$bb->scale($self->{scaling_factor});
|
||||
# no need to translate by instance offset because instance_bounding_box() does that
|
||||
my $points = $bb->polygon->pp;
|
||||
$dc->SetPen($self->{clearance_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->_y($points), 0, 0);
|
||||
}
|
||||
|
||||
# if sequential printing is enabled and we have more than one object, draw clearance area
|
||||
if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) {
|
||||
my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($self->{clearance_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->points_to_pixel($clearance, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# draw skirt
|
||||
if (@{$self->{objects}} && $self->{config}->skirts) {
|
||||
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}};
|
||||
if (@points >= 3) {
|
||||
my ($convex_hull) = @{offset([convex_hull(\@points)], scale($self->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($self->{skirt_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->points_to_pixel($convex_hull, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$event->Skip;
|
||||
}
|
||||
|
||||
sub mouse_event {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $point = $event->GetPosition;
|
||||
my $pos = $self->point_to_model_units([ $point->x, $point->y ]); #]]
|
||||
$pos = Slic3r::Point->new_scale(@$pos);
|
||||
if ($event->ButtonDown(&Wx::wxMOUSE_BTN_LEFT)) {
|
||||
$self->{on_select_object}->(undef);
|
||||
for my $obj_idx (0 .. $#{$self->{objects}}) {
|
||||
my $object = $self->{objects}->[$obj_idx];
|
||||
for my $instance_idx (0 .. $#{ $object->instance_thumbnails }) {
|
||||
my $thumbnail = $object->instance_thumbnails->[$instance_idx];
|
||||
if (defined first { $_->contour->contains_point($pos) } @$thumbnail) {
|
||||
$self->{on_select_object}->($obj_idx);
|
||||
my $instance = $self->{model}->objects->[$obj_idx]->instances->[$instance_idx];
|
||||
my $instance_origin = [ map scale($_), @{$instance->offset} ];
|
||||
$self->{drag_start_pos} = [ # displacement between the click and the instance origin in scaled model units
|
||||
$pos->x - $instance_origin->[X],
|
||||
$pos->y - $instance_origin->[Y], #-
|
||||
];
|
||||
$self->{drag_object} = [ $obj_idx, $instance_idx ];
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->Refresh;
|
||||
} elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) {
|
||||
$self->{on_instance_moved}->();
|
||||
$self->Refresh;
|
||||
$self->{drag_start_pos} = undef;
|
||||
$self->{drag_object} = undef;
|
||||
$self->SetCursor(wxSTANDARD_CURSOR);
|
||||
} elsif ($event->ButtonDClick) {
|
||||
$self->{on_double_click}->();
|
||||
} elsif ($event->Dragging) {
|
||||
return if !$self->{drag_start_pos}; # concurrency problems
|
||||
my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
$model_object->instances->[$instance_idx]->set_offset(
|
||||
Slic3r::Pointf->new(
|
||||
unscale($pos->[X] - $self->{drag_start_pos}[X]),
|
||||
unscale($pos->[Y] - $self->{drag_start_pos}[Y]),
|
||||
));
|
||||
$model_object->update_bounding_box;
|
||||
$self->Refresh;
|
||||
} elsif ($event->Moving) {
|
||||
my $cursor = wxSTANDARD_CURSOR;
|
||||
if (defined first { $_->contour->contains_point($pos) } map @$_, map @{$_->instance_thumbnails}, @{ $self->{objects} }) {
|
||||
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
|
||||
}
|
||||
$self->SetCursor($cursor);
|
||||
}
|
||||
}
|
||||
|
||||
sub update_bed_size {
|
||||
my $self = shift;
|
||||
|
||||
# supposing the preview canvas is square, calculate the scaling factor
|
||||
# to constrain print bed area inside preview
|
||||
# when the canvas is not rendered yet, its GetSize() method returns 0,0
|
||||
# scaling_factor is expressed in pixel / mm
|
||||
my $width = $self->GetSize->GetWidth;
|
||||
$self->{scaling_factor} = $width / max(@{ $self->{config}->bed_size })
|
||||
if $width != 0;
|
||||
}
|
||||
|
||||
sub clean_instance_thumbnails {
|
||||
my ($self) = @_;
|
||||
|
||||
foreach my $object (@{ $self->{objects} }) {
|
||||
@{ $object->instance_thumbnails } = ();
|
||||
}
|
||||
}
|
||||
|
||||
# coordinates of the model origin (0,0) in pixels
|
||||
sub model_origin_to_pixel {
|
||||
my ($self) = @_;
|
||||
|
||||
return [
|
||||
$self->GetSize->GetWidth/2 - ($self->{config}->print_center->[X] * $self->{scaling_factor}),
|
||||
$self->GetSize->GetHeight/2 - ($self->{config}->print_center->[Y] * $self->{scaling_factor}),
|
||||
];
|
||||
}
|
||||
|
||||
# convert a model coordinate into a pixel coordinate, assuming preview has square shape
|
||||
sub point_to_pixel {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $canvas_height = $self->GetSize->GetHeight;
|
||||
my $zero = $self->model_origin_to_pixel;
|
||||
return [
|
||||
$point->[X] * $self->{scaling_factor} + $zero->[X],
|
||||
$canvas_height - ($point->[Y] * $self->{scaling_factor} + $zero->[Y]),
|
||||
];
|
||||
}
|
||||
|
||||
sub points_to_pixel {
|
||||
my ($self, $points, $unscale) = @_;
|
||||
|
||||
my $result = [];
|
||||
foreach my $point (@$points) {
|
||||
$point = [ map unscale($_), @$point ] if $unscale;
|
||||
push @$result, $self->point_to_pixel($point);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub point_to_model_units {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $canvas_height = $self->GetSize->GetHeight;
|
||||
my $zero = $self->model_origin_to_pixel;
|
||||
return [
|
||||
($point->[X] - $zero->[X]) / $self->{scaling_factor},
|
||||
(($canvas_height - $point->[Y] - $zero->[Y]) / $self->{scaling_factor}),
|
||||
];
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,148 @@
|
|||
package Slic3r::GUI::Plater::ObjectCutDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Slic3r::Geometry qw(PI);
|
||||
use Wx qw(:dialog :id :misc :sizer wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{model_object_idx} = $params{model_object_idx};
|
||||
$self->{model_object} = $params{model_object};
|
||||
$self->{new_model_objects} = [];
|
||||
|
||||
# cut options
|
||||
$self->{cut_options} = {
|
||||
z => 0,
|
||||
keep_upper => 1,
|
||||
keep_lower => 1,
|
||||
rotate_lower => 1,
|
||||
};
|
||||
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
$self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
|
||||
$cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10);
|
||||
}
|
||||
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Cut',
|
||||
options => [
|
||||
{
|
||||
opt_key => 'z',
|
||||
type => 'slider',
|
||||
label => 'Z',
|
||||
default => $self->{cut_options}{z},
|
||||
min => 0,
|
||||
max => $self->{model_object}->bounding_box->size->z,
|
||||
},
|
||||
{
|
||||
opt_key => 'keep_upper',
|
||||
type => 'bool',
|
||||
label => 'Upper part',
|
||||
default => $self->{cut_options}{keep_upper},
|
||||
},
|
||||
{
|
||||
opt_key => 'keep_lower',
|
||||
type => 'bool',
|
||||
label => 'Lower part',
|
||||
default => $self->{cut_options}{keep_lower},
|
||||
},
|
||||
{
|
||||
opt_key => 'rotate_lower',
|
||||
type => 'bool',
|
||||
label => '',
|
||||
tooltip => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.',
|
||||
default => $self->{cut_options}{rotate_lower},
|
||||
},
|
||||
],
|
||||
lines => [
|
||||
{
|
||||
label => 'Z',
|
||||
options => [qw(z)],
|
||||
},
|
||||
{
|
||||
label => 'Keep',
|
||||
options => [qw(keep_upper keep_lower)],
|
||||
},
|
||||
{
|
||||
label => 'Rotate lower part',
|
||||
options => [qw(rotate_lower)],
|
||||
},
|
||||
{
|
||||
sizer => $cut_button_sizer,
|
||||
},
|
||||
],
|
||||
on_change => sub {
|
||||
my ($opt_key, $value) = @_;
|
||||
|
||||
$self->{cut_options}{$opt_key} = $value;
|
||||
if ($opt_key eq 'z') {
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->SetCuttingPlane($value);
|
||||
$self->{canvas}->Render;
|
||||
}
|
||||
}
|
||||
},
|
||||
label_width => 120,
|
||||
);
|
||||
|
||||
# left pane with tree
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self, $self->{model_object});
|
||||
$canvas->SetSize([500,500]);
|
||||
$canvas->SetMinSize($canvas->GetSize);
|
||||
}
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
|
||||
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
EVT_BUTTON($self, $self->{btn_cut}, sub { $self->perform_cut });
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub perform_cut {
|
||||
my ($self) = @_;
|
||||
|
||||
my ($upper_object, $lower_object) = $self->{model_object}->cut($self->{cut_options}{z});
|
||||
$self->{new_model_objects} = [];
|
||||
if ($self->{cut_options}{keep_upper} && defined $upper_object) {
|
||||
push @{$self->{new_model_objects}}, $upper_object;
|
||||
}
|
||||
if ($self->{cut_options}{keep_lower} && defined $lower_object) {
|
||||
push @{$self->{new_model_objects}}, $lower_object;
|
||||
if ($self->{cut_options}{rotate_lower}) {
|
||||
$lower_object->rotate_x(PI);
|
||||
}
|
||||
}
|
||||
|
||||
$self->Close;
|
||||
}
|
||||
|
||||
sub NewModelObjects {
|
||||
my ($self) = @_;
|
||||
return @{ $self->{new_model_objects} };
|
||||
}
|
||||
|
||||
1;
|
|
@ -67,7 +67,7 @@ sub new {
|
|||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self, $self->{model_object});
|
||||
$canvas = Slic3r::GUI::PreviewCanvas->new($self, $self->{model_object});
|
||||
$canvas->SetSize([500,500]);
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,7 @@ sub new {
|
|||
EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
|
||||
|
||||
$self->reload_tree;
|
||||
$self->{canvas} = $canvas;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
@ -158,7 +159,7 @@ sub selection_changed {
|
|||
|
||||
# attach volume material config to settings panel
|
||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
||||
my $material = $self->{model_object}->model->materials->{ $volume->material_id // '_' };
|
||||
my $material = $self->{model_object}->model->get_material($volume->material_id // '_');
|
||||
$material //= $volume->assign_unique_material;
|
||||
$self->{staticbox}->SetLabel('Part Settings');
|
||||
$self->{settings_panel}->enable;
|
||||
|
@ -197,7 +198,7 @@ sub on_btn_load {
|
|||
foreach my $object (@{$model->objects}) {
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
my $new_volume = $self->{model_object}->add_volume($volume);
|
||||
$new_volume->modifier($is_modifier);
|
||||
$new_volume->set_modifier($is_modifier);
|
||||
if (!defined $new_volume->material_id) {
|
||||
# it looks like this block is never entered because all input volumes seem to have an assigned material
|
||||
# TODO: check we can assume that for any input format
|
||||
|
@ -207,8 +208,11 @@ sub on_btn_load {
|
|||
$new_volume->material_id($material_name);
|
||||
}
|
||||
|
||||
# apply the same translation we applied to the object
|
||||
$new_volume->mesh->translate(@{$self->{model_object}->origin_translation}, 0);
|
||||
|
||||
# set a default extruder value, since user can't add it manually
|
||||
my $material = $self->{model_object}->model->materials->{$new_volume->material_id};
|
||||
my $material = $self->{model_object}->model->get_material($new_volume->material_id);
|
||||
$material->config->set_ifndef('extruder', 1);
|
||||
|
||||
$self->{parts_changed} = 1;
|
||||
|
|
|
@ -150,7 +150,7 @@ sub Closing {
|
|||
my $self = shift;
|
||||
|
||||
# save ranges into the plater object
|
||||
$self->model_object->layer_height_ranges([ $self->_get_ranges ]);
|
||||
$self->model_object->set_layer_height_ranges([ $self->_get_ranges ]);
|
||||
}
|
||||
|
||||
sub _get_ranges {
|
||||
|
|
|
@ -14,13 +14,18 @@ use Wx::GLCanvas qw(:all);
|
|||
__PACKAGE__->mk_accessors( qw(quat dirty init mview_init
|
||||
object_bounding_box object_shift
|
||||
volumes initpos
|
||||
sphi stheta) );
|
||||
sphi stheta
|
||||
cutting_plane_z
|
||||
) );
|
||||
|
||||
use constant TRACKBALLSIZE => 0.8;
|
||||
use constant TURNTABLE_MODE => 1;
|
||||
use constant SELECTED_COLOR => [0,1,0,1];
|
||||
use constant COLORS => [ [1,1,1], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
|
||||
|
||||
# make OpenGL::Array thread-safe
|
||||
*OpenGL::Array::CLONE_SKIP = sub { 1 };
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $object) = @_;
|
||||
my $self = $class->SUPER::new($parent);
|
||||
|
@ -114,6 +119,11 @@ sub load_object {
|
|||
}
|
||||
}
|
||||
|
||||
sub SetCuttingPlane {
|
||||
my ($self, $z) = @_;
|
||||
$self->cutting_plane_z($z);
|
||||
}
|
||||
|
||||
# Given an axis and angle, compute quaternion.
|
||||
sub axis_to_quat {
|
||||
my ($ax, $phi) = @_;
|
||||
|
@ -440,6 +450,23 @@ sub Render {
|
|||
glVertex3f($axis_len, $y, $ground_z);
|
||||
}
|
||||
glEnd();
|
||||
|
||||
# draw cutting plane
|
||||
if (defined $self->cutting_plane_z) {
|
||||
my $plane_z = $z0 + $self->cutting_plane_z;
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBegin(GL_QUADS);
|
||||
glColor4f(1, 0.8, 0.8, 0.5);
|
||||
glVertex3f(-$axis_len, -$axis_len, $plane_z);
|
||||
glVertex3f($axis_len, -$axis_len, $plane_z);
|
||||
glVertex3f($axis_len, $axis_len, $plane_z);
|
||||
glVertex3f(-$axis_len, $axis_len, $plane_z);
|
||||
glEnd();
|
||||
glEnable(GL_CULL_FACE);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
|
|
|
@ -134,6 +134,11 @@ sub quick_slice {
|
|||
$sprint->apply_config($config);
|
||||
$sprint->set_model(Slic3r::Model->read_from_file($input_file));
|
||||
|
||||
{
|
||||
my $extra = $self->extra_variables;
|
||||
$sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra;
|
||||
}
|
||||
|
||||
# select output file
|
||||
my $output_file;
|
||||
if ($params{reslice}) {
|
||||
|
@ -230,15 +235,6 @@ sub extra_variables {
|
|||
return { %extra_variables };
|
||||
}
|
||||
|
||||
sub init_print {
|
||||
my $self = shift;
|
||||
|
||||
return Slic3r::Print->new(
|
||||
config => $self->config,
|
||||
extra_variables => $self->extra_variables,
|
||||
);
|
||||
}
|
||||
|
||||
sub export_config {
|
||||
my $self = shift;
|
||||
|
||||
|
@ -479,16 +475,20 @@ sub config {
|
|||
} else {
|
||||
# TODO: handle dirty presets.
|
||||
# perhaps plater shouldn't expose dirty presets at all in multi-extruder environments.
|
||||
my $i = -1;
|
||||
foreach my $preset_idx ($self->{plater}->filament_presets) {
|
||||
$i++;
|
||||
my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx);
|
||||
my $config = $self->{options_tabs}{filament}->get_preset_config($preset);
|
||||
if (!$filament_config) {
|
||||
$filament_config = $config;
|
||||
$filament_config = $config->clone;
|
||||
next;
|
||||
}
|
||||
foreach my $opt_key (@{$config->get_keys}) {
|
||||
next unless ref $filament_config->get($opt_key) eq 'ARRAY';
|
||||
push @{ $filament_config->get($opt_key) }, $config->get($opt_key)->[0];
|
||||
my $value = $filament_config->get($opt_key);
|
||||
next unless ref $value eq 'ARRAY';
|
||||
$value->[$i] = $config->get($opt_key)->[0];
|
||||
$filament_config->set($opt_key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -268,7 +268,6 @@ sub add_options_page {
|
|||
my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}, %params, on_change => sub {
|
||||
$self->on_value_change(@_);
|
||||
$self->set_dirty(1);
|
||||
$self->on_presets_changed;
|
||||
});
|
||||
$page->Hide;
|
||||
$self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5);
|
||||
|
@ -312,6 +311,9 @@ sub update_tree {
|
|||
sub set_dirty {
|
||||
my $self = shift;
|
||||
my ($dirty) = @_;
|
||||
|
||||
return if $dirty and $self->is_dirty;
|
||||
return if (not $dirty) and (not $self->is_dirty);
|
||||
|
||||
my $selection = $self->{presets_choice}->GetSelection;
|
||||
my $i = $self->{dirty} // $selection; #/
|
||||
|
@ -346,7 +348,7 @@ sub load_presets {
|
|||
}];
|
||||
|
||||
my %presets = Slic3r::GUI->presets($self->name);
|
||||
foreach my $preset_name (keys %presets) {
|
||||
foreach my $preset_name (sort keys %presets) {
|
||||
push @{$self->{presets}}, {
|
||||
name => $preset_name,
|
||||
file => $presets{$preset_name},
|
||||
|
@ -415,21 +417,17 @@ sub build {
|
|||
},
|
||||
{
|
||||
title => 'Quality (slower slicing)',
|
||||
options => [qw(extra_perimeters avoid_crossing_perimeters start_perimeters_at_concave_points start_perimeters_at_non_overhang thin_walls overhangs)],
|
||||
options => [qw(extra_perimeters avoid_crossing_perimeters thin_walls overhangs)],
|
||||
lines => [
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('extra_perimeters'),
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('avoid_crossing_perimeters'),
|
||||
{
|
||||
label => 'Start perimeters at',
|
||||
options => [qw(start_perimeters_at_concave_points start_perimeters_at_non_overhang)],
|
||||
},
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('thin_walls'),
|
||||
Slic3r::GUI::OptionsGroup->single_option_line('overhangs'),
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'Advanced',
|
||||
options => [qw(randomize_start external_perimeters_first)],
|
||||
options => [qw(seam_position external_perimeters_first)],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -452,7 +450,7 @@ sub build {
|
|||
$self->add_options_page('Speed', 'time.png', optgroups => [
|
||||
{
|
||||
title => 'Speed for print moves',
|
||||
options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed bridge_speed gap_fill_speed)],
|
||||
options => [qw(perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed support_material_interface_speed bridge_speed gap_fill_speed)],
|
||||
},
|
||||
{
|
||||
title => 'Speed for non-print moves',
|
||||
|
@ -491,7 +489,8 @@ sub build {
|
|||
{
|
||||
title => 'Options for support material and raft',
|
||||
options => [qw(support_material_pattern support_material_spacing support_material_angle
|
||||
support_material_interface_layers support_material_interface_spacing)],
|
||||
support_material_interface_layers support_material_interface_spacing
|
||||
dont_support_bridges)],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -545,7 +544,7 @@ sub build {
|
|||
{
|
||||
title => 'Extrusion width',
|
||||
label_width => 180,
|
||||
options => [qw(extrusion_width first_layer_extrusion_width perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width top_infill_extrusion_width support_material_extrusion_width)],
|
||||
options => [qw(extrusion_width first_layer_extrusion_width perimeter_extrusion_width external_perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width top_infill_extrusion_width support_material_extrusion_width)],
|
||||
},
|
||||
{
|
||||
title => 'Flow',
|
||||
|
@ -553,7 +552,7 @@ sub build {
|
|||
},
|
||||
{
|
||||
title => 'Other',
|
||||
options => [($Slic3r::have_threads ? qw(threads) : ()), qw(resolution)],
|
||||
options => [($Slic3r::have_threads ? qw(threads) : ()), qw(resolution xy_size_compensation)],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
@ -681,6 +680,7 @@ sub build {
|
|||
options => [qw(gcode_flavor use_relative_e_distances)],
|
||||
},
|
||||
{
|
||||
class => 'Slic3r::GUI::OptionsGroup',
|
||||
title => 'Capabilities',
|
||||
options => [
|
||||
{
|
||||
|
@ -730,27 +730,28 @@ sub build {
|
|||
sub _extruder_options { qw(nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel wipe
|
||||
retract_layer_change retract_length_toolchange retract_restart_extra_toolchange) }
|
||||
|
||||
sub config {
|
||||
my $self = shift;
|
||||
|
||||
my $config = $self->SUPER::config(@_);
|
||||
|
||||
# remove all unused values
|
||||
foreach my $opt_key ($self->_extruder_options) {
|
||||
my $values = $config->get($opt_key);
|
||||
splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values;
|
||||
$config->set($opt_key, $values);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
sub _build_extruder_pages {
|
||||
my $self = shift;
|
||||
|
||||
foreach my $extruder_idx (0 .. $self->{extruders_count}-1) {
|
||||
# build page if it doesn't exist
|
||||
$self->{extruder_pages}[$extruder_idx] ||= $self->add_options_page("Extruder " . ($extruder_idx + 1), 'funnel.png', optgroups => [
|
||||
my $default_config = Slic3r::Config::Full->new;
|
||||
|
||||
foreach my $extruder_idx (@{$self->{extruder_pages}} .. $self->{extruders_count}-1) {
|
||||
# extend options
|
||||
foreach my $opt_key ($self->_extruder_options) {
|
||||
my $values = $self->{config}->get($opt_key);
|
||||
if (!defined $values) {
|
||||
$values = [ $default_config->get_at($opt_key, 0) ];
|
||||
} else {
|
||||
# use last extruder's settings for the new one
|
||||
my $last_value = $values->[-1];
|
||||
$values->[$extruder_idx] //= $last_value;
|
||||
}
|
||||
$self->{config}->set($opt_key, $values)
|
||||
or die "Unable to extend $opt_key";
|
||||
}
|
||||
|
||||
# build page
|
||||
$self->{extruder_pages}[$extruder_idx] = $self->add_options_page("Extruder " . ($extruder_idx + 1), 'funnel.png', optgroups => [
|
||||
{
|
||||
title => 'Size',
|
||||
options => ['nozzle_diameter#' . $extruder_idx],
|
||||
|
@ -777,6 +778,19 @@ sub _build_extruder_pages {
|
|||
$self->{extruder_pages}[$extruder_idx]{disabled} = 0;
|
||||
}
|
||||
|
||||
# remove extra pages
|
||||
if ($self->{extruders_count} <= $#{$self->{extruder_pages}}) {
|
||||
splice @{$self->{extruder_pages}}, $self->{extruders_count};
|
||||
}
|
||||
|
||||
# remove extra config values
|
||||
foreach my $opt_key ($self->_extruder_options) {
|
||||
my $values = $self->{config}->get($opt_key);
|
||||
splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values;
|
||||
$self->{config}->set($opt_key, $values)
|
||||
or die "Unable to truncate $opt_key";
|
||||
}
|
||||
|
||||
# rebuild page list
|
||||
@{$self->{pages}} = (
|
||||
(grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}),
|
||||
|
@ -790,14 +804,7 @@ sub on_value_change {
|
|||
$self->SUPER::on_value_change(@_);
|
||||
|
||||
if ($opt_key eq 'extruders_count') {
|
||||
# remove unused pages from list
|
||||
my @unused_pages = @{ $self->{extruder_pages} }[$self->{extruders_count} .. $#{$self->{extruder_pages}}];
|
||||
for my $page (@unused_pages) {
|
||||
@{$self->{pages}} = grep $_ ne $page, @{$self->{pages}};
|
||||
$page->{disabled} = 1;
|
||||
}
|
||||
|
||||
# add extra pages
|
||||
# add extra pages or remove unused
|
||||
$self->_build_extruder_pages;
|
||||
|
||||
# update page list and select first page (General)
|
||||
|
|
|
@ -20,7 +20,7 @@ our @EXPORT_OK = qw(
|
|||
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
|
||||
polyline_remove_short_segments normal triangle_normal polygon_is_convex
|
||||
scaled_epsilon bounding_box_3D size_3D size_2D
|
||||
convex_hull
|
||||
convex_hull directions_parallel directions_parallel_within
|
||||
);
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,6 @@ our @EXPORT_OK = qw(offset offset_ex
|
|||
diff_ex diff union_ex intersection_ex xor_ex JT_ROUND JT_MITER
|
||||
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex
|
||||
intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE
|
||||
union_pt_chained);
|
||||
union_pt_chained diff_ppl intersection_ppl);
|
||||
|
||||
1;
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
package Slic3r::Layer;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(scale);
|
||||
use Slic3r::Geometry qw(scale chained_path);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex);
|
||||
|
||||
has 'id' => (is => 'rw', required => 1); # sequential number of layer, 0-based
|
||||
has 'object' => (is => 'ro', weak_ref => 1, required => 1, handles => [qw(print config)]);
|
||||
has 'upper_layer' => (is => 'rw', weak_ref => 1);
|
||||
has 'regions' => (is => 'ro', default => sub { [] });
|
||||
has 'slicing_errors' => (is => 'rw');
|
||||
# the following two were previously generated by Moo
|
||||
sub print {
|
||||
my $self = shift;
|
||||
return $self->object->print;
|
||||
}
|
||||
|
||||
has 'slice_z' => (is => 'ro', required => 1); # Z used for slicing in unscaled coordinates
|
||||
has 'print_z' => (is => 'ro', required => 1); # Z used for printing in unscaled coordinates
|
||||
has 'height' => (is => 'ro', required => 1); # layer height in unscaled coordinates
|
||||
|
||||
# collection of expolygons generated by slicing the original geometry;
|
||||
# also known as 'islands' (all regions and surface types are merged here)
|
||||
has 'slices' => (is => 'rw', default => sub { Slic3r::ExPolygon::Collection->new });
|
||||
sub config {
|
||||
my $self = shift;
|
||||
return $self->object->config;
|
||||
}
|
||||
|
||||
# the purpose of this method is to be overridden for ::Support layers
|
||||
sub islands {
|
||||
|
@ -29,21 +27,32 @@ sub region {
|
|||
my $self = shift;
|
||||
my ($region_id) = @_;
|
||||
|
||||
for (my $i = @{$self->regions}; $i <= $region_id; $i++) {
|
||||
$self->regions->[$i] //= Slic3r::Layer::Region->new(
|
||||
layer => $self,
|
||||
region => $self->object->print->regions->[$i],
|
||||
);
|
||||
while ($self->region_count <= $region_id) {
|
||||
$self->add_region($self->object->print->get_region($self->region_count));
|
||||
}
|
||||
|
||||
return $self->regions->[$region_id];
|
||||
return $self->get_region($region_id);
|
||||
}
|
||||
|
||||
sub regions {
|
||||
my ($self) = @_;
|
||||
return [ map $self->get_region($_), 0..($self->region_count-1) ];
|
||||
}
|
||||
|
||||
# merge all regions' slices to get islands
|
||||
sub make_slices {
|
||||
my $self = shift;
|
||||
|
||||
my $slices = union_ex([ map $_->p, map @{$_->slices}, @{$self->regions} ]);
|
||||
my $slices;
|
||||
if (@{$self->regions} == 1) {
|
||||
$slices = [ map $_->expolygon->clone, @{$self->regions->[0]->slices} ];
|
||||
} else {
|
||||
$slices = union_ex([ map $_->p, map @{$_->slices}, @{$self->regions} ]);
|
||||
}
|
||||
|
||||
# sort slices
|
||||
$slices = [ @$slices[@{chained_path([ map $_->contour->first_point, @$slices ])}] ];
|
||||
|
||||
$self->slices->clear;
|
||||
$self->slices->append(@$slices);
|
||||
}
|
||||
|
@ -55,13 +64,8 @@ sub make_perimeters {
|
|||
}
|
||||
|
||||
package Slic3r::Layer::Support;
|
||||
use Moo;
|
||||
extends 'Slic3r::Layer';
|
||||
|
||||
# ordered collection of extrusion paths to fill surfaces for support material
|
||||
has 'support_islands' => (is => 'rw', default => sub { Slic3r::ExPolygon::Collection->new });
|
||||
has 'support_fills' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
has 'support_interface_fills' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
our @ISA = qw(Slic3r::Layer);
|
||||
|
||||
sub islands {
|
||||
my $self = shift;
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
package Slic3r::Layer::BridgeDetector;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first sum max min);
|
||||
use Slic3r::Geometry qw(PI unscale scaled_epsilon rad2deg epsilon directions_parallel_within);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex
|
||||
intersection_ppl);
|
||||
|
||||
has 'expolygon' => (is => 'ro', required => 1);
|
||||
has 'lower_slices' => (is => 'rw', required => 1); # ExPolygons or ExPolygonCollection
|
||||
has 'extrusion_width' => (is => 'rw', required => 1); # scaled
|
||||
has 'resolution' => (is => 'rw', default => sub { PI/36 });
|
||||
|
||||
has '_edges' => (is => 'rw'); # Polylines representing the supporting edges
|
||||
has '_anchors' => (is => 'rw'); # ExPolygons
|
||||
has 'angle' => (is => 'rw');
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
# outset our bridge by an arbitrary amout; we'll use this outer margin
|
||||
# for detecting anchors
|
||||
my $grown = $self->expolygon->offset(+$self->extrusion_width);
|
||||
|
||||
# detect what edges lie on lower slices
|
||||
$self->_edges(my $edges = []);
|
||||
foreach my $lower (@{$self->lower_slices}) {
|
||||
# turn bridge contour and holes into polylines and then clip them
|
||||
# with each lower slice's contour
|
||||
push @$edges, @{intersection_ppl($grown, [ $lower->contour ])};
|
||||
}
|
||||
Slic3r::debugf " bridge has %d support(s)\n", scalar(@$edges);
|
||||
|
||||
# detect anchors as intersection between our bridge expolygon and the lower slices
|
||||
$self->_anchors(intersection_ex(
|
||||
$grown,
|
||||
[ map @$_, @{$self->lower_slices} ],
|
||||
1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges
|
||||
));
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("bridge.svg",
|
||||
expolygons => [ $self->expolygon ],
|
||||
red_expolygons => $self->lower_slices,
|
||||
polylines => $self->_edges,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sub detect_angle {
|
||||
my ($self) = @_;
|
||||
|
||||
return undef if !@{$self->_edges};
|
||||
|
||||
my @edges = @{$self->_edges};
|
||||
my $anchors = $self->_anchors;
|
||||
|
||||
if (!@$anchors) {
|
||||
$self->angle(undef);
|
||||
return undef;
|
||||
}
|
||||
|
||||
# Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
# we'll use this one to clip our test lines and be sure that their endpoints
|
||||
# are inside the anchors and not on their contours leading to false negatives.
|
||||
my $clip_area = $self->expolygon->offset_ex(+$self->extrusion_width/2);
|
||||
|
||||
# we'll now try several directions using a rudimentary visibility check:
|
||||
# bridge in several directions and then sum the length of lines having both
|
||||
# endpoints within anchors
|
||||
|
||||
# we test angles according to configured resolution
|
||||
my @angles = map { $_*$self->resolution } 0..(PI/$self->resolution);
|
||||
|
||||
# we also test angles of each bridge contour
|
||||
push @angles, map $_->direction, map @{$_->lines}, @{$self->expolygon};
|
||||
|
||||
# we also test angles of each open supporting edge
|
||||
# (this finds the optimal angle for C-shaped supports)
|
||||
push @angles,
|
||||
map Slic3r::Line->new($_->first_point, $_->last_point)->direction,
|
||||
grep { !$_->first_point->coincides_with($_->last_point) }
|
||||
@edges;
|
||||
|
||||
# remove duplicates
|
||||
my $min_resolution = PI/180; # 1 degree
|
||||
# proceed in reverse order so that when we compare first value with last one (-1)
|
||||
# we remove the greatest one (PI) in case they are parallel (PI, 0)
|
||||
@angles = reverse sort @angles;
|
||||
for (my $i = 0; $i <= $#angles; ++$i) {
|
||||
if (directions_parallel_within($angles[$i], $angles[$i-1], $min_resolution)) {
|
||||
splice @angles, $i, 1;
|
||||
--$i;
|
||||
}
|
||||
}
|
||||
|
||||
my %directions_coverage = (); # angle => score
|
||||
my %directions_avg_length = (); # angle => score
|
||||
my $line_increment = $self->extrusion_width;
|
||||
my %unique_angles = map { $_ => 1 } @angles;
|
||||
for my $angle (@angles) {
|
||||
my $my_clip_area = [ map $_->clone, @$clip_area ];
|
||||
my $my_anchors = [ map $_->clone, @$anchors ];
|
||||
|
||||
# rotate everything - the center point doesn't matter
|
||||
$_->rotate(-$angle, [0,0]) for @$my_clip_area, @$my_anchors;
|
||||
|
||||
# generate lines in this direction
|
||||
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$my_anchors ]);
|
||||
|
||||
my @lines = ();
|
||||
for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y+= $line_increment) {
|
||||
push @lines, Slic3r::Polyline->new(
|
||||
[$bounding_box->x_min, $y],
|
||||
[$bounding_box->x_max, $y],
|
||||
);
|
||||
}
|
||||
|
||||
my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$my_clip_area ]) };
|
||||
|
||||
# remove any line not having both endpoints within anchors
|
||||
@clipped_lines = grep {
|
||||
my $line = $_;
|
||||
(first { $_->contains_point($line->a) } @$my_anchors)
|
||||
&& (first { $_->contains_point($line->b) } @$my_anchors);
|
||||
} @clipped_lines;
|
||||
|
||||
my @lengths = map $_->length, @clipped_lines;
|
||||
|
||||
# sum length of bridged lines
|
||||
$directions_coverage{$angle} = sum(@lengths) // 0;
|
||||
|
||||
### The following produces more correct results in some cases and more broken in others.
|
||||
### TODO: investigate, as it looks more reliable than line clipping.
|
||||
###$directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0;
|
||||
|
||||
# max length of bridged lines
|
||||
$directions_avg_length{$angle} = @lengths ? (max(@lengths)) : -1;
|
||||
}
|
||||
|
||||
# if no direction produced coverage, then there's no bridge direction
|
||||
return undef if !defined first { $_ > 0 } values %directions_coverage;
|
||||
|
||||
# the best direction is the one causing most lines to be bridged (thus most coverage)
|
||||
# and shortest max line length
|
||||
my @sorted_directions = sort {
|
||||
my $cmp;
|
||||
my $coverage_diff = $directions_coverage{$a} - $directions_coverage{$b};
|
||||
if (abs($coverage_diff) < $self->extrusion_width) {
|
||||
$cmp = $directions_avg_length{$b} <=> $directions_avg_length{$a};
|
||||
} else {
|
||||
$cmp = ($coverage_diff > 0) ? 1 : -1;
|
||||
}
|
||||
$cmp;
|
||||
} keys %directions_coverage;
|
||||
|
||||
$self->angle($sorted_directions[-1]);
|
||||
|
||||
if ($self->angle >= PI) {
|
||||
$self->angle($self->angle - PI);
|
||||
}
|
||||
|
||||
Slic3r::debugf " Optimal infill angle is %d degrees\n", rad2deg($self->angle);
|
||||
|
||||
return $self->angle;
|
||||
}
|
||||
|
||||
sub coverage {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
if (!defined $angle) {
|
||||
return [] if !defined($angle = $self->angle);
|
||||
}
|
||||
|
||||
# Clone our expolygon and rotate it so that we work with vertical lines.
|
||||
my $expolygon = $self->expolygon->clone;
|
||||
$expolygon->rotate(PI/2 - $angle, [0,0]);
|
||||
|
||||
# Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
# we'll use this one to generate our trapezoids and be sure that their vertices
|
||||
# are inside the anchors and not on their contours leading to false negatives.
|
||||
my $grown = $expolygon->offset_ex(+$self->extrusion_width/2);
|
||||
|
||||
# Compute trapezoids according to a vertical orientation
|
||||
my $trapezoids = [ map @{$_->get_trapezoids2(PI/2)}, @$grown ];
|
||||
|
||||
# get anchors and rotate them too
|
||||
my $anchors = [ map $_->clone, @{$self->_anchors} ];
|
||||
$_->rotate(PI/2 - $angle, [0,0]) for @$anchors;
|
||||
|
||||
my @covered = (); # polygons
|
||||
foreach my $trapezoid (@$trapezoids) {
|
||||
my @polylines = map $_->as_polyline, @{$trapezoid->lines};
|
||||
my @supported = @{intersection_pl(\@polylines, [map @$_, @$anchors])};
|
||||
|
||||
# not nice, we need a more robust non-numeric check
|
||||
@supported = grep $_->length >= $self->extrusion_width, @supported;
|
||||
|
||||
if (@supported >= 2) {
|
||||
push @covered, $trapezoid;
|
||||
}
|
||||
}
|
||||
|
||||
# merge trapezoids and rotate them back
|
||||
my $coverage = union(\@covered);
|
||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @$coverage;
|
||||
|
||||
# intersect trapezoids with actual bridge area to remove extra margins
|
||||
$coverage = intersection_ex($coverage, [ @{$self->expolygon} ]);
|
||||
|
||||
if (0) {
|
||||
my @lines = map @{$_->lines}, @$trapezoids;
|
||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
|
||||
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"coverage_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchors,
|
||||
red_expolygons => $coverage,
|
||||
lines => \@lines,
|
||||
);
|
||||
}
|
||||
|
||||
return $coverage;
|
||||
}
|
||||
|
||||
# this method returns the bridge edges (as polylines) that are not supported
|
||||
# but would allow the entire bridge area to be bridged with detected angle
|
||||
# if supported too
|
||||
sub unsupported_edges {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
if (!defined $angle) {
|
||||
return [] if !defined($angle = $self->angle);
|
||||
}
|
||||
|
||||
# get bridge edges (both contour and holes)
|
||||
my @bridge_edges = map $_->split_at_first_point, @{$self->expolygon};
|
||||
$_->[0]->translate(1,0) for @bridge_edges; # workaround for Clipper bug, see comments in Slic3r::Polygon::clip_as_polyline()
|
||||
|
||||
# get unsupported edges
|
||||
my $grown_lower = offset([ map @$_, @{$self->lower_slices} ], +$self->extrusion_width);
|
||||
my $unsupported = diff_pl(
|
||||
\@bridge_edges,
|
||||
$grown_lower,
|
||||
);
|
||||
|
||||
# split into individual segments and filter out edges parallel to the bridging angle
|
||||
# TODO: angle tolerance should probably be based on segment length and flow width,
|
||||
# so that we build supports whenever there's a chance that at least one or two bridge
|
||||
# extrusions would be anchored within such length (i.e. a slightly non-parallel bridging
|
||||
# direction might still benefit from anchors if long enough)
|
||||
my $angle_tolerance = PI/180*5;
|
||||
@$unsupported = map $_->as_polyline,
|
||||
grep !directions_parallel_within($_->direction, $angle, $angle_tolerance),
|
||||
map @{$_->lines},
|
||||
@$unsupported;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"unsupported_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchors,
|
||||
red_expolygons => union_ex($grown_lower),
|
||||
no_arrows => 1,
|
||||
polylines => \@bridge_edges,
|
||||
red_polylines => $unsupported,
|
||||
);
|
||||
}
|
||||
|
||||
return $unsupported;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,52 +1,33 @@
|
|||
package Slic3r::Layer::Region;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(sum first);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(PI A B scale unscale chained_path points_coincide);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex
|
||||
offset offset_ex offset2 offset2_ex union_pt diff intersection
|
||||
union diff intersection_pl);
|
||||
union diff intersection_ppl diff_ppl);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
has 'layer' => (
|
||||
is => 'ro',
|
||||
weak_ref => 1,
|
||||
required => 1,
|
||||
handles => [qw(id slice_z print_z height object print)],
|
||||
);
|
||||
has 'region' => (is => 'ro', required => 1, handles => [qw(config)]);
|
||||
has 'infill_area_threshold' => (is => 'lazy');
|
||||
has 'overhang_width' => (is => 'lazy');
|
||||
|
||||
# collection of surfaces generated by slicing the original geometry
|
||||
# divided by type top/bottom/internal
|
||||
has 'slices' => (is => 'rw', default => sub { Slic3r::Surface::Collection->new });
|
||||
|
||||
# collection of extrusion paths/loops filling gaps
|
||||
has 'thin_fills' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
|
||||
# collection of surfaces for infill generation
|
||||
has 'fill_surfaces' => (is => 'rw', default => sub { Slic3r::Surface::Collection->new });
|
||||
|
||||
# ordered collection of extrusion paths/loops to build all perimeters
|
||||
has 'perimeters' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
|
||||
# ordered collection of extrusion paths to fill surfaces
|
||||
has 'fills' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
|
||||
sub _build_overhang_width {
|
||||
my $self = shift;
|
||||
my $threshold_rad = PI/2 - atan2($self->flow(FLOW_ROLE_PERIMETER)->width / $self->height / 2, 1);
|
||||
return scale($self->height * ((cos $threshold_rad) / (sin $threshold_rad)));
|
||||
}
|
||||
|
||||
sub _build_infill_area_threshold {
|
||||
# TODO: lazy
|
||||
sub infill_area_threshold {
|
||||
my $self = shift;
|
||||
return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2;
|
||||
}
|
||||
|
||||
sub id { return $_[0]->layer->id; }
|
||||
sub slice_z { return $_[0]->layer->slice_z; }
|
||||
sub print_z { return $_[0]->layer->print_z; }
|
||||
sub height { return $_[0]->layer->height; }
|
||||
sub object { return $_[0]->layer->object; }
|
||||
sub print { return $_[0]->layer->print; }
|
||||
|
||||
sub config { return $_[0]->region->config; }
|
||||
|
||||
sub flow {
|
||||
my ($self, $role, $bridge, $width) = @_;
|
||||
return $self->region->flow(
|
||||
|
@ -55,20 +36,42 @@ sub flow {
|
|||
$bridge // 0,
|
||||
$self->layer->id == 0,
|
||||
$width,
|
||||
$self->object,
|
||||
);
|
||||
}
|
||||
|
||||
sub make_perimeters {
|
||||
my $self = shift;
|
||||
|
||||
# other perimeters
|
||||
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
|
||||
my $mm3_per_mm = $perimeter_flow->mm3_per_mm($self->height);
|
||||
my $mm3_per_mm = $perimeter_flow->mm3_per_mm;
|
||||
my $pwidth = $perimeter_flow->scaled_width;
|
||||
my $pspacing = $perimeter_flow->scaled_spacing;
|
||||
|
||||
# external perimeters
|
||||
my $ext_perimeter_flow = $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
|
||||
my $ext_mm3_per_mm = $ext_perimeter_flow->mm3_per_mm;
|
||||
my $ext_pwidth = $ext_perimeter_flow->scaled_width;
|
||||
my $ext_pspacing = scale($ext_perimeter_flow->spacing_to($perimeter_flow));
|
||||
|
||||
# overhang perimeters
|
||||
my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, undef, $self->layer->object);
|
||||
my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm;
|
||||
|
||||
# solid infill
|
||||
my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
my $ispacing = $solid_infill_flow->scaled_spacing;
|
||||
my $gap_area_threshold = $pwidth ** 2;
|
||||
|
||||
# Calculate the minimum required spacing between two adjacent traces.
|
||||
# This should be equal to the nominal flow spacing but we experiment
|
||||
# with some tolerance in order to avoid triggering medial axis when
|
||||
# some squishing might work. Loops are still spaced by the entire
|
||||
# flow spacing; this only applies to collapsing parts.
|
||||
my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
|
||||
$self->perimeters->clear;
|
||||
$self->fill_surfaces->clear;
|
||||
$self->thin_fills->clear;
|
||||
|
@ -76,7 +79,6 @@ sub make_perimeters {
|
|||
my @contours = (); # array of Polygons with ccw orientation
|
||||
my @holes = (); # array of Polygons with cw orientation
|
||||
my @thin_walls = (); # array of ExPolygons
|
||||
my @gaps = (); # array of ExPolygons
|
||||
|
||||
# we need to process each island separately because we might have different
|
||||
# extra perimeters for each one
|
||||
|
@ -85,35 +87,48 @@ sub make_perimeters {
|
|||
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
|
||||
|
||||
my @last = @{$surface->expolygon};
|
||||
my @last_gaps = ();
|
||||
my @gaps = (); # array of ExPolygons
|
||||
if ($loop_number > 0) {
|
||||
# we loop one time more than needed in order to find gaps after the last perimeter was applied
|
||||
for my $i (1 .. ($loop_number+1)) { # outer loop is 1
|
||||
my @offsets = ();
|
||||
if ($i == 1) {
|
||||
# the minimum thickness of a single loop is:
|
||||
# width/2 + spacing/2 + spacing/2 + width/2
|
||||
@offsets = @{offset2(\@last, -(0.5*$pwidth + 0.5*$pspacing - 1), +(0.5*$pspacing - 1))};
|
||||
# ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
||||
@offsets = @{offset2(
|
||||
\@last,
|
||||
-(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
|
||||
+(0.5*$ext_min_spacing - 1),
|
||||
)};
|
||||
|
||||
# look for thin walls
|
||||
if ($self->config->thin_walls) {
|
||||
my $diff = diff_ex(
|
||||
\@last,
|
||||
offset(\@offsets, +0.5*$pwidth),
|
||||
offset(\@offsets, +0.5*$ext_pwidth),
|
||||
1, # medial axis requires non-overlapping geometry
|
||||
);
|
||||
push @thin_walls, @$diff;
|
||||
}
|
||||
} else {
|
||||
@offsets = @{offset2(\@last, -(1.5*$pspacing - 1), +(0.5*$pspacing - 1))};
|
||||
my $distance = ($i == 2) ? $ext_pspacing : $pspacing;
|
||||
|
||||
@offsets = @{offset2(
|
||||
\@last,
|
||||
-($distance + 0.5*$min_spacing - 1),
|
||||
+(0.5*$min_spacing - 1),
|
||||
)};
|
||||
|
||||
# look for gaps
|
||||
if ($self->print->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
|
||||
if ($self->region->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
|
||||
# not using safety offset here would "detect" very narrow gaps
|
||||
# (but still long enough to escape the area threshold) that gap fill
|
||||
# won't be able to fill but we'd still remove from infill area
|
||||
my $diff = diff_ex(
|
||||
offset(\@last, -0.5*$pspacing),
|
||||
offset(\@offsets, +0.5*$pspacing),
|
||||
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
|
||||
);
|
||||
push @gaps, @last_gaps = grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,24 +147,57 @@ sub make_perimeters {
|
|||
}
|
||||
}
|
||||
|
||||
# make sure we don't infill narrow parts that are already gap-filled
|
||||
# (we only consider this surface's gaps to reduce the diff() complexity)
|
||||
@last = @{diff(\@last, [ map @$_, @last_gaps ])};
|
||||
# fill gaps
|
||||
if (@gaps) {
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"gaps.svg",
|
||||
expolygons => \@gaps,
|
||||
);
|
||||
}
|
||||
|
||||
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
|
||||
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
|
||||
my @gap_sizes = (
|
||||
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
|
||||
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
|
||||
);
|
||||
foreach my $gap_size (@gap_sizes) {
|
||||
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
|
||||
$self->thin_fills->append(@gap_fill);
|
||||
|
||||
# Make sure we don't infill narrow parts that are already gap-filled
|
||||
# (we only consider this surface's gaps to reduce the diff() complexity).
|
||||
# Growing actual extrusions ensures that gaps not filled by medial axis
|
||||
# are not subtracted from fill surfaces (they might be too short gaps
|
||||
# that medial axis skips but infill might join with other infill regions
|
||||
# and use zigzag).
|
||||
my $w = $gap_size->[2];
|
||||
my @filled = map {
|
||||
@{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
|
||||
->grow(scale $w/2)};
|
||||
} @gap_fill;
|
||||
@last = @{diff(\@last, \@filled)};
|
||||
}
|
||||
}
|
||||
|
||||
# create one more offset to be used as boundary for fill
|
||||
# we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||
# and then we offset back and forth by half the infill spacing to only consider the
|
||||
# non-collapsing regions
|
||||
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
$self->fill_surfaces->append(
|
||||
map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
|
||||
@{offset2_ex(
|
||||
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
|
||||
-($pspacing/2 + $ispacing/2),
|
||||
+$ispacing/2,
|
||||
-($pspacing/2 + $min_perimeter_infill_spacing/2),
|
||||
+$min_perimeter_infill_spacing/2,
|
||||
)}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
# process thin walls by collapsing slices to single passes
|
||||
my @thin_wall_polylines = ();
|
||||
if (@thin_walls) {
|
||||
|
@ -178,6 +226,19 @@ sub make_perimeters {
|
|||
my $contours_pt = union_pt(\@contours);
|
||||
my $holes_pt = union_pt(\@holes);
|
||||
|
||||
# prepare grown lower layer slices for overhang detection
|
||||
my $lower_slices = Slic3r::ExPolygon::Collection->new;
|
||||
if ($self->layer->lower_layer && $self->region->config->overhangs) {
|
||||
# We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
# in the current layer
|
||||
my $nozzle_diameter = $self->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1);
|
||||
$lower_slices->append(
|
||||
@{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)},
|
||||
);
|
||||
}
|
||||
my $lower_slices_p = $lower_slices->polygons;
|
||||
|
||||
# prepare a coderef for traversing the PolyTree object
|
||||
# external contours are root items of $contours_pt
|
||||
# internal contours are the ones next to external
|
||||
|
@ -191,29 +252,81 @@ sub make_perimeters {
|
|||
foreach my $polynode (@$polynodes) {
|
||||
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
|
||||
|
||||
my $role = EXTR_ROLE_PERIMETER;
|
||||
my $loop_role = EXTRL_ROLE_DEFAULT;
|
||||
|
||||
my $root_level = $depth == 0;
|
||||
my $no_children = !@{ $polynode->{children} };
|
||||
my $is_external = $is_contour ? $root_level : $no_children;
|
||||
my $is_internal = $is_contour ? $no_children : $root_level;
|
||||
if ($is_external) {
|
||||
# external perimeters are root level in case of contours
|
||||
# and items with no children in case of holes
|
||||
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
|
||||
$loop_role = EXTRL_ROLE_EXTERNAL_PERIMETER;
|
||||
} elsif ($is_contour && $is_internal) {
|
||||
# internal perimeters are root level in case of holes
|
||||
# and items with no children in case of contours
|
||||
$loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
|
||||
}
|
||||
|
||||
# detect overhanging/bridging perimeters
|
||||
my @paths = ();
|
||||
if ($self->region->config->overhangs && $lower_slices->count > 0) {
|
||||
# get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => $role,
|
||||
mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $mm3_per_mm),
|
||||
width => ($is_external ? $ext_perimeter_flow->width : $perimeter_flow->width),
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
|
||||
# get overhang paths by checking what parts of this loop fall
|
||||
# outside the grown lower slices (thus where the distance between
|
||||
# the loop centerline and original lower slices is >= half nozzle diameter
|
||||
foreach my $polyline (@{ diff_ppl([ $polygon ], $lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_OVERHANG_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm_overhang,
|
||||
width => $overhang_flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
|
||||
# reapply the nearest point search for starting point
|
||||
# (clone because the collection gets DESTROY'ed)
|
||||
# We allow polyline reversal because Clipper may have randomly
|
||||
# reversed polylines during clipping.
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths);
|
||||
@paths = map $_->clone, @{$collection->chained_path(0)};
|
||||
} else {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polygon->split_at_first_point,
|
||||
role => $role,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $perimeter_flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
|
||||
$loop->role($loop_role);
|
||||
|
||||
# return ccw contours and cw holes
|
||||
# GCode.pm will convert all of them to ccw, but it needs to know
|
||||
# what the holes are in order to compute the correct inwards move
|
||||
# We do this on the final Loop object instead of the polygon because
|
||||
# overhang clipping might have reversed its order since Clipper does
|
||||
# not preserve polyline orientation.
|
||||
if ($is_contour) {
|
||||
$polygon->make_counter_clockwise;
|
||||
$loop->make_counter_clockwise;
|
||||
} else {
|
||||
$polygon->make_clockwise;
|
||||
$loop->make_clockwise;
|
||||
}
|
||||
|
||||
my $role = EXTR_ROLE_PERIMETER;
|
||||
if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) {
|
||||
# external perimeters are root level in case of contours
|
||||
# and items with no children in case of holes
|
||||
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
|
||||
} elsif ($depth == 1 && $is_contour) {
|
||||
$role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER;
|
||||
}
|
||||
|
||||
$collection->append(Slic3r::ExtrusionLoop->new(
|
||||
polygon => $polygon,
|
||||
role => $role,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
));
|
||||
$collection->append($loop);
|
||||
|
||||
# save the children
|
||||
push @children, $polynode->{children};
|
||||
|
@ -227,14 +340,24 @@ sub make_perimeters {
|
|||
polyline => $polyline,
|
||||
role => EXTR_ROLE_EXTERNAL_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $perimeter_flow->width,
|
||||
height => $self->height,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
# use a nearest neighbor search to order these children
|
||||
# TODO: supply second argument to chained_path() too?
|
||||
my $sorted_collection = $collection->chained_path_indices(0);
|
||||
my @orig_indices = @{$sorted_collection->orig_indices};
|
||||
# Optimization: since islands are going to be sorted by slice anyway in the
|
||||
# G-code export process, we skip chained_path here
|
||||
my ($sorted_collection, @orig_indices);
|
||||
if ($is_contour && $depth == 0) {
|
||||
$sorted_collection = $collection;
|
||||
@orig_indices = (0..$#$sorted_collection);
|
||||
} else {
|
||||
$sorted_collection = $collection->chained_path_indices(0);
|
||||
@orig_indices = @{$sorted_collection->orig_indices};
|
||||
}
|
||||
|
||||
my @loops = ();
|
||||
foreach my $loop (@$sorted_collection) {
|
||||
|
@ -276,49 +399,58 @@ sub make_perimeters {
|
|||
# we continue inwards after having finished the brim
|
||||
# TODO: add test for perimeter order
|
||||
@loops = reverse @loops
|
||||
if $self->print->config->external_perimeters_first
|
||||
if $self->region->config->external_perimeters_first
|
||||
|| ($self->layer->id == 0 && $self->print->config->brim_width > 0);
|
||||
|
||||
# append perimeters
|
||||
$self->perimeters->append(@loops);
|
||||
|
||||
# fill gaps
|
||||
{
|
||||
my $fill_gaps = sub {
|
||||
my ($min, $max, $w) = @_;
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2([ map @$_, @gaps ], -$min/2, +$min/2),
|
||||
offset2([ map @$_, @gaps ], -$max/2, +$max/2),
|
||||
1,
|
||||
);
|
||||
|
||||
my $flow = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
|
||||
my %path_args = (
|
||||
role => EXTR_ROLE_GAPFILL,
|
||||
mm3_per_mm => $flow->mm3_per_mm($self->height),
|
||||
);
|
||||
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
|
||||
$self->thin_fills->append(map {
|
||||
$_->isa('Slic3r::Polygon')
|
||||
? Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point # should we keep these as loops?
|
||||
: Slic3r::ExtrusionPath->new(polyline => $_, %path_args),
|
||||
} @polylines);
|
||||
}
|
||||
|
||||
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
|
||||
if @$this;
|
||||
};
|
||||
|
||||
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
|
||||
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
|
||||
$fill_gaps->($pwidth, 2*$pspacing, unscale 1.5*$pwidth);
|
||||
$fill_gaps->(0.5*$pwidth, $pwidth, unscale 0.5*$pwidth);
|
||||
sub _fill_gaps {
|
||||
my ($self, $min, $max, $w, $gaps) = @_;
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
|
||||
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
|
||||
1,
|
||||
);
|
||||
|
||||
my $flow = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
|
||||
my %path_args = (
|
||||
role => EXTR_ROLE_GAPFILL,
|
||||
mm3_per_mm => $flow->mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
|
||||
|
||||
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
|
||||
if @$this;
|
||||
|
||||
for my $i (0..$#polylines) {
|
||||
if ($polylines[$i]->isa('Slic3r::Polygon')) {
|
||||
my $loop = Slic3r::ExtrusionLoop->new;
|
||||
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
|
||||
$polylines[$i] = $loop;
|
||||
} elsif ($polylines[$i]->is_valid && $polylines[$i]->first_point->coincides_with($polylines[$i]->last_point)) {
|
||||
# since medial_axis() now returns only Polyline objects, detect loops here
|
||||
my $loop = Slic3r::ExtrusionLoop->new;
|
||||
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args));
|
||||
$polylines[$i] = $loop;
|
||||
} else {
|
||||
$polylines[$i] = Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args);
|
||||
}
|
||||
}
|
||||
return @polylines;
|
||||
}
|
||||
|
||||
sub prepare_fill_surfaces {
|
||||
my $self = shift;
|
||||
|
||||
# Note: in order to make the psPrepareInfill step idempotent, we should never
|
||||
# alter fill_surfaces boundaries on which our idempotency relies since that's
|
||||
# the only meaningful information returned by psPerimeters.
|
||||
|
||||
# if no solid layers are requested, turn top/bottom surfaces to internal
|
||||
if ($self->config->top_solid_layers == 0) {
|
||||
$_->surface_type(S_TYPE_INTERNAL) for @{$self->fill_surfaces->filter_by_type(S_TYPE_TOP)};
|
||||
|
@ -350,9 +482,21 @@ sub process_external_surfaces {
|
|||
# would get merged into a single one while they need different directions
|
||||
# also, supply the original expolygon instead of the grown one, because in case
|
||||
# of very thin (but still working) anchors, the grown expolygon would go beyond them
|
||||
my $angle = $lower_layer
|
||||
? $self->_detect_bridge_direction($surface->expolygon, $lower_layer)
|
||||
: undef;
|
||||
my $angle;
|
||||
if ($lower_layer) {
|
||||
my $bridge_detector = Slic3r::Layer::BridgeDetector->new(
|
||||
expolygon => $surface->expolygon,
|
||||
lower_slices => $lower_layer->slices,
|
||||
extrusion_width => $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width,
|
||||
);
|
||||
Slic3r::debugf "Processing bridge at layer %d:\n", $self->id;
|
||||
$angle = $bridge_detector->detect_angle;
|
||||
|
||||
if (defined $angle && $self->object->config->support_material) {
|
||||
$self->bridged->append(@{ $bridge_detector->coverage($angle) });
|
||||
$self->unsupported_bridge_edges->append(@{ $bridge_detector->unsupported_edges });
|
||||
}
|
||||
}
|
||||
|
||||
push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown;
|
||||
}
|
||||
|
@ -397,122 +541,4 @@ sub process_external_surfaces {
|
|||
$self->fill_surfaces->append(@new_surfaces);
|
||||
}
|
||||
|
||||
sub _detect_bridge_direction {
|
||||
my ($self, $expolygon, $lower_layer) = @_;
|
||||
|
||||
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
|
||||
my $infill_flow = $self->flow(FLOW_ROLE_INFILL);
|
||||
|
||||
my $grown = $expolygon->offset(+$perimeter_flow->scaled_width);
|
||||
my @lower = @{$lower_layer->slices}; # expolygons
|
||||
|
||||
# detect what edges lie on lower slices
|
||||
my @edges = (); # polylines
|
||||
foreach my $lower (@lower) {
|
||||
# turn bridge contour and holes into polylines and then clip them
|
||||
# with each lower slice's contour
|
||||
my @clipped = @{intersection_pl([ map $_->split_at_first_point, @$grown ], [$lower->contour])};
|
||||
if (@clipped == 2) {
|
||||
# If the split_at_first_point() call above happens to split the polygon inside the clipping area
|
||||
# we would get two consecutive polylines instead of a single one, so we use this ugly hack to
|
||||
# recombine them back into a single one in order to trigger the @edges == 2 logic below.
|
||||
# This needs to be replaced with something way better.
|
||||
if (points_coincide($clipped[0][0], $clipped[-1][-1])) {
|
||||
@clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]}));
|
||||
}
|
||||
if (points_coincide($clipped[-1][0], $clipped[0][-1])) {
|
||||
@clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]}));
|
||||
}
|
||||
}
|
||||
push @edges, @clipped;
|
||||
}
|
||||
|
||||
Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges);
|
||||
return undef if !@edges;
|
||||
|
||||
my $bridge_angle = undef;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("bridge_$expolygon.svg",
|
||||
expolygons => [ $expolygon ],
|
||||
red_expolygons => [ @lower ],
|
||||
polylines => [ @edges ],
|
||||
);
|
||||
}
|
||||
|
||||
if (@edges == 2) {
|
||||
my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
|
||||
my @midpoints = map $_->midpoint, @chords;
|
||||
my $line_between_midpoints = Slic3r::Line->new(@midpoints);
|
||||
$bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction);
|
||||
} elsif (@edges == 1) {
|
||||
# TODO: this case includes both U-shaped bridges and plain overhangs;
|
||||
# we need a trapezoidation algorithm to detect the actual bridged area
|
||||
# and separate it from the overhang area.
|
||||
# in the mean time, we're treating as overhangs all cases where
|
||||
# our supporting edge is a straight line
|
||||
if (@{$edges[0]} > 2) {
|
||||
my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
|
||||
$bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction);
|
||||
}
|
||||
} elsif (@edges) {
|
||||
# inset the bridge expolygon; we'll use this one to clip our test lines
|
||||
my $inset = $expolygon->offset_ex($infill_flow->scaled_width);
|
||||
|
||||
# detect anchors as intersection between our bridge expolygon and the lower slices
|
||||
my $anchors = intersection_ex(
|
||||
$grown,
|
||||
[ map @$_, @lower ],
|
||||
1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges
|
||||
);
|
||||
|
||||
if (@$anchors) {
|
||||
# we'll now try several directions using a rudimentary visibility check:
|
||||
# bridge in several directions and then sum the length of lines having both
|
||||
# endpoints within anchors
|
||||
my %directions = (); # angle => score
|
||||
my $angle_increment = PI/36; # 5°
|
||||
my $line_increment = $infill_flow->scaled_width;
|
||||
for (my $angle = 0; $angle <= PI; $angle += $angle_increment) {
|
||||
# rotate everything - the center point doesn't matter
|
||||
$_->rotate($angle, [0,0]) for @$inset, @$anchors;
|
||||
|
||||
# generate lines in this direction
|
||||
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]);
|
||||
|
||||
my @lines = ();
|
||||
for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) {
|
||||
push @lines, Slic3r::Polyline->new([$x, $bounding_box->y_min], [$x, $bounding_box->y_max]);
|
||||
}
|
||||
|
||||
my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$inset ]) };
|
||||
|
||||
# remove any line not having both endpoints within anchors
|
||||
# NOTE: these calls to contains_point() probably need to check whether the point
|
||||
# is on the anchor boundaries too
|
||||
@clipped_lines = grep {
|
||||
my $line = $_;
|
||||
!(first { $_->contains_point($line->a) } @$anchors)
|
||||
&& !(first { $_->contains_point($line->b) } @$anchors);
|
||||
} @clipped_lines;
|
||||
|
||||
# sum length of bridged lines
|
||||
$directions{-$angle} = sum(map $_->length, @clipped_lines) // 0;
|
||||
}
|
||||
|
||||
# this could be slightly optimized with a max search instead of the sort
|
||||
my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions;
|
||||
|
||||
# the best direction is the one causing most lines to be bridged
|
||||
$bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]);
|
||||
}
|
||||
}
|
||||
|
||||
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
|
||||
$self->id, $bridge_angle if defined $bridge_angle;
|
||||
|
||||
return $bridge_angle;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package Slic3r::Model;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first max);
|
||||
use Slic3r::Geometry qw(X Y Z MIN move_points);
|
||||
|
||||
has 'materials' => (is => 'ro', default => sub { {} });
|
||||
has 'objects' => (is => 'ro', default => sub { [] });
|
||||
use Slic3r::Geometry qw(X Y Z move_points);
|
||||
|
||||
sub read_from_file {
|
||||
my $class = shift;
|
||||
|
@ -16,7 +12,7 @@ sub read_from_file {
|
|||
: $input_file =~ /\.amf(\.xml)?$/i ? Slic3r::Format::AMF->read_file($input_file)
|
||||
: die "Input file must have .stl, .obj or .amf(.xml) extension\n";
|
||||
|
||||
$_->input_file($input_file) for @{$model->objects};
|
||||
$_->set_input_file($input_file) for @{$model->objects};
|
||||
return $model;
|
||||
}
|
||||
|
||||
|
@ -35,70 +31,54 @@ sub merge {
|
|||
sub add_object {
|
||||
my $self = shift;
|
||||
|
||||
my $new_object;
|
||||
if (@_ == 1) {
|
||||
# we have a Model::Object
|
||||
my ($object) = @_;
|
||||
|
||||
$new_object = $self->add_object(
|
||||
input_file => $object->input_file,
|
||||
config => $object->config,
|
||||
layer_height_ranges => $object->layer_height_ranges, # TODO: clone!
|
||||
);
|
||||
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
$new_object->add_volume($volume);
|
||||
}
|
||||
|
||||
$new_object->add_instance(
|
||||
offset => $_->offset,
|
||||
rotation => $_->rotation,
|
||||
scaling_factor => $_->scaling_factor,
|
||||
) for @{ $object->instances // [] };
|
||||
return $self->_add_object_clone($object);
|
||||
} else {
|
||||
push @{$self->objects}, $new_object = Slic3r::Model::Object->new(model => $self, @_);
|
||||
my (%args) = @_;
|
||||
|
||||
my $new_object = $self->_add_object;
|
||||
|
||||
$new_object->set_input_file($args{input_file})
|
||||
if defined $args{input_file};
|
||||
$new_object->config->apply($args{config})
|
||||
if defined $args{config};
|
||||
$new_object->set_layer_height_ranges($args{layer_height_ranges})
|
||||
if defined $args{layer_height_ranges};
|
||||
$new_object->set_origin_translation($args{origin_translation})
|
||||
if defined $args{origin_translation};
|
||||
|
||||
return $new_object;
|
||||
}
|
||||
|
||||
return $new_object;
|
||||
}
|
||||
|
||||
sub delete_object {
|
||||
my ($self, $obj_idx) = @_;
|
||||
splice @{$self->objects}, $obj_idx, 1;
|
||||
}
|
||||
|
||||
sub delete_all_objects {
|
||||
my ($self) = @_;
|
||||
@{$self->objects} = ();
|
||||
}
|
||||
|
||||
sub set_material {
|
||||
my $self = shift;
|
||||
my ($material_id, $attributes) = @_;
|
||||
|
||||
return $self->materials->{$material_id} = Slic3r::Model::Material->new(
|
||||
model => $self,
|
||||
attributes => $attributes || {},
|
||||
);
|
||||
my $material = $self->add_material($material_id);
|
||||
$material->apply($attributes // {});
|
||||
return $material;
|
||||
}
|
||||
|
||||
sub duplicate_objects_grid {
|
||||
my ($self, $grid, $distance) = @_;
|
||||
|
||||
|
||||
die "Grid duplication is not supported with multiple objects\n"
|
||||
if @{$self->objects} > 1;
|
||||
|
||||
|
||||
my $object = $self->objects->[0];
|
||||
@{$object->instances} = ();
|
||||
|
||||
$object->clear_instances;
|
||||
|
||||
my $size = $object->bounding_box->size;
|
||||
for my $x_copy (1..$grid->[X]) {
|
||||
for my $y_copy (1..$grid->[Y]) {
|
||||
$object->add_instance(
|
||||
offset => [
|
||||
offset => Slic3r::Pointf->new(
|
||||
($size->[X] + $distance) * ($x_copy-1),
|
||||
($size->[Y] + $distance) * ($y_copy-1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -112,12 +92,7 @@ sub duplicate_objects {
|
|||
foreach my $object (@{$self->objects}) {
|
||||
my @instances = @{$object->instances};
|
||||
foreach my $instance (@instances) {
|
||||
### $object->add_instance($instance->clone); if we had clone()
|
||||
$object->add_instance(
|
||||
offset => [ @{$instance->offset} ],
|
||||
rotation => $instance->rotation,
|
||||
scaling_factor => $instance->scaling_factor,
|
||||
) for 2..$copies_num;
|
||||
$object->add_instance($instance) for 2..$copies_num;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +114,7 @@ sub arrange_objects {
|
|||
my @positions = $self->_arrange(\@instance_sizes, $distance, $bb);
|
||||
|
||||
foreach my $object (@{$self->objects}) {
|
||||
$_->offset([ @{shift @positions} ]) for @{$object->instances};
|
||||
$_->set_offset(Slic3r::Pointf->new(@{shift @positions})) for @{$object->instances};
|
||||
$object->update_bounding_box;
|
||||
}
|
||||
}
|
||||
|
@ -157,9 +132,8 @@ sub duplicate {
|
|||
my @instances = @{$object->instances}; # store separately to avoid recursion from add_instance() below
|
||||
foreach my $instance (@instances) {
|
||||
foreach my $pos (@positions) {
|
||||
### $object->add_instance($instance->clone); if we had clone()
|
||||
$object->add_instance(
|
||||
offset => [ $instance->offset->[X] + $pos->[X], $instance->offset->[Y] + $pos->[Y] ],
|
||||
offset => Slic3r::Pointf->new($instance->offset->[X] + $pos->[X], $instance->offset->[Y] + $pos->[Y]),
|
||||
rotation => $instance->rotation,
|
||||
scaling_factor => $instance->scaling_factor,
|
||||
);
|
||||
|
@ -186,6 +160,21 @@ sub has_objects_with_no_instances {
|
|||
return (first { !defined $_->instances } @{$self->objects}) ? 1 : 0;
|
||||
}
|
||||
|
||||
# makes sure all objects have at least one instance
|
||||
sub add_default_instances {
|
||||
my ($self) = @_;
|
||||
|
||||
# apply a default position to all objects not having one
|
||||
my $added = 0;
|
||||
foreach my $object (@{$self->objects}) {
|
||||
if ($object->instances_count == 0) {
|
||||
$object->add_instance(offset => Slic3r::Pointf->new(0,0));
|
||||
$added = 1;
|
||||
}
|
||||
}
|
||||
return $added;
|
||||
}
|
||||
|
||||
# this returns the bounding box of the *transformed* instances
|
||||
sub bounding_box {
|
||||
my $self = shift;
|
||||
|
@ -211,13 +200,26 @@ sub center_instances_around_point {
|
|||
|
||||
foreach my $object (@{$self->objects}) {
|
||||
foreach my $instance (@{$object->instances}) {
|
||||
$instance->offset->[X] += $shift[X];
|
||||
$instance->offset->[Y] += $shift[Y];
|
||||
$instance->set_offset(Slic3r::Pointf->new(
|
||||
$instance->offset->x + $shift[X],
|
||||
$instance->offset->y + $shift[Y], #++
|
||||
));
|
||||
}
|
||||
$object->update_bounding_box;
|
||||
}
|
||||
}
|
||||
|
||||
sub align_instances_to_origin {
|
||||
my ($self) = @_;
|
||||
|
||||
my $bb = $self->bounding_box;
|
||||
return if !defined $bb;
|
||||
|
||||
my $new_center = $bb->size;
|
||||
$new_center->translate(-$new_center->x/2, -$new_center->y/2); #//
|
||||
$self->center_instances_around_point($new_center);
|
||||
}
|
||||
|
||||
sub translate {
|
||||
my $self = shift;
|
||||
my @shift = @_;
|
||||
|
@ -245,7 +247,7 @@ sub split_meshes {
|
|||
if (@{$object->volumes} > 1) {
|
||||
# We can't split meshes if there's more than one material, because
|
||||
# we can't group the resulting meshes by object afterwards
|
||||
push @{$self->objects}, $object;
|
||||
$self->_add_object($object);
|
||||
next;
|
||||
}
|
||||
|
||||
|
@ -255,6 +257,7 @@ sub split_meshes {
|
|||
input_file => $object->input_file,
|
||||
config => $object->config->clone,
|
||||
layer_height_ranges => $object->layer_height_ranges, # TODO: this needs to be cloned
|
||||
origin_translation => $object->origin_translation,
|
||||
);
|
||||
$new_object->add_volume(
|
||||
mesh => $mesh,
|
||||
|
@ -263,7 +266,7 @@ sub split_meshes {
|
|||
|
||||
# add one instance per original instance
|
||||
$new_object->add_instance(
|
||||
offset => [ @{$_->offset} ],
|
||||
offset => Slic3r::Pointf->new(@{$_->offset}),
|
||||
rotation => $_->rotation,
|
||||
scaling_factor => $_->scaling_factor,
|
||||
) for @{ $object->instances // [] };
|
||||
|
@ -281,56 +284,44 @@ sub get_material_name {
|
|||
my ($material_id) = @_;
|
||||
|
||||
my $name;
|
||||
if (exists $self->materials->{$material_id}) {
|
||||
$name //= $self->materials->{$material_id}->attributes->{$_} for qw(Name name);
|
||||
if ($self->has_material($material_id)) {
|
||||
$name //= $self->get_material($material_id)
|
||||
->attributes->{$_} for qw(Name name);
|
||||
}
|
||||
$name //= $material_id;
|
||||
return $name;
|
||||
}
|
||||
|
||||
package Slic3r::Model::Material;
|
||||
use Moo;
|
||||
|
||||
has 'model' => (is => 'ro', weak_ref => 1, required => 1);
|
||||
has 'attributes' => (is => 'rw', default => sub { {} });
|
||||
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new });
|
||||
sub apply {
|
||||
my ($self, $attributes) = @_;
|
||||
$self->set_attribute($_, $attributes{$_}) for keys %$attributes;
|
||||
}
|
||||
|
||||
package Slic3r::Model::Object;
|
||||
use Moo;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use List::Util qw(first sum);
|
||||
use Slic3r::Geometry qw(X Y Z MIN MAX);
|
||||
|
||||
has 'input_file' => (is => 'rw');
|
||||
has 'model' => (is => 'ro', weak_ref => 1, required => 1);
|
||||
has 'volumes' => (is => 'ro', default => sub { [] });
|
||||
has 'instances' => (is => 'rw');
|
||||
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new });
|
||||
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
|
||||
has '_bounding_box' => (is => 'rw');
|
||||
use Slic3r::Geometry qw(X Y Z rad2deg);
|
||||
|
||||
sub add_volume {
|
||||
my $self = shift;
|
||||
|
||||
|
||||
my $new_volume;
|
||||
if (@_ == 1) {
|
||||
# we have a Model::Volume
|
||||
my ($volume) = @_;
|
||||
|
||||
$new_volume = Slic3r::Model::Volume->new(
|
||||
object => $self,
|
||||
material_id => $volume->material_id,
|
||||
mesh => $volume->mesh->clone,
|
||||
modifier => $volume->modifier,
|
||||
);
|
||||
$new_volume = $self->_add_volume_clone($volume);
|
||||
|
||||
# TODO: material_id can't be undef.
|
||||
if (defined $volume->material_id) {
|
||||
# merge material attributes and config (should we rename materials in case of duplicates?)
|
||||
if (my $material = $volume->object->model->materials->{$volume->material_id}) {
|
||||
if (my $material = $volume->object->model->get_material($volume->material_id)) {
|
||||
my %attributes = %{ $material->attributes };
|
||||
if (exists $self->model->materials->{$volume->material_id}) {
|
||||
%attributes = (%attributes, %{ $self->model->materials->{$volume->material_id}->attributes })
|
||||
if ($self->model->has_material($volume->material_id)) {
|
||||
%attributes = (%attributes, %{ $self->model->get_material($volume->material_id)->attributes })
|
||||
}
|
||||
my $new_material = $self->model->set_material($volume->material_id, {%attributes});
|
||||
$new_material->config->apply($material->config);
|
||||
|
@ -338,44 +329,47 @@ sub add_volume {
|
|||
}
|
||||
} else {
|
||||
my %args = @_;
|
||||
$new_volume = Slic3r::Model::Volume->new(
|
||||
object => $self,
|
||||
%args,
|
||||
);
|
||||
|
||||
$new_volume = $self->_add_volume($args{mesh});
|
||||
|
||||
$new_volume->set_material_id($args{material_id})
|
||||
if defined $args{material_id};
|
||||
$new_volume->set_modifier($args{modifier})
|
||||
if defined $args{modifier};
|
||||
}
|
||||
|
||||
push @{$self->volumes}, $new_volume;
|
||||
if (defined $new_volume->material_id && !defined $self->model->get_material($new_volume->material_id)) {
|
||||
# TODO: this should be a trigger on Volume::material_id
|
||||
$self->model->set_material($new_volume->material_id);
|
||||
}
|
||||
|
||||
# invalidate cached bounding box
|
||||
$self->_bounding_box(undef);
|
||||
$self->invalidate_bounding_box;
|
||||
|
||||
return $new_volume;
|
||||
}
|
||||
|
||||
sub delete_volume {
|
||||
my ($self, $i) = @_;
|
||||
splice @{$self->volumes}, $i, 1;
|
||||
}
|
||||
|
||||
sub add_instance {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
$self->instances([]) if !defined $self->instances;
|
||||
push @{$self->instances}, my $i = Slic3r::Model::Instance->new(object => $self, %params);
|
||||
$self->_bounding_box(undef);
|
||||
return $i;
|
||||
}
|
||||
|
||||
sub delete_last_instance {
|
||||
my ($self) = @_;
|
||||
pop @{$self->instances};
|
||||
$self->_bounding_box(undef);
|
||||
}
|
||||
|
||||
sub instances_count {
|
||||
my $self = shift;
|
||||
return scalar(@{ $self->instances // [] });
|
||||
if (@_ == 1) {
|
||||
# we have a Model::Instance
|
||||
my ($instance) = @_;
|
||||
return $self->_add_instance_clone($instance);
|
||||
} else {
|
||||
my (%args) = @_;
|
||||
|
||||
my $new_instance = $self->_add_instance;
|
||||
|
||||
$new_instance->set_rotation($args{rotation})
|
||||
if defined $args{rotation};
|
||||
$new_instance->set_scaling_factor($args{scaling_factor})
|
||||
if defined $args{scaling_factor};
|
||||
$new_instance->set_offset($args{offset})
|
||||
if defined $args{offset};
|
||||
|
||||
return $new_instance;
|
||||
}
|
||||
}
|
||||
|
||||
sub raw_mesh {
|
||||
|
@ -446,11 +440,14 @@ sub center_around_origin {
|
|||
$shift[Y] -= $size->y/2; #//
|
||||
|
||||
$self->translate(@shift);
|
||||
$self->origin_translation->translate(@shift[X,Y]);
|
||||
|
||||
if (defined $self->instances) {
|
||||
if ($self->instances_count > 0) {
|
||||
foreach my $instance (@{ $self->instances }) {
|
||||
$instance->offset->[X] -= $shift[X];
|
||||
$instance->offset->[Y] -= $shift[Y];
|
||||
$instance->set_offset(Slic3r::Pointf->new(
|
||||
$instance->offset->x - $shift[X],
|
||||
$instance->offset->y - $shift[Y], #--
|
||||
));
|
||||
}
|
||||
$self->update_bounding_box;
|
||||
}
|
||||
|
@ -466,6 +463,16 @@ sub translate {
|
|||
$self->_bounding_box->translate(@shift) if defined $self->_bounding_box;
|
||||
}
|
||||
|
||||
sub rotate_x {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
# we accept angle in radians but mesh currently uses degrees
|
||||
$angle = rad2deg($angle);
|
||||
|
||||
$_->mesh->rotate_x($angle) for @{$self->volumes};
|
||||
$self->invalidate_bounding_box;
|
||||
}
|
||||
|
||||
sub materials_count {
|
||||
my $self = shift;
|
||||
|
||||
|
@ -524,30 +531,66 @@ sub print_info {
|
|||
}
|
||||
}
|
||||
|
||||
package Slic3r::Model::Volume;
|
||||
use Moo;
|
||||
sub cut {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
# clone this one
|
||||
my $upper = $self->model->add_object($self);
|
||||
my $lower = $self->model->add_object($self);
|
||||
|
||||
foreach my $instance (@{$self->instances}) {
|
||||
$upper->add_instance(offset => Slic3r::Pointf->new(@{$instance->offset}));
|
||||
$lower->add_instance(offset => Slic3r::Pointf->new(@{$instance->offset}));
|
||||
}
|
||||
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
if ($volume->modifier) {
|
||||
# don't cut modifiers
|
||||
$upper->add_volume($volume);
|
||||
$lower->add_volume($volume);
|
||||
} else {
|
||||
my $upper_mesh = Slic3r::TriangleMesh->new;
|
||||
my $lower_mesh = Slic3r::TriangleMesh->new;
|
||||
$volume->mesh->cut($z + $volume->mesh->bounding_box->z_min, $upper_mesh, $lower_mesh);
|
||||
$upper_mesh->repair;
|
||||
$lower_mesh->repair;
|
||||
$upper_mesh->reset_repair_stats;
|
||||
$lower_mesh->reset_repair_stats;
|
||||
|
||||
if ($upper_mesh->facets_count > 0) {
|
||||
$upper->add_volume(
|
||||
material_id => $volume->material_id,
|
||||
mesh => $upper_mesh,
|
||||
modifier => $volume->modifier,
|
||||
);
|
||||
}
|
||||
if ($lower_mesh->facets_count > 0) {
|
||||
$lower->add_volume(
|
||||
material_id => $volume->material_id,
|
||||
mesh => $lower_mesh,
|
||||
modifier => $volume->modifier,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$upper = undef if !@{$upper->volumes};
|
||||
$lower = undef if !@{$lower->volumes};
|
||||
return ($upper, $lower);
|
||||
}
|
||||
|
||||
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
|
||||
has 'material_id' => (is => 'rw');
|
||||
has 'mesh' => (is => 'rw', required => 1);
|
||||
has 'modifier' => (is => 'rw', defualt => sub { 0 });
|
||||
package Slic3r::Model::Volume;
|
||||
|
||||
sub assign_unique_material {
|
||||
my ($self) = @_;
|
||||
|
||||
my $model = $self->object->model;
|
||||
my $material_id = 1 + scalar keys %{$model->materials};
|
||||
my $material_id = 1 + $model->material_count;
|
||||
$self->material_id($material_id);
|
||||
return $model->set_material($material_id);
|
||||
}
|
||||
|
||||
package Slic3r::Model::Instance;
|
||||
use Moo;
|
||||
|
||||
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
|
||||
has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point
|
||||
has 'scaling_factor' => (is => 'rw', default => sub { 1 });
|
||||
has 'offset' => (is => 'rw'); # must be arrayref in *unscaled* coordinates
|
||||
|
||||
sub transform_mesh {
|
||||
my ($self, $mesh, $dont_translate) = @_;
|
||||
|
|
|
@ -8,12 +8,18 @@ use parent 'Slic3r::Polyline';
|
|||
use Slic3r::Geometry qw(
|
||||
polygon_segment_having_point
|
||||
PI X1 X2 Y1 Y2 epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
|
||||
sub wkt {
|
||||
my $self = shift;
|
||||
return sprintf "POLYGON((%s))", join ',', map "$_->[0] $_->[1]", @$self;
|
||||
}
|
||||
|
||||
sub dump_perl {
|
||||
my $self = shift;
|
||||
return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self;
|
||||
}
|
||||
|
||||
sub grow {
|
||||
my $self = shift;
|
||||
return $self->split_at_first_point->grow(@_);
|
||||
|
@ -39,15 +45,52 @@ sub subdivide {
|
|||
return Slic3r::Polygon->new(@new_points);
|
||||
}
|
||||
|
||||
# for cw polygons this will return convex points!
|
||||
sub concave_points {
|
||||
my $self = shift;
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
$angle //= PI;
|
||||
|
||||
# input angle threshold is checked on the internal side of the polygon
|
||||
# but angle3points measures CCW angle, so we calculate the complementary angle
|
||||
my $ccw_angle = 2*PI-$angle;
|
||||
|
||||
my @points = @$self;
|
||||
my @points_pp = @{$self->pp};
|
||||
return map $points[$_],
|
||||
grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) < PI - epsilon,
|
||||
-1 .. ($#points-1);
|
||||
|
||||
my @concave = ();
|
||||
for my $i (-1 .. ($#points-1)) {
|
||||
next if $points[$i-1]->coincides_with_epsilon($points[$i]) || $points[$i+1]->coincides_with_epsilon($points[$i]);
|
||||
# angle is measured in ccw orientation
|
||||
my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]);
|
||||
if ($vertex_angle <= $ccw_angle) {
|
||||
push @concave, $points[$i];
|
||||
}
|
||||
}
|
||||
return [@concave];
|
||||
}
|
||||
|
||||
sub convex_points {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
$angle //= PI;
|
||||
|
||||
# input angle threshold is checked on the internal side of the polygon
|
||||
# but angle3points measures CCW angle, so we calculate the complementary angle
|
||||
my $ccw_angle = 2*PI-$angle;
|
||||
|
||||
my @points = @$self;
|
||||
my @points_pp = @{$self->pp};
|
||||
|
||||
my @convex = ();
|
||||
for my $i (-1 .. ($#points-1)) {
|
||||
next if $points[$i-1]->coincides_with_epsilon($points[$i]) || $points[$i+1]->coincides_with_epsilon($points[$i]);
|
||||
# angle is measured in ccw orientation
|
||||
my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]);
|
||||
if ($vertex_angle >= $ccw_angle) {
|
||||
push @convex, $points[$i];
|
||||
}
|
||||
}
|
||||
return [@convex];
|
||||
}
|
||||
|
||||
1;
|
|
@ -2,7 +2,8 @@ package Slic3r::Polyline;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::Geometry qw(A B X Y X1 X2 Y1 Y2);
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(X Y PI epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(JT_SQUARE);
|
||||
|
||||
sub new_scale {
|
||||
|
@ -26,4 +27,14 @@ sub size {
|
|||
return [ Slic3r::Geometry::size_2D($self) ];
|
||||
}
|
||||
|
||||
sub is_straight {
|
||||
my ($self) = @_;
|
||||
|
||||
# Check that each segment's direction is equal to the line connecting
|
||||
# first point and last point. (Checking each line against the previous
|
||||
# one would have caused the error to accumulate.)
|
||||
my $dir = Slic3r::Line->new($self->first_point, $self->last_point)->direction;
|
||||
return !defined first { !$_->parallel_to($dir) } @{$self->lines};
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package Slic3r::Print;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename qw(basename fileparse);
|
||||
use File::Spec;
|
||||
use List::Util qw(min max first);
|
||||
use List::Util qw(min max first sum);
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points chained_path
|
||||
|
@ -12,22 +13,26 @@ use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex inter
|
|||
offset2 union union_pt_chained JT_ROUND JT_SQUARE);
|
||||
use Slic3r::Print::State ':steps';
|
||||
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::Print->new });
|
||||
has 'default_object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new });
|
||||
has 'default_region_config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new });
|
||||
has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new });
|
||||
has 'objects' => (is => 'rw', default => sub {[]});
|
||||
has 'status_cb' => (is => 'rw');
|
||||
has 'regions' => (is => 'rw', default => sub {[]});
|
||||
has 'total_used_filament' => (is => 'rw');
|
||||
has 'total_extruded_volume' => (is => 'rw');
|
||||
has '_state' => (is => 'ro', default => sub { Slic3r::Print::State->new });
|
||||
our $status_cb;
|
||||
|
||||
# ordered collection of extrusion paths to build skirt loops
|
||||
has 'skirt' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
sub new {
|
||||
# TODO: port PlaceholderParser methods to C++, then its own constructor
|
||||
# can call them and no need for this new() method at all
|
||||
my ($class) = @_;
|
||||
my $self = $class->_new;
|
||||
$self->placeholder_parser->apply_env_variables;
|
||||
$self->placeholder_parser->update_timestamp;
|
||||
return $self;
|
||||
}
|
||||
|
||||
# ordered collection of extrusion paths to build a brim
|
||||
has 'brim' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
sub set_status_cb {
|
||||
my ($class, $cb) = @_;
|
||||
$status_cb = $cb;
|
||||
}
|
||||
|
||||
sub status_cb {
|
||||
return $status_cb;
|
||||
}
|
||||
|
||||
sub apply_config {
|
||||
my ($self, $config) = @_;
|
||||
|
@ -38,13 +43,17 @@ sub apply_config {
|
|||
# apply variables to placeholder parser
|
||||
$self->placeholder_parser->apply_config($config);
|
||||
|
||||
my $invalidated = 0;
|
||||
|
||||
# handle changes to print config
|
||||
my $print_diff = $self->config->diff($config);
|
||||
if (@$print_diff) {
|
||||
$self->config->apply_dynamic($config);
|
||||
|
||||
# TODO: only invalidate changed steps
|
||||
$self->_state->invalidate_all;
|
||||
my $res;
|
||||
$res = $self->invalidate_all_steps
|
||||
if !$self->invalidate_state_by_config_options($print_diff);
|
||||
$invalidated = 1 if $res;
|
||||
}
|
||||
|
||||
# handle changes to object config defaults
|
||||
|
@ -53,6 +62,7 @@ sub apply_config {
|
|||
# we don't assume that $config contains a full ObjectConfig,
|
||||
# so we base it on the current print-wise default
|
||||
my $new = $self->default_object_config->clone;
|
||||
$new->apply_dynamic($config);
|
||||
|
||||
# we override the new config with object-specific options
|
||||
my $model_object_config = $object->model_object->config->clone;
|
||||
|
@ -63,8 +73,11 @@ sub apply_config {
|
|||
my $diff = $object->config->diff($new);
|
||||
if (@$diff) {
|
||||
$object->config->apply($new);
|
||||
# TODO: only invalidate changed steps
|
||||
$object->_state->invalidate_all;
|
||||
|
||||
my $res;
|
||||
$res = $object->invalidate_all_steps
|
||||
if !$object->invalidate_state_by_config_options($diff);
|
||||
$invalidated = 1 if $res;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,9 +87,12 @@ sub apply_config {
|
|||
# All regions now have distinct settings.
|
||||
# Check whether applying the new region config defaults we'd get different regions.
|
||||
my $rearrange_regions = 0;
|
||||
REGION: foreach my $region_id (0..$#{$self->regions}) {
|
||||
my @other_region_configs = ();
|
||||
REGION: foreach my $region_id (0..($self->region_count - 1)) {
|
||||
my $region = $self->regions->[$region_id];
|
||||
my @this_region_configs = ();
|
||||
foreach my $object (@{$self->objects}) {
|
||||
foreach my $volume_id (@{ $object->region_volumes->[$region_id] }) {
|
||||
foreach my $volume_id (@{ $object->get_region_volumes($region_id) }) {
|
||||
my $volume = $object->model_object->volumes->[$volume_id];
|
||||
|
||||
my $new = $self->default_region_config->clone;
|
||||
|
@ -86,28 +102,56 @@ sub apply_config {
|
|||
$new->apply_dynamic($model_object_config);
|
||||
}
|
||||
if (defined $volume->material_id) {
|
||||
my $material_config = $object->model_object->model->materials->{$volume->material_id}->config->clone;
|
||||
my $material_config = $object->model_object->model->get_material($volume->material_id)->config->clone;
|
||||
$material_config->normalize;
|
||||
$new->apply_dynamic($material_config);
|
||||
}
|
||||
if (!$new->equals($self->regions->[$region_id]->config)) {
|
||||
if (defined first { !$_->equals($new) } @this_region_configs) {
|
||||
# if the new config for this volume differs from the other
|
||||
# volume configs currently associated to this region, it means
|
||||
# the region subdivision does not make sense anymore
|
||||
$rearrange_regions = 1;
|
||||
last REGION;
|
||||
}
|
||||
push @this_region_configs, $new;
|
||||
|
||||
if (defined first { $_->equals($new) } @other_region_configs) {
|
||||
# if the new config for this volume equals any of the other
|
||||
# volume configs that are not currently associated to this
|
||||
# region, it means the region subdivision does not make
|
||||
# sense anymore
|
||||
$rearrange_regions = 1;
|
||||
last REGION;
|
||||
}
|
||||
|
||||
# if we're here and the new region config is different from the old
|
||||
# one, we need to apply the new config and invalidate all objects
|
||||
# (possible optimization: only invalidate objects using this region)
|
||||
my $region_config_diff = $region->config->diff($new);
|
||||
if (@$region_config_diff) {
|
||||
$region->config->apply($new);
|
||||
foreach my $o (@{$self->objects}) {
|
||||
my $res;
|
||||
$res = $o->invalidate_all_steps
|
||||
if !$o->invalidate_state_by_config_options($region_config_diff);
|
||||
$invalidated = 1 if $res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
push @other_region_configs, @this_region_configs;
|
||||
}
|
||||
|
||||
# Some optimization is possible: if the volumes-regions mappings don't change
|
||||
# but still region configs are changed somehow, we could just apply the diff
|
||||
# and invalidate the affected steps.
|
||||
if ($rearrange_regions) {
|
||||
# the current subdivision of regions does not make sense anymore.
|
||||
# we need to remove all objects and re-add them
|
||||
my @model_objects = map $_->model_object, @{$self->objects};
|
||||
$self->delete_all_objects;
|
||||
$self->clear_objects;
|
||||
$self->add_model_object($_) for @model_objects;
|
||||
$invalidated = 1;
|
||||
}
|
||||
|
||||
return $invalidated;
|
||||
}
|
||||
|
||||
sub has_support_material {
|
||||
|
@ -125,8 +169,22 @@ sub add_model_object {
|
|||
|
||||
my $object_config = $object->config->clone;
|
||||
$object_config->normalize;
|
||||
|
||||
my %volumes = (); # region_id => [ volume_id, ... ]
|
||||
|
||||
# initialize print object and store it at the given position
|
||||
my $o;
|
||||
if (defined $obj_idx) {
|
||||
$o = $self->set_new_object($obj_idx, $object, $object->bounding_box);
|
||||
} else {
|
||||
$o = $self->add_object($object, $object->bounding_box);
|
||||
}
|
||||
|
||||
$o->set_copies([ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ]);
|
||||
$o->set_layer_height_ranges($object->layer_height_ranges);
|
||||
|
||||
# TODO: translate _trigger_copies to C++, then this can be done by
|
||||
# PrintObject constructor
|
||||
$o->_trigger_copies;
|
||||
|
||||
foreach my $volume_id (0..$#{$object->volumes}) {
|
||||
my $volume = $object->volumes->[$volume_id];
|
||||
|
||||
|
@ -138,14 +196,14 @@ sub add_model_object {
|
|||
$config->apply_dynamic($object_config);
|
||||
|
||||
if (defined $volume->material_id) {
|
||||
my $material_config = $object->model->materials->{ $volume->material_id }->config->clone;
|
||||
my $material_config = $volume->material->config->clone;
|
||||
$material_config->normalize;
|
||||
$config->apply_dynamic($material_config);
|
||||
}
|
||||
|
||||
# find an existing print region with the same config
|
||||
my $region_id;
|
||||
foreach my $i (0..$#{$self->regions}) {
|
||||
foreach my $i (0..($self->region_count - 1)) {
|
||||
my $region = $self->regions->[$i];
|
||||
if ($config->equals($region->config)) {
|
||||
$region_id = $i;
|
||||
|
@ -155,60 +213,21 @@ sub add_model_object {
|
|||
|
||||
# if no region exists with the same config, create a new one
|
||||
if (!defined $region_id) {
|
||||
push @{$self->regions}, my $r = Slic3r::Print::Region->new(
|
||||
print => $self,
|
||||
);
|
||||
my $r = $self->add_region();
|
||||
$r->config->apply($config);
|
||||
$region_id = $#{$self->regions};
|
||||
$region_id = $self->region_count - 1;
|
||||
}
|
||||
|
||||
# assign volume to region
|
||||
$volumes{$region_id} //= [];
|
||||
push @{ $volumes{$region_id} }, $volume_id;
|
||||
$o->add_region_volume($region_id, $volume_id);
|
||||
}
|
||||
|
||||
# initialize print object
|
||||
my $o = Slic3r::Print::Object->new(
|
||||
print => $self,
|
||||
model_object => $object,
|
||||
region_volumes => [ map $volumes{$_}, 0..$#{$self->regions} ],
|
||||
copies => [ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ],
|
||||
layer_height_ranges => $object->layer_height_ranges,
|
||||
);
|
||||
|
||||
|
||||
# apply config to print object
|
||||
$o->config->apply($self->default_object_config);
|
||||
$o->config->apply_dynamic($object_config);
|
||||
|
||||
# store print object at the given position
|
||||
if (defined $obj_idx) {
|
||||
splice @{$self->objects}, $obj_idx, 0, $o;
|
||||
} else {
|
||||
push @{$self->objects}, $o;
|
||||
}
|
||||
|
||||
$self->_state->invalidate(STEP_SKIRT);
|
||||
$self->_state->invalidate(STEP_BRIM);
|
||||
}
|
||||
|
||||
sub delete_object {
|
||||
my ($self, $obj_idx) = @_;
|
||||
|
||||
splice @{$self->objects}, $obj_idx, 1;
|
||||
# TODO: purge unused regions
|
||||
|
||||
$self->_state->invalidate(STEP_SKIRT);
|
||||
$self->_state->invalidate(STEP_BRIM);
|
||||
}
|
||||
|
||||
sub delete_all_objects {
|
||||
my ($self) = @_;
|
||||
|
||||
@{$self->objects} = ();
|
||||
@{$self->regions} = ();
|
||||
|
||||
$self->_state->invalidate(STEP_SKIRT);
|
||||
$self->_state->invalidate(STEP_BRIM);
|
||||
$self->invalidate_step(STEP_SKIRT);
|
||||
$self->invalidate_step(STEP_BRIM);
|
||||
}
|
||||
|
||||
sub reload_object {
|
||||
|
@ -220,9 +239,9 @@ sub reload_object {
|
|||
# For now we just re-add all objects since we haven't implemented this incremental logic yet.
|
||||
# This should also check whether object volumes (parts) have changed.
|
||||
|
||||
my @model_objects = map $_->model_object, @{$self->objects};
|
||||
$self->delete_all_objects;
|
||||
$self->add_model_object($_) for @model_objects;
|
||||
my @models_objects = map $_->model_object, @{$self->objects};
|
||||
$self->clear_objects;
|
||||
$self->add_model_object($_) for @models_objects;
|
||||
}
|
||||
|
||||
sub validate {
|
||||
|
@ -325,9 +344,9 @@ sub init_extruders {
|
|||
|
||||
# this value is not supposed to be compared with $layer->id
|
||||
# since they have different semantics
|
||||
sub layer_count {
|
||||
sub total_layer_count {
|
||||
my $self = shift;
|
||||
return max(map $_->layer_count, @{$self->objects});
|
||||
return max(map $_->total_layer_count, @{$self->objects});
|
||||
}
|
||||
|
||||
sub regions_count {
|
||||
|
@ -371,22 +390,20 @@ sub process {
|
|||
|
||||
my $print_step = sub {
|
||||
my ($step, $cb) = @_;
|
||||
if (!$self->_state->done($step)) {
|
||||
$self->_state->set_started($step);
|
||||
if (!$self->step_done($step)) {
|
||||
$self->set_step_started($step);
|
||||
$cb->();
|
||||
### Re-enable this for step-based slicing:
|
||||
### $self->_state->set_done($step);
|
||||
$self->set_step_done($step);
|
||||
}
|
||||
};
|
||||
my $object_step = sub {
|
||||
my ($step, $cb) = @_;
|
||||
for my $obj_idx (0..$#{$self->objects}) {
|
||||
for my $obj_idx (0..($self->object_count - 1)) {
|
||||
my $object = $self->objects->[$obj_idx];
|
||||
if (!$object->_state->done($step)) {
|
||||
$object->_state->set_started($step);
|
||||
if (!$object->step_done($step)) {
|
||||
$object->set_step_started($step);
|
||||
$cb->($obj_idx);
|
||||
### Re-enable this for step-based slicing:
|
||||
### $object->_state->set_done($step);
|
||||
$object->set_step_done($step);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -454,7 +471,7 @@ sub process {
|
|||
items => sub {
|
||||
my @items = (); # [layer_id, region_id]
|
||||
for my $region_id (0 .. ($self->regions_count-1)) {
|
||||
push @items, map [$_, $region_id], 0..$#{$object->layers};
|
||||
push @items, map [$_, $region_id], 0..($object->layer_count - 1);
|
||||
}
|
||||
@items;
|
||||
},
|
||||
|
@ -463,12 +480,14 @@ sub process {
|
|||
while (defined (my $obj_layer = $q->dequeue)) {
|
||||
my ($i, $region_id) = @$obj_layer;
|
||||
my $layerm = $object->layers->[$i]->regions->[$region_id];
|
||||
$layerm->fills->clear;
|
||||
$layerm->fills->append( $object->fill_maker->make_fill($layerm) );
|
||||
}
|
||||
},
|
||||
collect_cb => sub {},
|
||||
no_threads_cb => sub {
|
||||
foreach my $layerm (map @{$_->regions}, @{$object->layers}) {
|
||||
$layerm->fills->clear;
|
||||
$layerm->fills->append($object->fill_maker->make_fill($layerm));
|
||||
}
|
||||
},
|
||||
|
@ -580,7 +599,7 @@ EOF
|
|||
|
||||
my @current_layer_slices = ();
|
||||
# sort slices so that the outermost ones come first
|
||||
my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
|
||||
my @slices = sort { $a->contour->contains_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
|
||||
foreach my $copy (@{$layer->object->copies}) {
|
||||
foreach my $slice (@slices) {
|
||||
my $expolygon = $slice->clone;
|
||||
|
@ -625,11 +644,14 @@ EOF
|
|||
|
||||
sub make_skirt {
|
||||
my $self = shift;
|
||||
|
||||
# since this method must be idempotent, we clear skirt paths *before*
|
||||
# checking whether we need to generate them
|
||||
$self->skirt->clear;
|
||||
|
||||
return unless $self->config->skirts > 0
|
||||
|| ($self->config->ooze_prevention && @{$self->extruders} > 1);
|
||||
|
||||
$self->skirt->clear; # method must be idempotent
|
||||
|
||||
# First off we need to decide how tall the skirt must be.
|
||||
# The skirt_height option from config is expressed in layers, but our
|
||||
# object might have different layer heights, so we need to find the print_z
|
||||
|
@ -694,7 +716,7 @@ sub make_skirt {
|
|||
bridge_flow_ratio => 0,
|
||||
);
|
||||
my $spacing = $flow->spacing;
|
||||
my $mm3_per_mm = $flow->mm3_per_mm($first_layer_height);
|
||||
my $mm3_per_mm = $flow->mm3_per_mm;
|
||||
|
||||
my @extruders_e_per_mm = ();
|
||||
my $extruder_idx = 0;
|
||||
|
@ -705,16 +727,20 @@ sub make_skirt {
|
|||
for (my $i = $self->config->skirts; $i > 0; $i--) {
|
||||
$distance += scale $spacing;
|
||||
my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0];
|
||||
$self->skirt->append(Slic3r::ExtrusionLoop->new(
|
||||
polygon => Slic3r::Polygon->new(@$loop),
|
||||
role => EXTR_ROLE_SKIRT,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
$self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths(
|
||||
Slic3r::ExtrusionPath->new(
|
||||
polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point,
|
||||
role => EXTR_ROLE_SKIRT,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $first_layer_height,
|
||||
),
|
||||
));
|
||||
|
||||
if ($self->config->min_skirt_length > 0) {
|
||||
$extruded_length[$extruder_idx] ||= 0;
|
||||
if (!$extruders_e_per_mm[$extruder_idx]) {
|
||||
my $extruder = Slic3r::Extruder->new_from_config($self->config, $extruder_idx);
|
||||
my $extruder = Slic3r::Extruder->new($extruder_idx, $self->config);
|
||||
$extruders_e_per_mm[$extruder_idx] = $extruder->e_per_mm($mm3_per_mm);
|
||||
}
|
||||
$extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx];
|
||||
|
@ -733,9 +759,12 @@ sub make_skirt {
|
|||
|
||||
sub make_brim {
|
||||
my $self = shift;
|
||||
return unless $self->config->brim_width > 0;
|
||||
|
||||
$self->brim->clear; # method must be idempotent
|
||||
# since this method must be idempotent, we clear brim paths *before*
|
||||
# checking whether we need to generate them
|
||||
$self->brim->clear;
|
||||
|
||||
return unless $self->config->brim_width > 0;
|
||||
|
||||
# brim is only printed on first layer and uses support material extruder
|
||||
my $first_layer_height = $self->objects->[0]->config->get_abs_value('first_layer_height');
|
||||
|
@ -746,11 +775,11 @@ sub make_brim {
|
|||
layer_height => $first_layer_height,
|
||||
bridge_flow_ratio => 0,
|
||||
);
|
||||
my $mm3_per_mm = $flow->mm3_per_mm($first_layer_height);
|
||||
my $mm3_per_mm = $flow->mm3_per_mm;
|
||||
|
||||
my $grow_distance = $flow->scaled_width / 2;
|
||||
my @islands = (); # array of polygons
|
||||
foreach my $obj_idx (0 .. $#{$self->objects}) {
|
||||
foreach my $obj_idx (0 .. ($self->object_count - 1)) {
|
||||
my $object = $self->objects->[$obj_idx];
|
||||
my $layer0 = $object->layers->[0];
|
||||
my @object_islands = (
|
||||
|
@ -773,7 +802,7 @@ sub make_brim {
|
|||
# if brim touches skirt, make it around skirt too
|
||||
# TODO: calculate actual skirt width (using each extruder's flow in multi-extruder setups)
|
||||
if ($self->config->skirt_distance + (($self->config->skirts - 1) * $flow->spacing) <= $self->config->brim_width) {
|
||||
push @islands, map @{$_->split_at_first_point->polyline->grow($grow_distance)}, @{$self->skirt};
|
||||
push @islands, map @{$_->polygon->split_at_first_point->grow($grow_distance)}, @{$self->skirt};
|
||||
}
|
||||
|
||||
my @loops = ();
|
||||
|
@ -786,10 +815,14 @@ sub make_brim {
|
|||
push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, 100000, JT_SQUARE)};
|
||||
}
|
||||
|
||||
$self->brim->append(map Slic3r::ExtrusionLoop->new(
|
||||
polygon => Slic3r::Polygon->new(@$_),
|
||||
role => EXTR_ROLE_SKIRT,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
$self->brim->append(map Slic3r::ExtrusionLoop->new_from_paths(
|
||||
Slic3r::ExtrusionPath->new(
|
||||
polyline => Slic3r::Polygon->new(@$_)->split_at_first_point,
|
||||
role => EXTR_ROLE_SKIRT,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $first_layer_height,
|
||||
),
|
||||
), reverse @{union_pt_chained(\@loops)});
|
||||
}
|
||||
|
||||
|
@ -818,35 +851,48 @@ sub write_gcode {
|
|||
print $fh "; $_\n" foreach split /\R/, $self->config->notes;
|
||||
print $fh "\n" if $self->config->notes;
|
||||
|
||||
my $layer_height = $self->objects->[0]->config->layer_height;
|
||||
my $first_object = $self->objects->[0];
|
||||
my $layer_height = $first_object->config->layer_height;
|
||||
for my $region_id (0..$#{$self->regions}) {
|
||||
printf $fh "; external perimeters extrusion width = %.2fmm\n",
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, undef, $first_object)->width;
|
||||
printf $fh "; perimeters extrusion width = %.2fmm\n",
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height)->width;
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, undef, $first_object)->width;
|
||||
printf $fh "; infill extrusion width = %.2fmm\n",
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_INFILL, $layer_height)->width;
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, undef, $first_object)->width;
|
||||
printf $fh "; solid infill extrusion width = %.2fmm\n",
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_SOLID_INFILL, $layer_height)->width;
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, undef, $first_object)->width;
|
||||
printf $fh "; top infill extrusion width = %.2fmm\n",
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height)->width;
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, undef, $first_object)->width;
|
||||
printf $fh "; support material extrusion width = %.2fmm\n",
|
||||
$self->objects->[0]->support_material_flow->width
|
||||
if $self->has_support_material;
|
||||
printf $fh "; first layer extrusion width = %.2fmm\n",
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1)->width
|
||||
$self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, undef, $self->objects->[0])->width
|
||||
if $self->regions->[$region_id]->config->first_layer_extrusion_width;
|
||||
print $fh "\n";
|
||||
}
|
||||
|
||||
# prepare the helper object for replacing placeholders in custom G-code and output filename
|
||||
$self->placeholder_parser->update_timestamp;
|
||||
|
||||
# estimate the total number of layer changes
|
||||
# TODO: only do this when M73 is enabled
|
||||
my $layer_count;
|
||||
if ($self->config->complete_objects) {
|
||||
$layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects});
|
||||
} else {
|
||||
# if sequential printing is not enable, all copies of the same object share the same layer change command(s)
|
||||
$layer_count = sum(map { $_->total_layer_count } @{$self->objects});
|
||||
}
|
||||
|
||||
# set up our helper object
|
||||
my $gcodegen = Slic3r::GCode->new(
|
||||
print_config => $self->config,
|
||||
placeholder_parser => $self->placeholder_parser,
|
||||
layer_count => $self->layer_count,
|
||||
layer_count => $layer_count,
|
||||
);
|
||||
$gcodegen->set_extruders($self->extruders);
|
||||
$gcodegen->config->apply_print_config($self->config);
|
||||
$gcodegen->set_extruders($self->extruders, $self->config);
|
||||
|
||||
print $fh "G21 ; set units to millimeters\n" if $self->config->gcode_flavor ne 'makerware';
|
||||
print $fh $gcodegen->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers;
|
||||
|
@ -887,7 +933,7 @@ sub write_gcode {
|
|||
my $distance_from_objects = 1;
|
||||
# compute the offsetted convex hull for each object and repeat it for each copy.
|
||||
my @islands = ();
|
||||
foreach my $obj_idx (0 .. $#{$self->objects}) {
|
||||
foreach my $obj_idx (0 .. ($self->object_count - 1)) {
|
||||
my $convex_hull = convex_hull([
|
||||
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
|
||||
]);
|
||||
|
@ -908,7 +954,7 @@ sub write_gcode {
|
|||
|
||||
# calculate wiping points if needed
|
||||
if ($self->config->ooze_prevention) {
|
||||
my @skirt_points = map @$_, @{$self->skirt};
|
||||
my @skirt_points = map @$_, map @$_, @{$self->skirt};
|
||||
if (@skirt_points) {
|
||||
my $outer_skirt = convex_hull(\@skirt_points);
|
||||
my @skirts = ();
|
||||
|
@ -934,10 +980,11 @@ sub write_gcode {
|
|||
if ($self->config->complete_objects) {
|
||||
# print objects from the smallest to the tallest to avoid collisions
|
||||
# when moving onto next object starting point
|
||||
my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..$#{$self->objects};
|
||||
my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..($self->object_count - 1);
|
||||
|
||||
my $finished_objects = 0;
|
||||
for my $obj_idx (@obj_idx) {
|
||||
my $object = $self->objects->[$obj_idx];
|
||||
for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
|
||||
# move to the origin position for the copy we're going to print.
|
||||
# this happens before Z goes down to layer 0 again, so that
|
||||
|
@ -945,7 +992,7 @@ sub write_gcode {
|
|||
if ($finished_objects > 0) {
|
||||
$gcodegen->set_shift(map unscale $copy->[$_], X,Y);
|
||||
print $fh $gcodegen->retract;
|
||||
print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object');
|
||||
print $fh $gcodegen->G0($object->_copies_shift->negative, undef, 0, $gcodegen->config->travel_speed*60, 'move to origin position for next object');
|
||||
}
|
||||
|
||||
my $buffer = Slic3r::GCode::CoolingBuffer->new(
|
||||
|
@ -953,7 +1000,6 @@ sub write_gcode {
|
|||
gcodegen => $gcodegen,
|
||||
);
|
||||
|
||||
my $object = $self->objects->[$obj_idx];
|
||||
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
|
||||
for my $layer (@layers) {
|
||||
# if we are printing the bottom layer of an object, and we have already finished
|
||||
|
@ -966,7 +1012,7 @@ sub write_gcode {
|
|||
}
|
||||
print $fh $buffer->append(
|
||||
$layer_gcode->process_layer($layer, [$copy]),
|
||||
$layer->object."",
|
||||
$layer->object->ptr,
|
||||
$layer->id,
|
||||
$layer->print_z,
|
||||
);
|
||||
|
@ -981,7 +1027,7 @@ sub write_gcode {
|
|||
|
||||
# sort layers by Z
|
||||
my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
|
||||
foreach my $obj_idx (0 .. $#{$self->objects}) {
|
||||
foreach my $obj_idx (0 .. ($self->object_count - 1)) {
|
||||
my $object = $self->objects->[$obj_idx];
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
$layers{ $layer->print_z } ||= [];
|
||||
|
@ -999,7 +1045,7 @@ sub write_gcode {
|
|||
foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
|
||||
print $fh $buffer->append(
|
||||
$layer_gcode->process_layer($layer, $layer->object->_shifted_copies),
|
||||
$layer->object . ref($layer), # differentiate $obj_id between normal layers and support layers
|
||||
$layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers
|
||||
$layer->id,
|
||||
$layer->print_z,
|
||||
);
|
||||
|
@ -1031,9 +1077,11 @@ sub write_gcode {
|
|||
|
||||
# append full config
|
||||
print $fh "\n";
|
||||
foreach my $opt_key (sort @{$self->config->get_keys}) {
|
||||
next if $Slic3r::Config::Options->{$opt_key}{shortcut};
|
||||
printf $fh "; %s = %s\n", $opt_key, $self->config->serialize($opt_key);
|
||||
foreach my $config ($self->config, $self->default_object_config, $self->default_region_config) {
|
||||
foreach my $opt_key (sort @{$config->get_keys}) {
|
||||
next if $Slic3r::Config::Options->{$opt_key}{shortcut};
|
||||
printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key);
|
||||
}
|
||||
}
|
||||
|
||||
# close our gcode file
|
||||
|
@ -1069,30 +1117,11 @@ sub expanded_output_filepath {
|
|||
# path is a full path to a file so we use it as it is
|
||||
}
|
||||
|
||||
# make sure we use an up-to-date timestamp
|
||||
$self->placeholder_parser->update_timestamp;
|
||||
return $self->placeholder_parser->process($path, $extra);
|
||||
}
|
||||
|
||||
sub invalidate_step {
|
||||
my ($self, $step, $obj_idx) = @_;
|
||||
|
||||
# invalidate $step in the correct state object
|
||||
if ($Slic3r::Print::State::print_step->{$step}) {
|
||||
$self->_state->invalidate($step);
|
||||
} else {
|
||||
# object step
|
||||
if (defined $obj_idx) {
|
||||
$self->objects->[$obj_idx]->_state->invalidate($step);
|
||||
} else {
|
||||
$_->_state->invalidate($step) for @{$self->objects};
|
||||
}
|
||||
}
|
||||
|
||||
# recursively invalidate steps depending on $step
|
||||
$self->invalidate_step($_)
|
||||
for grep { grep { $_ == $step } @{$Slic3r::Print::State::prereqs{$_}} }
|
||||
keys %Slic3r::Print::State::prereqs;
|
||||
}
|
||||
|
||||
# This method assigns extruders to the volumes having a material
|
||||
# but not having extruders set in the material config.
|
||||
sub auto_assign_extruders {
|
||||
|
@ -1105,7 +1134,7 @@ sub auto_assign_extruders {
|
|||
foreach my $i (0..$#{$model_object->volumes}) {
|
||||
my $volume = $model_object->volumes->[$i];
|
||||
if (defined $volume->material_id) {
|
||||
my $material = $model_object->model->materials->{ $volume->material_id };
|
||||
my $material = $model_object->model->get_material($volume->material_id);
|
||||
my $config = $material->config;
|
||||
my $extruder_id = $i + 1;
|
||||
$config->set_ifndef('extruder', $extruder_id);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package Slic3r::Print::Object;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(min max sum first);
|
||||
use Slic3r::Flow ':roles';
|
||||
|
@ -9,65 +10,38 @@ use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union
|
|||
use Slic3r::Print::State ':steps';
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
has 'print' => (is => 'ro', weak_ref => 1, required => 1);
|
||||
has 'model_object' => (is => 'ro', required => 1);
|
||||
has 'region_volumes' => (is => 'rw', default => sub { [] }); # by region_id
|
||||
has 'copies' => (is => 'ro'); # Slic3r::Point objects in scaled G-code coordinates
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new });
|
||||
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
|
||||
|
||||
has 'size' => (is => 'rw'); # XYZ in scaled coordinates
|
||||
has '_copies_shift' => (is => 'rw'); # scaled coordinates to add to copies (to compensate for the alignment operated when creating the object but still preserving a coherent API for external callers)
|
||||
has '_shifted_copies' => (is => 'rw'); # Slic3r::Point objects in scaled G-code coordinates in our coordinates
|
||||
has 'layers' => (is => 'rw', default => sub { [] });
|
||||
has 'support_layers' => (is => 'rw', default => sub { [] });
|
||||
has 'fill_maker' => (is => 'lazy');
|
||||
has '_state' => (is => 'ro', default => sub { Slic3r::Print::State->new });
|
||||
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
# translate meshes so that we work with smaller coordinates
|
||||
{
|
||||
# compute the bounding box of the supplied meshes
|
||||
my @meshes = map $self->model_object->volumes->[$_]->mesh,
|
||||
map @$_,
|
||||
grep defined $_,
|
||||
@{$self->region_volumes};
|
||||
|
||||
my $bb = @meshes
|
||||
? $meshes[0]->bounding_box
|
||||
: Slic3r::Geometry::BoundingBoxf3->new;
|
||||
$bb->merge($_->bounding_box) for @meshes[1..$#meshes];
|
||||
|
||||
# Translate meshes so that our toolpath generation algorithms work with smaller
|
||||
# XY coordinates; this translation is an optimization and not strictly required.
|
||||
# However, this also aligns object to Z = 0, which on the contrary is required
|
||||
# since we don't assume input is already aligned.
|
||||
# We store the XY translation so that we can place copies correctly in the output G-code
|
||||
# (copies are expressed in G-code coordinates and this translation is not publicly exposed).
|
||||
$self->_copies_shift(Slic3r::Point->new_scale($bb->x_min, $bb->y_min));
|
||||
$self->_trigger_copies;
|
||||
|
||||
# Scale the object size and store it
|
||||
my $scaled_bb = $bb->clone;
|
||||
$scaled_bb->scale(1 / &Slic3r::SCALING_FACTOR);
|
||||
$self->size($scaled_bb->size);
|
||||
}
|
||||
}
|
||||
|
||||
sub _build_fill_maker {
|
||||
# TODO: lazy
|
||||
sub fill_maker {
|
||||
my $self = shift;
|
||||
return Slic3r::Fill->new(bounding_box => $self->bounding_box);
|
||||
}
|
||||
|
||||
sub region_volumes {
|
||||
my $self = shift;
|
||||
return [ map $self->get_region_volumes($_), 0..($self->region_count - 1) ];
|
||||
}
|
||||
|
||||
sub layers {
|
||||
my $self = shift;
|
||||
return [ map $self->get_layer($_), 0..($self->layer_count - 1) ];
|
||||
}
|
||||
|
||||
sub support_layers {
|
||||
my $self = shift;
|
||||
return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
|
||||
}
|
||||
|
||||
# TODO: translate to C++, then call it from constructor (see also
|
||||
# Print->add_model_object)
|
||||
sub _trigger_copies {
|
||||
my $self = shift;
|
||||
|
||||
# TODO: should this mean point is 0,0?
|
||||
return if !defined $self->_copies_shift;
|
||||
|
||||
# order copies with a nearest neighbor search and translate them by _copies_shift
|
||||
$self->_shifted_copies([
|
||||
$self->set_shifted_copies([
|
||||
map {
|
||||
my $c = $_->clone;
|
||||
$c->translate(@{ $self->_copies_shift });
|
||||
|
@ -75,35 +49,39 @@ sub _trigger_copies {
|
|||
} @{$self->copies}[@{chained_path($self->copies)}]
|
||||
]);
|
||||
|
||||
$self->print->_state->invalidate(STEP_SKIRT);
|
||||
$self->print->_state->invalidate(STEP_BRIM);
|
||||
$self->print->invalidate_step(STEP_SKIRT);
|
||||
$self->print->invalidate_step(STEP_BRIM);
|
||||
}
|
||||
|
||||
# in unscaled coordinates
|
||||
sub add_copy {
|
||||
my ($self, $x, $y) = @_;
|
||||
push @{$self->copies}, Slic3r::Point->new_scale($x, $y);
|
||||
my @copies = @{$self->copies};
|
||||
push @copies, Slic3r::Point->new_scale($x, $y);
|
||||
$self->set_copies(\@copies);
|
||||
$self->_trigger_copies;
|
||||
}
|
||||
|
||||
sub delete_last_copy {
|
||||
my ($self) = @_;
|
||||
pop @{$self->copies};
|
||||
my @copies = $self->copies;
|
||||
pop @copies;
|
||||
$self->set_copies(\@copies);
|
||||
$self->_trigger_copies;
|
||||
}
|
||||
|
||||
sub delete_all_copies {
|
||||
my ($self) = @_;
|
||||
@{$self->copies} = ();
|
||||
$self->set_copies([]);
|
||||
$self->_trigger_copies;
|
||||
}
|
||||
|
||||
# this is the *total* layer count
|
||||
# this is the *total* layer count (including support layers)
|
||||
# this value is not supposed to be compared with $layer->id
|
||||
# since they have different semantics
|
||||
sub layer_count {
|
||||
sub total_layer_count {
|
||||
my $self = shift;
|
||||
return scalar @{ $self->layers } + scalar @{ $self->support_layers };
|
||||
return $self->layer_count + $self->support_layer_count;
|
||||
}
|
||||
|
||||
sub bounding_box {
|
||||
|
@ -123,11 +101,10 @@ sub slice {
|
|||
|
||||
# init layers
|
||||
{
|
||||
@{$self->layers} = ();
|
||||
$self->clear_layers;
|
||||
|
||||
# make layers taking custom heights into account
|
||||
my $print_z = my $slice_z = my $height = my $id = 0;
|
||||
my $first_object_layer_height = -1;
|
||||
|
||||
# add raft layers
|
||||
if ($self->config->raft_layers > 0) {
|
||||
|
@ -145,7 +122,7 @@ sub slice {
|
|||
|
||||
# force first layer print_z according to the contact distance
|
||||
# (the loop below will raise print_z by such height)
|
||||
$first_object_layer_height = $distance;
|
||||
$print_z += $distance;
|
||||
}
|
||||
|
||||
# loop until we have at least one layer and the max slice_z reaches the object height
|
||||
|
@ -167,24 +144,15 @@ sub slice {
|
|||
}
|
||||
}
|
||||
|
||||
if ($first_object_layer_height != -1 && !@{$self->layers}) {
|
||||
$height = $first_object_layer_height;
|
||||
}
|
||||
|
||||
$print_z += $height;
|
||||
$slice_z += $height/2;
|
||||
|
||||
### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z;
|
||||
|
||||
push @{$self->layers}, Slic3r::Layer->new(
|
||||
object => $self,
|
||||
id => $id,
|
||||
height => $height,
|
||||
print_z => $print_z,
|
||||
slice_z => $slice_z,
|
||||
);
|
||||
$self->add_layer($id, $height, $print_z, $slice_z);
|
||||
if (@{$self->layers} >= 2) {
|
||||
$self->layers->[-2]->upper_layer($self->layers->[-1]);
|
||||
$self->layers->[-2]->set_upper_layer($self->layers->[-1]);
|
||||
$self->layers->[-1]->set_lower_layer($self->layers->[-2]);
|
||||
}
|
||||
$id++;
|
||||
|
||||
|
@ -202,7 +170,7 @@ sub slice {
|
|||
my @z = map $_->slice_z, @{$self->layers};
|
||||
|
||||
# slice all non-modifier volumes
|
||||
for my $region_id (0..$#{$self->region_volumes}) {
|
||||
for my $region_id (0..($self->region_count - 1)) {
|
||||
my $expolygons_by_layer = $self->_slice_region($region_id, \@z, 0);
|
||||
for my $layer_id (0..$#$expolygons_by_layer) {
|
||||
my $layerm = $self->layers->[$layer_id]->regions->[$region_id];
|
||||
|
@ -217,12 +185,12 @@ sub slice {
|
|||
}
|
||||
|
||||
# then slice all modifier volumes
|
||||
if (@{$self->region_volumes} > 1) {
|
||||
for my $region_id (0..$#{$self->region_volumes}) {
|
||||
if ($self->region_count > 1) {
|
||||
for my $region_id (0..$self->region_count) {
|
||||
my $expolygons_by_layer = $self->_slice_region($region_id, \@z, 1);
|
||||
|
||||
# loop through the other regions and 'steal' the slices belonging to this one
|
||||
for my $other_region_id (0..$#{$self->region_volumes}) {
|
||||
for my $other_region_id (0..$self->region_count) {
|
||||
next if $other_region_id == $region_id;
|
||||
|
||||
for my $layer_id (0..$#$expolygons_by_layer) {
|
||||
|
@ -256,16 +224,70 @@ sub slice {
|
|||
}
|
||||
|
||||
# remove last layer(s) if empty
|
||||
pop @{$self->layers} while @{$self->layers} && (!map @{$_->slices}, @{$self->layers->[-1]->regions});
|
||||
$self->delete_layer($self->layer_count - 1)
|
||||
while $self->layer_count && (!map @{$_->slices}, @{$self->layers->[-1]->regions});
|
||||
|
||||
foreach my $layer (@{ $self->layers }) {
|
||||
# apply size compensation
|
||||
if ($self->config->xy_size_compensation != 0) {
|
||||
my $delta = scale($self->config->xy_size_compensation);
|
||||
if (@{$layer->regions} == 1) {
|
||||
# single region
|
||||
my $layerm = $layer->regions->[0];
|
||||
my $slices = [ map $_->p, @{$layerm->slices} ];
|
||||
$layerm->slices->clear;
|
||||
$layerm->slices->append(Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNAL,
|
||||
)) for @{offset_ex($slices, $delta)};
|
||||
} else {
|
||||
if ($delta < 0) {
|
||||
# multiple regions, shrinking
|
||||
# we apply the offset to the combined shape, then intersect it
|
||||
# with the original slices for each region
|
||||
my $slices = union([ map $_->p, map @{$_->slices}, @{$layer->regions} ]);
|
||||
$slices = offset($slices, $delta);
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
my $this_slices = intersection_ex(
|
||||
$slices,
|
||||
[ map $_->p, @{$layerm->slices} ],
|
||||
);
|
||||
$layerm->slices->clear;
|
||||
$layerm->slices->append(Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNAL,
|
||||
)) for @$this_slices;
|
||||
}
|
||||
} else {
|
||||
# multiple regions, growing
|
||||
# this is an ambiguous case, since it's not clear how to grow regions where they are going to overlap
|
||||
# so we give priority to the first one and so on
|
||||
for my $i (0..$#{$layer->regions}) {
|
||||
my $layerm = $layer->regions->[$i];
|
||||
my $slices = offset_ex([ map $_->p, @{$layerm->slices} ], $delta);
|
||||
if ($i > 0) {
|
||||
$slices = diff_ex(
|
||||
[ map @$_, @$slices ],
|
||||
[ map $_->p, map @{$_->slices}, map $layer->regions->[$_], 0..($i-1) ], # slices of already processed regions
|
||||
);
|
||||
}
|
||||
$layerm->slices->clear;
|
||||
$layerm->slices->append(Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNAL,
|
||||
)) for @$slices;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# merge all regions' slices to get islands
|
||||
$layer->make_slices;
|
||||
}
|
||||
|
||||
# detect slicing errors
|
||||
my $warning_thrown = 0;
|
||||
for my $i (0 .. $#{$self->layers}) {
|
||||
for my $i (0 .. ($self->layer_count - 1)) {
|
||||
my $layer = $self->layers->[$i];
|
||||
next unless $layer->slicing_errors;
|
||||
if (!$warning_thrown) {
|
||||
|
@ -278,11 +300,11 @@ sub slice {
|
|||
# neighbor layers
|
||||
Slic3r::debugf "Attempting to repair layer %d\n", $i;
|
||||
|
||||
foreach my $region_id (0 .. $#{$layer->regions}) {
|
||||
foreach my $region_id (0 .. ($layer->region_count - 1)) {
|
||||
my $layerm = $layer->region($region_id);
|
||||
|
||||
my (@upper_surfaces, @lower_surfaces);
|
||||
for (my $j = $i+1; $j <= $#{$self->layers}; $j++) {
|
||||
for (my $j = $i+1; $j < $self->layer_count; $j++) {
|
||||
if (!$self->layers->[$j]->slicing_errors) {
|
||||
@upper_surfaces = @{$self->layers->[$j]->region($region_id)->slices};
|
||||
last;
|
||||
|
@ -316,11 +338,10 @@ sub slice {
|
|||
}
|
||||
|
||||
# remove empty layers from bottom
|
||||
my $first_object_layer_id = $self->config->raft_layers;
|
||||
while (@{$self->layers} && !@{$self->layers->[$first_object_layer_id]->slices}) {
|
||||
splice @{$self->layers}, $first_object_layer_id, 1;
|
||||
for (my $i = $first_object_layer_id; $i <= $#{$self->layers}; $i++) {
|
||||
$self->layers->[$i]->id($i);
|
||||
while (@{$self->layers} && !@{$self->layers->[0]->slices}) {
|
||||
shift @{$self->layers};
|
||||
for (my $i = 0; $i <= $#{$self->layers}; $i++) {
|
||||
$self->layers->[$i]->id( $self->layers->[$i]->id-1 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,11 +354,11 @@ sub slice {
|
|||
sub _slice_region {
|
||||
my ($self, $region_id, $z, $modifier) = @_;
|
||||
|
||||
return [] if !defined $self->region_volumes->[$region_id];
|
||||
|
||||
return [] if !@{$self->get_region_volumes($region_id)};
|
||||
|
||||
# compose mesh
|
||||
my $mesh;
|
||||
foreach my $volume_id (@{$self->region_volumes->[$region_id]}) {
|
||||
foreach my $volume_id (@{ $self->get_region_volumes($region_id) }) {
|
||||
my $volume = $self->model_object->volumes->[$volume_id];
|
||||
next if $volume->modifier && !$modifier;
|
||||
next if !$volume->modifier && $modifier;
|
||||
|
@ -377,15 +398,16 @@ sub make_perimeters {
|
|||
my $region_perimeters = $region->config->perimeters;
|
||||
|
||||
if ($region->config->extra_perimeters && $region_perimeters > 0 && $region->config->fill_density > 0) {
|
||||
for my $i (0 .. $#{$self->layers}-1) {
|
||||
for my $i (0 .. ($self->layer_count - 2)) {
|
||||
my $layerm = $self->layers->[$i]->regions->[$region_id];
|
||||
my $upper_layerm = $self->layers->[$i+1]->regions->[$region_id];
|
||||
my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing;
|
||||
my $ext_perimeter_spacing = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_spacing;
|
||||
|
||||
my $overlap = $perimeter_spacing; # one perimeter
|
||||
|
||||
my $diff = diff(
|
||||
offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($region_perimeters * $perimeter_spacing)),
|
||||
offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($ext_perimeter_spacing + ($region_perimeters-1) * $perimeter_spacing)),
|
||||
offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap),
|
||||
);
|
||||
next if !@$diff;
|
||||
|
@ -426,7 +448,7 @@ sub make_perimeters {
|
|||
|
||||
Slic3r::parallelize(
|
||||
threads => $self->print->config->threads,
|
||||
items => sub { 0 .. $#{$self->layers} },
|
||||
items => sub { 0 .. ($self->layer_count - 1) },
|
||||
thread_cb => sub {
|
||||
my $q = shift;
|
||||
while (defined (my $i = $q->dequeue)) {
|
||||
|
@ -450,7 +472,7 @@ sub detect_surfaces_type {
|
|||
Slic3r::debugf "Detecting solid surfaces...\n";
|
||||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
for my $i (0 .. $#{$self->layers}) {
|
||||
for my $i (0 .. ($self->layer_count - 1)) {
|
||||
my $layerm = $self->layers->[$i]->regions->[$region_id];
|
||||
|
||||
# prepare a reusable subroutine to make surface differences
|
||||
|
@ -462,7 +484,7 @@ sub detect_surfaces_type {
|
|||
);
|
||||
|
||||
# collapse very narrow parts (using the safety offset in the diff is not enough)
|
||||
my $offset = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width / 10;
|
||||
my $offset = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width / 10;
|
||||
return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
|
||||
@{ offset2_ex($diff, -$offset, +$offset) };
|
||||
};
|
||||
|
@ -553,6 +575,10 @@ sub detect_surfaces_type {
|
|||
# clip surfaces to the fill boundaries
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
my $layerm = $layer->regions->[$region_id];
|
||||
|
||||
# Note: this method should be idempotent, but fill_surfaces gets modified
|
||||
# in place. However we're now only using its boundaries (which are invariant)
|
||||
# so we're safe
|
||||
my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ];
|
||||
$layerm->fill_surfaces->clear;
|
||||
foreach my $surface (@{$layerm->slices}) {
|
||||
|
@ -575,10 +601,10 @@ sub clip_fill_surfaces {
|
|||
# We only want infill under ceilings; this is almost like an
|
||||
# internal support material.
|
||||
|
||||
my $additional_margin = scale 3;
|
||||
my $additional_margin = scale 3*0;
|
||||
|
||||
my $overhangs = []; # arrayref of polygons
|
||||
for my $layer_id (reverse 0..$#{$self->layers}) {
|
||||
for my $layer_id (reverse 0..($self->layer_count - 1)) {
|
||||
my $layer = $self->layers->[$layer_id];
|
||||
my @layer_internal = (); # arrayref of Surface objects
|
||||
my @new_internal = (); # arrayref of Surface objects
|
||||
|
@ -614,10 +640,11 @@ sub clip_fill_surfaces {
|
|||
# (thus we also consider perimeters)
|
||||
if ($layer_id > 0) {
|
||||
my $solid = diff(
|
||||
[ map @$_, @{$layer->slices} ],
|
||||
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
|
||||
[ map $_->p, @layer_internal ],
|
||||
);
|
||||
$overhangs = offset($solid, +$additional_margin);
|
||||
|
||||
push @$overhangs, map $_->p, @new_internal; # propagate upper overhangs
|
||||
}
|
||||
}
|
||||
|
@ -626,11 +653,11 @@ sub clip_fill_surfaces {
|
|||
sub bridge_over_infill {
|
||||
my $self = shift;
|
||||
|
||||
for my $region_id (0..$#{$self->print->regions}) {
|
||||
for my $region_id (0..($self->print->region_count - 1)) {
|
||||
my $fill_density = $self->print->regions->[$region_id]->config->fill_density;
|
||||
next if $fill_density == 100 || $fill_density == 0;
|
||||
|
||||
for my $layer_id (1..$#{$self->layers}) {
|
||||
for my $layer_id (1..($self->layer_count - 1)) {
|
||||
my $layer = $self->layers->[$layer_id];
|
||||
my $layerm = $layer->regions->[$region_id];
|
||||
my $lower_layer = $self->layers->[$layer_id-1];
|
||||
|
@ -704,7 +731,7 @@ sub process_external_surfaces {
|
|||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
$self->layers->[0]->regions->[$region_id]->process_external_surfaces(undef);
|
||||
for my $i (1 .. $#{$self->layers}) {
|
||||
for my $i (1 .. ($self->layer_count - 1)) {
|
||||
$self->layers->[$i]->regions->[$region_id]->process_external_surfaces($self->layers->[$i-1]);
|
||||
}
|
||||
}
|
||||
|
@ -716,7 +743,7 @@ sub discover_horizontal_shells {
|
|||
Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
|
||||
|
||||
for my $region_id (0 .. ($self->print->regions_count-1)) {
|
||||
for (my $i = 0; $i <= $#{$self->layers}; $i++) {
|
||||
for (my $i = 0; $i < $self->layer_count; $i++) {
|
||||
my $layerm = $self->layers->[$i]->regions->[$region_id];
|
||||
|
||||
if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0
|
||||
|
@ -740,7 +767,7 @@ sub discover_horizontal_shells {
|
|||
];
|
||||
next if !@$solid;
|
||||
Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom';
|
||||
|
||||
|
||||
my $solid_layers = ($type == S_TYPE_TOP)
|
||||
? $layerm->config->top_solid_layers
|
||||
: $layerm->config->bottom_solid_layers;
|
||||
|
@ -748,7 +775,7 @@ sub discover_horizontal_shells {
|
|||
abs($n - $i) <= $solid_layers-1;
|
||||
($type == S_TYPE_TOP) ? $n-- : $n++) {
|
||||
|
||||
next if $n < 0 || $n > $#{$self->layers};
|
||||
next if $n < 0 || $n >= $self->layer_count;
|
||||
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
|
||||
|
||||
my $neighbor_layerm = $self->layers->[$n]->regions->[$region_id];
|
||||
|
@ -766,8 +793,8 @@ sub discover_horizontal_shells {
|
|||
# upper perimeter as an obstacle and shell will not be propagated to more upper layers
|
||||
my $new_internal_solid = $solid = intersection(
|
||||
$solid,
|
||||
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ],
|
||||
1,
|
||||
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @{$neighbor_layerm->slices} ],
|
||||
0
|
||||
);
|
||||
next EXTERNAL if !@$new_internal_solid;
|
||||
|
||||
|
@ -776,7 +803,7 @@ sub discover_horizontal_shells {
|
|||
# than a perimeter width, since it's probably just crossing a sloping wall
|
||||
# and it's not wanted in a hollow print even if it would make sense when
|
||||
# obeying the solid shell count option strictly (DWIM!)
|
||||
my $margin = $neighbor_layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width;
|
||||
my $margin = $neighbor_layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width;
|
||||
my $too_narrow = diff(
|
||||
$new_internal_solid,
|
||||
offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5),
|
||||
|
@ -817,22 +844,31 @@ sub discover_horizontal_shells {
|
|||
}
|
||||
}
|
||||
|
||||
# intersect with fill surfaces before assigning to object
|
||||
$new_internal_solid = intersection($new_internal_solid, [ map { $_->p } @neighbor_fill_surfaces ]);
|
||||
|
||||
# internal-solid are the union of the existing internal-solid surfaces
|
||||
# and new ones
|
||||
my $internal_solid = union_ex([
|
||||
my $internal_solid = union([
|
||||
( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ),
|
||||
@$new_internal_solid,
|
||||
]);
|
||||
|
||||
# subtract intersections from layer surfaces to get resulting internal surfaces
|
||||
my $internal = diff_ex(
|
||||
my $internal = diff(
|
||||
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ],
|
||||
[ map @$_, @$internal_solid ],
|
||||
$internal_solid,
|
||||
1,
|
||||
);
|
||||
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n",
|
||||
scalar(@$internal_solid), scalar(@$internal);
|
||||
|
||||
# we don't want to affect top, bottom and, most importantly, bottom bridge surfaces!
|
||||
my @neighbor_solid = grep { ($_->surface_type == S_TYPE_TOP) || $_->is_bottom } @neighbor_fill_surfaces;
|
||||
my $neighbor_solid_p = [ map { $_->p } @neighbor_solid ];
|
||||
$internal_solid = diff_ex($internal_solid, $neighbor_solid_p) if @$internal_solid;
|
||||
$internal = diff_ex($internal, $neighbor_solid_p) if @$internal;
|
||||
|
||||
# assign resulting internal surfaces to layer
|
||||
$neighbor_fill_surfaces->clear;
|
||||
$neighbor_fill_surfaces->append(map Slic3r::Surface->new
|
||||
|
@ -843,14 +879,7 @@ sub discover_horizontal_shells {
|
|||
(expolygon => $_, surface_type => S_TYPE_INTERNALSOLID), @$internal_solid);
|
||||
|
||||
# assign top and bottom surfaces to layer
|
||||
foreach my $s (@{Slic3r::Surface::Collection->new(grep { ($_->surface_type == S_TYPE_TOP) || $_->is_bottom } @neighbor_fill_surfaces)->group}) {
|
||||
my $solid_surfaces = diff_ex(
|
||||
[ map $_->p, @$s ],
|
||||
[ map @$_, @$internal_solid, @$internal ],
|
||||
1,
|
||||
);
|
||||
$neighbor_fill_surfaces->append(map $s->[0]->clone(expolygon => $_), @$solid_surfaces);
|
||||
}
|
||||
$neighbor_fill_surfaces->append(@neighbor_solid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -972,6 +1001,9 @@ sub combine_infill {
|
|||
|
||||
sub generate_support_material {
|
||||
my $self = shift;
|
||||
|
||||
$self->clear_support_layers;
|
||||
|
||||
return unless ($self->config->support_material || $self->config->raft_layers > 0)
|
||||
&& scalar(@{$self->layers}) >= 2;
|
||||
|
||||
|
@ -1014,7 +1046,7 @@ sub support_material_flow {
|
|||
# we use a bogus layer_height because we use the same flow for all
|
||||
# support material layers
|
||||
return Slic3r::Flow->new_from_width(
|
||||
width => $self->config->support_material_extrusion_width,
|
||||
width => $self->config->support_material_extrusion_width || $self->config->extrusion_width,
|
||||
role => $role,
|
||||
nozzle_diameter => $self->print->config->nozzle_diameter->[$extruder-1] // $self->print->config->nozzle_diameter->[0],
|
||||
layer_height => $self->config->layer_height,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package Slic3r::Print::Region;
|
||||
use Moo;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::Extruder ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
|
@ -7,11 +8,8 @@ use Slic3r::Flow ':roles';
|
|||
# A Print::Region object represents a group of volumes to print
|
||||
# sharing the same config (including the same assigned extruder(s))
|
||||
|
||||
has 'print' => (is => 'ro', required => 1, weak_ref => 1);
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new});
|
||||
|
||||
sub flow {
|
||||
my ($self, $role, $layer_height, $bridge, $first_layer, $width) = @_;
|
||||
my ($self, $role, $layer_height, $bridge, $first_layer, $width, $object) = @_;
|
||||
|
||||
$bridge //= 0;
|
||||
$first_layer //= 0;
|
||||
|
@ -23,6 +21,8 @@ sub flow {
|
|||
# (might be an absolute value, or a percent value, or zero for auto)
|
||||
if ($first_layer && $self->print->config->first_layer_extrusion_width) {
|
||||
$config_width = $self->print->config->first_layer_extrusion_width;
|
||||
} elsif ($role == FLOW_ROLE_EXTERNAL_PERIMETER) {
|
||||
$config_width = $self->config->external_perimeter_extrusion_width;
|
||||
} elsif ($role == FLOW_ROLE_PERIMETER) {
|
||||
$config_width = $self->config->perimeter_extrusion_width;
|
||||
} elsif ($role == FLOW_ROLE_INFILL) {
|
||||
|
@ -35,11 +35,14 @@ sub flow {
|
|||
die "Unknown role $role";
|
||||
}
|
||||
}
|
||||
if ($config_width eq '0') {
|
||||
$config_width = $object->config->extrusion_width;
|
||||
}
|
||||
|
||||
# get the configured nozzle_diameter for the extruder associated
|
||||
# to the flow role requested
|
||||
my $extruder; # 1-based
|
||||
if ($role == FLOW_ROLE_PERIMETER) {
|
||||
if ($role == FLOW_ROLE_PERIMETER || $role == FLOW_ROLE_EXTERNAL_PERIMETER) {
|
||||
$extruder = $self->config->perimeter_extruder;
|
||||
} elsif ($role == FLOW_ROLE_INFILL || $role == FLOW_ROLE_SOLID_INFILL || $role == FLOW_ROLE_TOP_SOLID_INFILL) {
|
||||
$extruder = $self->config->infill_extruder;
|
||||
|
@ -53,7 +56,7 @@ sub flow {
|
|||
role => $role,
|
||||
nozzle_diameter => $nozzle_diameter,
|
||||
layer_height => $layer_height,
|
||||
bridge_flow_ratio => ($bridge ? $self->print->config->bridge_flow_ratio : 0),
|
||||
bridge_flow_ratio => ($bridge ? $self->config->bridge_flow_ratio : 0),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ has '_print' => (
|
|||
is => 'ro',
|
||||
default => sub { Slic3r::Print->new },
|
||||
handles => [qw(apply_config extruders expanded_output_filepath
|
||||
total_used_filament total_extruded_volume)],
|
||||
total_used_filament total_extruded_volume
|
||||
placeholder_parser)],
|
||||
);
|
||||
|
||||
has 'duplicate' => (
|
||||
|
@ -43,20 +44,15 @@ sub set_model {
|
|||
my ($self, $model) = @_;
|
||||
|
||||
# make method idempotent so that the object is reusable
|
||||
$self->_print->delete_all_objects;
|
||||
$self->_print->clear_objects;
|
||||
|
||||
my $need_arrange = $model->has_objects_with_no_instances;
|
||||
if ($need_arrange) {
|
||||
# apply a default position to all objects not having one
|
||||
foreach my $object (@{$model->objects}) {
|
||||
$object->add_instance(offset => [0,0]) if !defined $object->instances;
|
||||
}
|
||||
}
|
||||
# make sure all objects have at least one defined instance
|
||||
my $need_arrange = $model->add_default_instances;
|
||||
|
||||
# apply scaling and rotation supplied from command line if any
|
||||
foreach my $instance (map @{$_->instances}, @{$model->objects}) {
|
||||
$instance->scaling_factor($instance->scaling_factor * $self->scale);
|
||||
$instance->rotation($instance->rotation + $self->rotate);
|
||||
$instance->set_scaling_factor($instance->scaling_factor * $self->scale);
|
||||
$instance->set_rotation($instance->rotation + $self->rotate);
|
||||
}
|
||||
|
||||
if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) {
|
||||
|
@ -78,14 +74,14 @@ sub set_model {
|
|||
sub _before_export {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_print->status_cb($self->status_cb);
|
||||
$self->_print->set_status_cb($self->status_cb);
|
||||
$self->_print->validate;
|
||||
}
|
||||
|
||||
sub _after_export {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_print->status_cb(undef);
|
||||
$self->_print->set_status_cb(undef);
|
||||
}
|
||||
|
||||
sub export_gcode {
|
||||
|
|
|
@ -8,21 +8,4 @@ our @EXPORT_OK = qw(STEP_INIT_EXTRUDERS STEP_SLICE STEP_PERIMETERS STEP_PREPAR
|
|||
STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM);
|
||||
our %EXPORT_TAGS = (steps => \@EXPORT_OK);
|
||||
|
||||
our %print_steps = map { $_ => 1 } (
|
||||
STEP_INIT_EXTRUDERS,
|
||||
STEP_SKIRT,
|
||||
STEP_BRIM,
|
||||
);
|
||||
|
||||
our %prereqs = (
|
||||
STEP_INIT_EXTRUDERS => [],
|
||||
STEP_SLICE => [],
|
||||
STEP_PERIMETERS => [STEP_SLICE, STEP_INIT_EXTRUDERS],
|
||||
STEP_PREPARE_INFILL => [STEP_PERIMETERS],
|
||||
STEP_INFILL => [STEP_INFILL],
|
||||
STEP_SUPPORTMATERIAL => [STEP_SLICE, STEP_INIT_EXTRUDERS],
|
||||
STEP_SKIRT => [STEP_PERIMETERS, STEP_INFILL],
|
||||
STEP_BRIM => [STEP_PERIMETERS, STEP_INFILL, STEP_SKIRT],
|
||||
);
|
||||
|
||||
1;
|
||||
|
|
|
@ -6,7 +6,7 @@ use Slic3r::ExtrusionPath ':roles';
|
|||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(scale scaled_epsilon PI rad2deg deg2rad convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2
|
||||
intersection_pl);
|
||||
intersection_pl offset2_ex diff_pl);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
has 'print_config' => (is => 'rw', required => 1);
|
||||
|
@ -70,14 +70,17 @@ sub generate {
|
|||
$self->clip_with_shape($base, $shape) if @$shape;
|
||||
|
||||
# Install support layers into object.
|
||||
push @{$object->support_layers}, map Slic3r::Layer::Support->new(
|
||||
object => $object,
|
||||
id => $_,
|
||||
height => ($_ == 0) ? $support_z->[$_] : ($support_z->[$_] - $support_z->[$_-1]),
|
||||
print_z => $support_z->[$_],
|
||||
slice_z => -1,
|
||||
slices => [],
|
||||
), 0 .. $#$support_z;
|
||||
for my $i (0 .. $#$support_z) {
|
||||
$object->add_support_layer(
|
||||
$i, # id
|
||||
($i == 0) ? $support_z->[$i] : ($support_z->[$i] - $support_z->[$i-1]), # height
|
||||
$support_z->[$i], # print_z
|
||||
-1); # slice_z
|
||||
if ($i >= 1) {
|
||||
$object->support_layers->[-2]->set_upper_layer($object->support_layers->[-1]);
|
||||
$object->support_layers->[-1]->set_lower_layer($object->support_layers->[-2]);
|
||||
}
|
||||
}
|
||||
|
||||
# Generate the actual toolpaths and save them into each layer.
|
||||
$self->generate_toolpaths($object, $overhang, $contact, $interface, $base);
|
||||
|
@ -115,52 +118,113 @@ sub contact_area {
|
|||
if ($layer_id == 0) {
|
||||
# this is the first object layer, so we're here just to get the object
|
||||
# footprint for the raft
|
||||
push @overhang, map $_->clone, map @$_, @{$layer->slices};
|
||||
# we only consider contours and discard holes to get a more continuous raft
|
||||
push @overhang, map $_->clone, map $_->contour, @{$layer->slices};
|
||||
push @contact, @{offset(\@overhang, scale +MARGIN)};
|
||||
} else {
|
||||
my $lower_layer = $object->layers->[$layer_id-1];
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
my $fw = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width;
|
||||
my $fw = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width;
|
||||
my $diff;
|
||||
|
||||
# If a threshold angle was specified, use a different logic for detecting overhangs.
|
||||
if (defined $threshold_rad
|
||||
|| $layer_id < $self->object_config->support_material_enforce_layers
|
||||
|| $self->object_config->raft_layers > 0) {
|
||||
my $d = defined $threshold_rad
|
||||
my $d = 0;
|
||||
if ($layer_id > $self->object_config->support_material_enforce_layers) {
|
||||
$d = defined $threshold_rad
|
||||
? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad))
|
||||
: 0;
|
||||
|
||||
$diff = diff(
|
||||
offset([ map $_->p, @{$layerm->slices} ], -$d),
|
||||
[ map @$_, @{$lower_layer->slices} ],
|
||||
);
|
||||
|
||||
# only enforce spacing from the object ($fw/2) if the threshold angle
|
||||
# is not too high: in that case, $d will be very small (as we need to catch
|
||||
# very short overhangs), and such contact area would be eaten by the
|
||||
# enforced spacing, resulting in high threshold angles to be almost ignored
|
||||
$diff = diff(
|
||||
offset($diff, $d - $fw/2),
|
||||
[ map @$_, @{$lower_layer->slices} ],
|
||||
) if $d > $fw/2;
|
||||
} else {
|
||||
$diff = diff(
|
||||
offset([ map $_->p, @{$layerm->slices} ], -$fw/2),
|
||||
[ map @$_, @{$lower_layer->slices} ],
|
||||
);
|
||||
|
||||
# collapse very tiny spots
|
||||
$diff = offset2($diff, -$fw/10, +$fw/10);
|
||||
|
||||
# $diff now contains the ring or stripe comprised between the boundary of
|
||||
# lower slices and the centerline of the last perimeter in this overhanging layer.
|
||||
# Void $diff means that there's no upper perimeter whose centerline is
|
||||
# outside the lower slice boundary, thus no overhang
|
||||
: $fw*2;
|
||||
}
|
||||
|
||||
# TODO: this is the place to remove bridged areas
|
||||
$diff = diff(
|
||||
[ map $_->p, @{$layerm->slices} ],
|
||||
offset([ map @$_, @{$lower_layer->slices} ], +$d),
|
||||
);
|
||||
|
||||
# collapse very tiny spots
|
||||
$diff = offset2($diff, -$fw/10, +$fw/10);
|
||||
|
||||
# $diff now contains the ring or stripe comprised between the boundary of
|
||||
# lower slices and the centerline of the last perimeter in this overhanging layer.
|
||||
# Void $diff means that there's no upper perimeter whose centerline is
|
||||
# outside the lower slice boundary, thus no overhang
|
||||
|
||||
if ($self->object_config->dont_support_bridges) {
|
||||
# compute the area of bridging perimeters
|
||||
# Note: this is duplicate code from GCode.pm, we need to refactor
|
||||
|
||||
my $bridged_perimeters; # Polygons
|
||||
{
|
||||
my $bridge_flow = $layerm->flow(FLOW_ROLE_PERIMETER, 1);
|
||||
|
||||
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $layerm->region->config->perimeter_extruder-1);
|
||||
my $lower_grown_slices = offset([ map @$_, @{$lower_layer->slices} ], +scale($nozzle_diameter/2));
|
||||
|
||||
# TODO: split_at_first_point() could split a bridge mid-way
|
||||
my @overhang_perimeters =
|
||||
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline->clone }
|
||||
@{$layerm->perimeters};
|
||||
|
||||
# workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
|
||||
$_->[0]->translate(1,0) for @overhang_perimeters;
|
||||
@overhang_perimeters = @{diff_pl(
|
||||
\@overhang_perimeters,
|
||||
$lower_grown_slices,
|
||||
)};
|
||||
|
||||
# only consider straight overhangs
|
||||
@overhang_perimeters = grep $_->is_straight, @overhang_perimeters;
|
||||
|
||||
# only consider overhangs having endpoints inside layer's slices
|
||||
foreach my $polyline (@overhang_perimeters) {
|
||||
$polyline->extend_start($fw);
|
||||
$polyline->extend_end($fw);
|
||||
}
|
||||
@overhang_perimeters = grep {
|
||||
$layer->slices->contains_point($_->first_point) && $layer->slices->contains_point($_->last_point)
|
||||
} @overhang_perimeters;
|
||||
|
||||
# convert bridging polylines into polygons by inflating them with their thickness
|
||||
{
|
||||
# since we're dealing with bridges, we can't assume width is larger than spacing,
|
||||
# so we take the largest value and also apply safety offset to be ensure no gaps
|
||||
# are left in between
|
||||
my $w = max($bridge_flow->scaled_width, $bridge_flow->scaled_spacing);
|
||||
$bridged_perimeters = union([
|
||||
map @{$_->grow($w/2 + 10)}, @overhang_perimeters
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (1) {
|
||||
# remove the entire bridges and only support the unsupported edges
|
||||
my @bridges = map $_->expolygon,
|
||||
grep $_->bridge_angle != -1,
|
||||
@{$layerm->fill_surfaces->filter_by_type(S_TYPE_BOTTOMBRIDGE)};
|
||||
|
||||
$diff = diff(
|
||||
$diff,
|
||||
[
|
||||
(map @$_, @bridges),
|
||||
@$bridged_perimeters,
|
||||
],
|
||||
1,
|
||||
);
|
||||
|
||||
push @$diff, @{intersection(
|
||||
[ map @{$_->grow(+scale MARGIN)}, @{$layerm->unsupported_bridge_edges} ],
|
||||
[ map @$_, @bridges ],
|
||||
)};
|
||||
|
||||
} else {
|
||||
# just remove bridged areas
|
||||
$diff = diff(
|
||||
$diff,
|
||||
[ map @$_, @{$layerm->bridged} ],
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
next if !@$diff;
|
||||
push @overhang, @$diff; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
|
||||
|
||||
|
@ -169,7 +233,7 @@ sub contact_area {
|
|||
# We increment the area in steps because we don't want our support to overflow
|
||||
# on the other side of the object (if it's very thin).
|
||||
{
|
||||
my @slices_margin = @{offset([ map @$_, @{$lower_layer->slices} ], $fw/2)};
|
||||
my @slices_margin = @{offset([ map @$_, @{$lower_layer->slices} ], +$fw/2)};
|
||||
for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) {
|
||||
$diff = diff(
|
||||
offset($diff, $_),
|
||||
|
@ -190,7 +254,7 @@ sub contact_area {
|
|||
@{$layer->regions};
|
||||
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
|
||||
|
||||
my $contact_z = $layer->print_z - contact_distance($nozzle_diameter);
|
||||
my $contact_z = $layer->print_z - contact_distance($nozzle_diameter) - $layer->height;
|
||||
###$contact_z = $layer->print_z - $layer->height;
|
||||
|
||||
# ignore this contact area if it's too low
|
||||
|
@ -258,7 +322,7 @@ sub support_layers_z {
|
|||
# we use max() to prevent many ultra-thin layers to be inserted in case
|
||||
# layer_height > nozzle_diameter * 0.75
|
||||
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1);
|
||||
my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75);
|
||||
my $support_material_height = $max_object_layer_height;#max($max_object_layer_height, $nozzle_diameter * 0.75);
|
||||
|
||||
my @z = sort { $a <=> $b } @$contact_z, @$top_z, (map $_ + $nozzle_diameter, @$top_z);
|
||||
|
||||
|
@ -273,9 +337,9 @@ sub support_layers_z {
|
|||
# $z[1] is last raft layer (contact layer for the first layer object)
|
||||
my $height = ($z[1] - $z[0]) / ($self->object_config->raft_layers - 1);
|
||||
splice @z, 1, 0,
|
||||
map { int($_*100)/100 }
|
||||
map { sprintf "%.2f", $_ }
|
||||
map { $z[0] + $height * $_ }
|
||||
0..($self->object_config->raft_layers - 1);
|
||||
1..($self->object_config->raft_layers - 2);
|
||||
}
|
||||
|
||||
for (my $i = $#z; $i >= 0; $i--) {
|
||||
|
@ -306,13 +370,13 @@ sub generate_interface_layers {
|
|||
|
||||
# let's now generate interface layers below contact areas
|
||||
my %interface = (); # layer_id => [ polygons ]
|
||||
my $interface_layers = $self->object_config->support_material_interface_layers;
|
||||
my $interface_layers_num = $self->object_config->support_material_interface_layers;
|
||||
for my $layer_id (0 .. $#$support_z) {
|
||||
my $z = $support_z->[$layer_id];
|
||||
my $this = $contact->{$z} // next;
|
||||
|
||||
# count contact layer as interface layer
|
||||
for (my $i = $layer_id-1; $i >= 0 && $i > $layer_id-$interface_layers; $i--) {
|
||||
for (my $i = $layer_id-1; $i >= 0 && $i > $layer_id-$interface_layers_num; $i--) {
|
||||
$z = $support_z->[$i];
|
||||
my @overlapping_layers = $self->overlapping_layers($i, $support_z);
|
||||
my @overlapping_z = map $support_z->[$_], @overlapping_layers;
|
||||
|
@ -377,6 +441,9 @@ sub generate_base_layers {
|
|||
return $base;
|
||||
}
|
||||
|
||||
# This method removes object silhouette from support material
|
||||
# (it's used with interface and base only). It removes a bit more,
|
||||
# leaving a thin gap between object and support in the XY plane.
|
||||
sub clip_with_object {
|
||||
my ($self, $support, $support_z, $object) = @_;
|
||||
|
||||
|
@ -388,6 +455,10 @@ sub clip_with_object {
|
|||
my @layers = grep { $_->print_z > $zmin && ($_->print_z - $_->height) < $zmax }
|
||||
@{$object->layers};
|
||||
|
||||
# $layer->slices contains the full shape of layer, thus including
|
||||
# perimeter's width. $support contains the full shape of support
|
||||
# material, thus including the width of its foremost extrusion.
|
||||
# We leave a gap equal to a full extrusion width.
|
||||
$support->{$i} = diff(
|
||||
$support->{$i},
|
||||
offset([ map @$_, map @{$_->slices}, @layers ], +$self->flow->scaled_width),
|
||||
|
@ -463,12 +534,19 @@ sub generate_toolpaths {
|
|||
# if no interface layers were requested we treat the contact layer
|
||||
# exactly as a generic base layer
|
||||
push @$base, @$contact;
|
||||
} elsif (@$contact && $contact_loops > 0) {
|
||||
} else {
|
||||
push @$interface, @$contact;
|
||||
}
|
||||
if (0 && @$contact && $contact_loops > 0) { # WTF?!!
|
||||
# generate the outermost loop
|
||||
|
||||
# find centerline of the external loop (or any other kind of extrusions should the loop be skipped)
|
||||
$contact = offset($contact, -$interface_flow->scaled_width/2);
|
||||
|
||||
my @loops0 = ();
|
||||
{
|
||||
# find centerline of the external loop of the contours
|
||||
my @external_loops = @{offset($contact, -$interface_flow->scaled_width/2)};
|
||||
my @external_loops = @$contact;
|
||||
|
||||
# only consider the loops facing the overhang
|
||||
{
|
||||
|
@ -513,11 +591,13 @@ sub generate_toolpaths {
|
|||
);
|
||||
|
||||
# transform loops into ExtrusionPath objects
|
||||
my $mm3_per_mm = $interface_flow->mm3_per_mm($layer->height);
|
||||
my $mm3_per_mm = $interface_flow->mm3_per_mm;
|
||||
@loops = map Slic3r::ExtrusionPath->new(
|
||||
polyline => $_,
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL,
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $interface_flow->width,
|
||||
height => $layer->height,
|
||||
), @loops;
|
||||
|
||||
$layer->support_interface_fills->append(@loops);
|
||||
|
@ -527,6 +607,9 @@ sub generate_toolpaths {
|
|||
if (@$interface || @$contact_infill) {
|
||||
$fillers{interface}->angle($interface_angle);
|
||||
|
||||
# find centerline of the external loop
|
||||
$interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $interface_flow->scaled_width/2));
|
||||
|
||||
# join regions by offsetting them to ensure they're merged
|
||||
$interface = offset([ @$interface, @$contact_infill ], scaled_epsilon);
|
||||
|
||||
|
@ -545,21 +628,40 @@ sub generate_toolpaths {
|
|||
}
|
||||
}
|
||||
$base = diff($base, $interface);
|
||||
|
||||
|
||||
my @paths = ();
|
||||
foreach my $expolygon (@{union_ex($interface)}) {
|
||||
my $to_infill = union_ex($interface);
|
||||
|
||||
# draw a perimeter all around interface infill
|
||||
# TODO: use brim ordering algorithm
|
||||
my $mm3_per_mm = $flow->mm3_per_mm;
|
||||
push @paths, map Slic3r::ExtrusionPath->new(
|
||||
polyline => $_->split_at_first_point,
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $layer->height,
|
||||
), map @$_, @$to_infill;
|
||||
|
||||
# TODO: use offset2_ex()
|
||||
$to_infill = offset_ex([ map @$_, @$to_infill ], -$flow->scaled_spacing);
|
||||
|
||||
foreach my $expolygon (@$to_infill) {
|
||||
my ($params, @p) = $fillers{interface}->fill_surface(
|
||||
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
|
||||
density => $interface_density,
|
||||
flow => $interface_flow,
|
||||
layer_height => $layer->height,
|
||||
complete => 1,
|
||||
);
|
||||
my $mm3_per_mm = $params->{flow}->mm3_per_mm($layer->height);
|
||||
my $mm3_per_mm = $params->{flow}->mm3_per_mm;
|
||||
|
||||
push @paths, map Slic3r::ExtrusionPath->new(
|
||||
polyline => Slic3r::Polyline->new(@$_),
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL,
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $params->{flow}->width,
|
||||
height => $layer->height,
|
||||
), @p;
|
||||
}
|
||||
|
||||
|
@ -573,8 +675,9 @@ sub generate_toolpaths {
|
|||
my $density = $support_density;
|
||||
my $base_flow = $flow;
|
||||
|
||||
# TODO: use offset2_ex()
|
||||
my $to_infill = union_ex($base, 1);
|
||||
# find centerline of the external loop/extrusions
|
||||
my $to_infill = offset2_ex($base, +scaled_epsilon, -(scaled_epsilon + $flow->scaled_width/2));
|
||||
|
||||
my @paths = ();
|
||||
|
||||
# base flange
|
||||
|
@ -583,33 +686,38 @@ sub generate_toolpaths {
|
|||
$filler->angle($self->object_config->support_material_angle + 90);
|
||||
$density = 0.5;
|
||||
$base_flow = $self->first_layer_flow;
|
||||
} else {
|
||||
# draw a perimeter all around support infill
|
||||
# TODO: use brim ordering algorithm
|
||||
my $mm3_per_mm = $flow->mm3_per_mm($layer->height);
|
||||
push @paths, map Slic3r::ExtrusionPath->new(
|
||||
polyline => $_->split_at_first_point,
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
), map @$_, @$to_infill;
|
||||
|
||||
# TODO: use offset2_ex()
|
||||
$to_infill = offset_ex([ map @$_, @$to_infill ], -$flow->scaled_spacing);
|
||||
}
|
||||
|
||||
# draw a perimeter all around support infill
|
||||
# TODO: use brim ordering algorithm
|
||||
my $mm3_per_mm = $flow->mm3_per_mm;
|
||||
push @paths, map Slic3r::ExtrusionPath->new(
|
||||
polyline => $_->split_at_first_point,
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $layer->height,
|
||||
), map @$_, @$to_infill;
|
||||
|
||||
# TODO: use offset2_ex()
|
||||
$to_infill = offset_ex([ map @$_, @$to_infill ], -$flow->scaled_spacing);
|
||||
|
||||
foreach my $expolygon (@$to_infill) {
|
||||
my ($params, @p) = $filler->fill_surface(
|
||||
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
|
||||
density => $density,
|
||||
flow => $base_flow,
|
||||
layer_height => $layer->height,
|
||||
complete => 1,
|
||||
);
|
||||
my $mm3_per_mm = $params->{flow}->mm3_per_mm($layer->height);
|
||||
my $mm3_per_mm = $params->{flow}->mm3_per_mm;
|
||||
|
||||
push @paths, map Slic3r::ExtrusionPath->new(
|
||||
polyline => Slic3r::Polyline->new(@$_),
|
||||
role => EXTR_ROLE_SUPPORTMATERIAL,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $params->{flow}->width,
|
||||
height => $layer->height,
|
||||
), @p;
|
||||
}
|
||||
|
||||
|
@ -645,6 +753,9 @@ sub generate_toolpaths {
|
|||
sub generate_pillars_shape {
|
||||
my ($self, $contact, $support_z, $shape) = @_;
|
||||
|
||||
# this prevents supplying an empty point set to BoundingBox constructor
|
||||
return if !%$contact;
|
||||
|
||||
my $pillar_size = scale PILLAR_SIZE;
|
||||
my $pillar_spacing = scale PILLAR_SPACING;
|
||||
|
||||
|
@ -656,7 +767,7 @@ sub generate_pillars_shape {
|
|||
[$pillar_size, $pillar_size],
|
||||
[0, $pillar_size],
|
||||
);
|
||||
|
||||
|
||||
my @pillars = ();
|
||||
my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, values %$contact ]);
|
||||
for (my $x = $bb->x_min; $x <= $bb->x_max-$pillar_size; $x += $pillar_spacing) {
|
||||
|
@ -718,6 +829,11 @@ sub clip_with_shape {
|
|||
my ($self, $support, $shape) = @_;
|
||||
|
||||
foreach my $i (keys %$support) {
|
||||
# don't clip bottom layer with shape so that we
|
||||
# can generate a continuous base flange
|
||||
# also don't clip raft layers
|
||||
next if $i == 0;
|
||||
next if $i < $self->object_config->raft_layers;
|
||||
$support->{$i} = intersection(
|
||||
$support->{$i},
|
||||
$shape->[$i],
|
||||
|
@ -739,10 +855,10 @@ sub overlapping_layers {
|
|||
} 0..$#$support_z;
|
||||
}
|
||||
|
||||
# class method
|
||||
# wanted Z distance between support/raft material and next object layer
|
||||
sub contact_distance {
|
||||
my ($nozzle_diameter) = @_;
|
||||
return $nozzle_diameter * 1.5;
|
||||
return 0; #$nozzle_diameter * 0.5;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,19 +5,21 @@ use List::Util qw(first max);
|
|||
use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2 unscale);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex intersection_pl);
|
||||
use SVG;
|
||||
use Slic3r::SVG;
|
||||
|
||||
has 'scale' => (is => 'ro', default => sub {30});
|
||||
has 'print' => (is => 'ro', required => 1);
|
||||
has 'y_percent' => (is => 'ro', default => sub {0.5});
|
||||
has 'line' => (is => 'lazy');
|
||||
has 'line' => (is => 'rw');
|
||||
has 'height' => (is => 'rw');
|
||||
|
||||
sub _build_line {
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
my $bb = $self->print->bounding_box;
|
||||
my $y = $bb->size->[Y] * $self->y_percent;
|
||||
return Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ]);
|
||||
my $y = ($bb->y_min + $bb->y_max) * $self->y_percent;
|
||||
my $line = Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ]);
|
||||
$self->line($line);
|
||||
}
|
||||
|
||||
sub export_svg {
|
||||
|
@ -79,27 +81,40 @@ sub _plot {
|
|||
my (@rectangles, @circles) = ();
|
||||
|
||||
foreach my $object (@{$self->print->objects}) {
|
||||
foreach my $copy (@{$object->shifted_copies}) {
|
||||
foreach my $copy (@{$object->_shifted_copies}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
# get all ExtrusionPath objects
|
||||
my @paths =
|
||||
map { $_->polyline->translate(@$copy); $_ }
|
||||
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_ }
|
||||
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_->clone }
|
||||
map { $_->isa('Slic3r::ExtrusionPath::Collection') ? @$_ : $_ }
|
||||
grep defined $_,
|
||||
$filter->($layer);
|
||||
|
||||
$_->polyline->translate(@$copy) for @paths;
|
||||
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"line.svg",
|
||||
no_arrows => 1,
|
||||
#polygon => $line->grow(Slic3r::Geometry::scale $path->width/2),
|
||||
polygons => [ $object->bounding_box->polygon ],
|
||||
lines => [ $self->line ],
|
||||
red_polylines => [ map $_->polyline, @paths ],
|
||||
);
|
||||
exit;
|
||||
|
||||
foreach my $path (@paths) {
|
||||
foreach my $line (@{$path->lines}) {
|
||||
my @intersections = @{intersection_pl(
|
||||
[ $self->line->as_polyline ],
|
||||
$line->grow(Slic3r::Geometry::scale $path->flow_spacing/2),
|
||||
$line->grow(Slic3r::Geometry::scale $path->width/2),
|
||||
)};
|
||||
|
||||
die "Intersection has more than two points!\n" if first { @$_ > 2 } @intersections;
|
||||
|
||||
if ($path->is_bridge) {
|
||||
foreach my $line (@intersections) {
|
||||
my $radius = $path->flow_spacing / 2;
|
||||
my $radius = $path->width / 2;
|
||||
my $width = unscale abs($line->[B][X] - $line->[A][X]);
|
||||
if ((10 * Slic3r::Geometry::scale $radius) < $width) {
|
||||
# we're cutting the path in the longitudinal direction, so we've got a rectangle
|
||||
|
@ -148,7 +163,7 @@ sub _plot {
|
|||
sub _y {
|
||||
my $self = shift;
|
||||
my ($y) = @_;
|
||||
|
||||
return $y;
|
||||
return $self->height - $y;
|
||||
}
|
||||
|
||||
|
|
64
slic3r.pl
64
slic3r.pl
|
@ -36,6 +36,7 @@ my %cli_options = ();
|
|||
'export-svg' => \$opt{export_svg},
|
||||
'merge|m' => \$opt{merge},
|
||||
'repair' => \$opt{repair},
|
||||
'cut=f' => \$opt{cut},
|
||||
'info' => \$opt{info},
|
||||
|
||||
'scale=f' => \$opt{scale},
|
||||
|
@ -52,9 +53,6 @@ my %cli_options = ();
|
|||
GetOptions(%options) or usage(1);
|
||||
}
|
||||
|
||||
# process command line options
|
||||
my $cli_config = Slic3r::Config->new_from_cli(%cli_options);
|
||||
|
||||
# load configuration files
|
||||
my @external_configs = ();
|
||||
if ($opt{load}) {
|
||||
|
@ -70,16 +68,22 @@ if ($opt{load}) {
|
|||
}
|
||||
}
|
||||
|
||||
# merge configuration
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->apply($_) for @external_configs, $cli_config;
|
||||
# process command line options
|
||||
my $cli_config = Slic3r::Config->new;
|
||||
foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) {
|
||||
$c->normalize; # expand shortcuts before applying, otherwise destination values would be already filled with defaults
|
||||
$cli_config->apply($c);
|
||||
}
|
||||
|
||||
# save configuration
|
||||
if ($opt{save}) {
|
||||
$config->validate;
|
||||
$config->save($opt{save});
|
||||
$cli_config->save($opt{save});
|
||||
}
|
||||
|
||||
# apply command line config on top of default config
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->apply($cli_config);
|
||||
|
||||
# launch GUI
|
||||
my $gui;
|
||||
if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") {
|
||||
|
@ -117,6 +121,25 @@ if (@ARGV) { # slicing from command line
|
|||
exit;
|
||||
}
|
||||
|
||||
if ($opt{cut}) {
|
||||
foreach my $file (@ARGV) {
|
||||
my $model = Slic3r::Model->read_from_file($file);
|
||||
$model->add_default_instances;
|
||||
my $mesh = $model->mesh;
|
||||
$mesh->translate(0, 0, -$mesh->bounding_box->z_min);
|
||||
my $upper = Slic3r::TriangleMesh->new;
|
||||
my $lower = Slic3r::TriangleMesh->new;
|
||||
$mesh->cut($opt{cut}, $upper, $lower);
|
||||
$upper->repair;
|
||||
$lower->repair;
|
||||
$upper->write_ascii("${file}_upper.stl")
|
||||
if $upper->facets_count > 0;
|
||||
$lower->write_ascii("${file}_lower.stl")
|
||||
if $lower->facets_count > 0;
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
while (my $input_file = shift @ARGV) {
|
||||
my $model;
|
||||
if ($opt{merge}) {
|
||||
|
@ -149,7 +172,6 @@ if (@ARGV) { # slicing from command line
|
|||
|
||||
$sprint->apply_config($config);
|
||||
$sprint->set_model($model);
|
||||
undef $model; # free memory
|
||||
|
||||
if ($opt{export_svg}) {
|
||||
$sprint->export_svg;
|
||||
|
@ -195,11 +217,16 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
|
|||
--load <file> Load configuration from the specified file. It can be used
|
||||
more than once to load options from multiple files.
|
||||
-o, --output <file> File to output gcode to (by default, the file will be saved
|
||||
into the same directory as the input file using the
|
||||
--output-filename-format to generate the filename)
|
||||
into the same directory as the input file using the
|
||||
--output-filename-format to generate the filename.) If a
|
||||
directory is specified for this option, the output will
|
||||
be saved under that directory, and the filename will be
|
||||
generated by --output-filename-format.
|
||||
|
||||
Non-slicing actions (no G-code will be generated):
|
||||
--repair Repair given STL files and save them as <name>_fixed.obj
|
||||
--cut <z> Cut given input files at given Z (relative) and export
|
||||
them as <name>_upper.stl and <name>_lower.stl
|
||||
--info Output information about the supplied file(s) and exit
|
||||
|
||||
$j
|
||||
|
@ -266,6 +293,9 @@ $j
|
|||
(default: $config->{top_solid_infill_speed})
|
||||
--support-material-speed
|
||||
Speed of support material print moves in mm/s (default: $config->{support_material_speed})
|
||||
--support-material-interface-speed
|
||||
Speed of support material interface print moves in mm/s or % over support material
|
||||
speed (default: $config->{support_material_interface_speed})
|
||||
--bridge-speed Speed of bridge print moves in mm/s (default: $config->{bridge_speed})
|
||||
--gap-fill-speed Speed of gap fill print moves in mm/s (default: $config->{gap_fill_speed})
|
||||
--first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute
|
||||
|
@ -312,7 +342,7 @@ $j
|
|||
home X axis [G28 X], disable motors [M84]).
|
||||
--layer-gcode Load layer-change G-code from the supplied file (default: nothing).
|
||||
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
|
||||
--randomize-start Randomize starting point across layers (default: yes)
|
||||
--seam-position Position of loop starting points (random/nearest/aligned, default: $config->{seam_position}).
|
||||
--external-perimeters-first Reverse perimeter order. (default: no)
|
||||
--spiral-vase Experimental option to raise Z gradually when printing single-walled vases
|
||||
(default: no)
|
||||
|
@ -329,10 +359,6 @@ $j
|
|||
Quality options (slower slicing):
|
||||
--extra-perimeters Add more perimeters when needed (default: yes)
|
||||
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
|
||||
--start-perimeters-at-concave-points
|
||||
Try to start perimeters at concave points if any (default: no)
|
||||
--start-perimeters-at-non-overhang
|
||||
Try to start perimeters at non-overhang points if any (default: no)
|
||||
--thin-walls Detect single-width walls (default: yes)
|
||||
--overhangs Experimental option to use bridge flow, speed and fan for overhangs
|
||||
(default: yes)
|
||||
|
@ -356,6 +382,8 @@ $j
|
|||
--support-material-enforce-layers
|
||||
Enforce support material on the specified number of layers from bottom,
|
||||
regardless of --support-material and threshold (0+, default: $config->{support_material_enforce_layers})
|
||||
--dont-support-bridges
|
||||
Experimental option for preventing support material from being generated under bridged areas (default: yes)
|
||||
|
||||
Retraction options:
|
||||
--retract-length Length of retraction in mm when pausing extrusion (default: $config->{retract_length}[0])
|
||||
|
@ -408,6 +436,8 @@ $j
|
|||
--bed-size Bed size, only used for auto-arrange (mm, default: $config->{bed_size}->[0],$config->{bed_size}->[1])
|
||||
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
||||
--duplicate-distance Distance in mm between copies (default: $config->{duplicate_distance})
|
||||
--xy-size-compensation
|
||||
Grow/shrink objects by the configured absolute distance (mm, default: $config->{xy_size_compensation})
|
||||
|
||||
Sequential printing options:
|
||||
--complete-objects When printing multiple objects and/or copies, complete each one before
|
||||
|
@ -428,6 +458,8 @@ $j
|
|||
Set a different extrusion width for first layer
|
||||
--perimeter-extrusion-width
|
||||
Set a different extrusion width for perimeters
|
||||
--external-perimeter-extrusion-width
|
||||
Set a different extrusion width for external perimeters
|
||||
--infill-extrusion-width
|
||||
Set a different extrusion width for infill
|
||||
--solid-infill-extrusion-width
|
||||
|
|
71
t/arcs.t
71
t/arcs.t
|
@ -2,8 +2,7 @@ use Test::More;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan skip_all => 'arcs are currently disabled';
|
||||
plan tests => 13;
|
||||
plan tests => 24;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
|
@ -12,25 +11,71 @@ BEGIN {
|
|||
|
||||
use Slic3r;
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Geometry qw(scaled_epsilon scale X Y);
|
||||
use Slic3r::Geometry qw(scaled_epsilon epsilon scale unscale X Y deg2rad);
|
||||
|
||||
{
|
||||
my $path = Slic3r::ExtrusionPath->new(polyline => Slic3r::Polyline->new(
|
||||
[135322.42,26654.96], [187029.11,99546.23], [222515.14,92381.93], [258001.16,99546.23],
|
||||
[286979.42,119083.91], [306517.1,148062.17], [313681.4,183548.2],
|
||||
[306517.1,219034.23], [286979.42,248012.49], [258001.16,267550.17], [222515.14,274714.47],
|
||||
[187029.11,267550.17], [158050.85,248012.49], [138513.17,219034.23], [131348.87,183548.2],
|
||||
[86948.77,175149.09], [119825.35,100585],
|
||||
), role => EXTR_ROLE_FILL, mm3_per_mm => 0.5);
|
||||
my $angle = deg2rad(4);
|
||||
foreach my $ccw (1, 0) {
|
||||
my $polyline = Slic3r::Polyline->new_scale([0,0], [0,10]);
|
||||
{
|
||||
my $p3 = Slic3r::Point->new_scale(0, 20);
|
||||
$p3->rotate($angle * ($ccw ? 1 : -1), $polyline->[-1]);
|
||||
is $ccw, ($p3->[X] < $polyline->[-1][X]) ? 1 : 0, 'third point is rotated correctly';
|
||||
$polyline->append($p3);
|
||||
}
|
||||
ok abs($polyline->length - scale(20)) < scaled_epsilon, 'curved polyline length';
|
||||
is $ccw, ($polyline->[2]->ccw(@$polyline[0,1]) > 0) ? 1 : 0, 'curved polyline has wanted orientation';
|
||||
|
||||
my @paths = $path->detect_arcs(30);
|
||||
ok my $arc = Slic3r::GCode::ArcFitting::polyline_to_arc($polyline), 'arc is detected';
|
||||
is $ccw, $arc->is_ccw, 'arc orientation is correct';
|
||||
|
||||
is scalar(@paths), 3, 'path collection now contains three paths';
|
||||
isa_ok $paths[1], 'Slic3r::ExtrusionPath::Arc', 'second one';
|
||||
ok abs($arc->angle - $angle) < epsilon, 'arc relative angle is correct';
|
||||
|
||||
ok $arc->start->coincides_with($polyline->[0]), 'arc start point is correct';
|
||||
ok $arc->end->coincides_with($polyline->[-1]), 'arc end point is correct';
|
||||
|
||||
# since first polyline segment is vertical we expect arc center to have same Y as its first point
|
||||
is $arc->center->[Y], 0, 'arc center has correct Y';
|
||||
|
||||
my $s1 = Slic3r::Line->new(@$polyline[0,1]);
|
||||
my $s2 = Slic3r::Line->new(@$polyline[1,2]);
|
||||
ok abs($arc->center->distance_to($s1->midpoint) - $arc->center->distance_to($s2->midpoint)) < scaled_epsilon,
|
||||
'arc center is equidistant from both segments\' midpoints';
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my $path = Slic3r::Polyline->new_scale(
|
||||
[13.532242,2.665496], [18.702911,9.954623], [22.251514,9.238193], [25.800116,9.954623],
|
||||
[28.697942,11.908391], [30.65171,14.806217], [31.36814,18.35482],
|
||||
[30.65171,21.903423], [28.697942,24.801249], [25.800116,26.755017], [22.251514,27.471447],
|
||||
[18.702911,26.755017], [15.805085,24.801249], [13.851317,21.903423], [13.134887,18.35482],
|
||||
[86948.77,175149.09], [119825.35,100585],
|
||||
);
|
||||
|
||||
if (0) {
|
||||
require "Slic3r::SVG";
|
||||
Slic3r::SVG::output(
|
||||
"arc.svg",
|
||||
polylines => [$path],
|
||||
);
|
||||
}
|
||||
|
||||
my $af = Slic3r::GCode::ArcFitting->new(max_relative_angle => deg2rad(30));
|
||||
my @chunks = $af->detect_arcs($path);
|
||||
|
||||
is scalar(@chunks), 3, 'path collection now contains three paths';
|
||||
isa_ok $chunks[0], 'Slic3r::Polyline', 'first one is polyline';
|
||||
isa_ok $chunks[1], 'Slic3r::GCode::ArcFitting::Arc', 'second one is arc';
|
||||
isa_ok $chunks[2], 'Slic3r::Polyline', 'third one is polyline';
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my @points = map [ scale $_->[0], scale $_->[1] ], (
|
||||
[10,20], [10.7845909572784,19.9691733373313], [11.5643446504023,19.8768834059514],
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
use Test::More tests => 14;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first sum);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(scale epsilon deg2rad rad2deg PI);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $test = sub {
|
||||
my ($bridge_size, $rotate, $expected_angle, $tolerance) = @_;
|
||||
|
||||
my ($x, $y) = @$bridge_size;
|
||||
my $lower = Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([-2,-2], [$x+2,-2], [$x+2,$y+2], [-2,$y+2]),
|
||||
Slic3r::Polygon->new_scale([0,0], [0,$y], [$x,$y], [$x,0]),
|
||||
);
|
||||
$lower->translate(scale 20, scale 20); # avoid negative coordinates for easier SVG preview
|
||||
$lower->rotate(deg2rad($rotate), [$x/2,$y/2]);
|
||||
my $bridge = $lower->[1]->clone;
|
||||
$bridge->reverse;
|
||||
$bridge = Slic3r::ExPolygon->new($bridge);
|
||||
|
||||
ok check_angle([$lower], $bridge, $expected_angle, $tolerance), 'correct bridge angle for O-shaped overhang';
|
||||
};
|
||||
|
||||
$test->([20,10], 0, 90);
|
||||
$test->([10,20], 0, 0);
|
||||
$test->([20,10], 45, 135, 20);
|
||||
$test->([20,10], 135, 45, 20);
|
||||
}
|
||||
|
||||
{
|
||||
my $bridge = Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([0,0], [20,0], [20,10], [0,10]),
|
||||
);
|
||||
my $lower = [
|
||||
Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([-2,0], [0,0], [0,10], [-2,10]),
|
||||
),
|
||||
];
|
||||
$_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview
|
||||
|
||||
$lower->[1] = $lower->[0]->clone;
|
||||
$lower->[1]->translate(scale 22, 0);
|
||||
|
||||
ok check_angle($lower, $bridge, 0), 'correct bridge angle for two-sided bridge';
|
||||
}
|
||||
|
||||
{
|
||||
my $bridge = Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([0,0], [20,0], [10,10], [0,10]),
|
||||
);
|
||||
my $lower = [
|
||||
Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([0,0], [0,10], [10,10], [10,12], [-2,12], [-2,-2], [22,-2], [22,0]),
|
||||
),
|
||||
];
|
||||
$_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview
|
||||
|
||||
ok check_angle($lower, $bridge, 135), 'correct bridge angle for C-shaped overhang';
|
||||
}
|
||||
|
||||
{
|
||||
my $bridge = Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([10,10],[20,10],[20,20], [10,20]),
|
||||
);
|
||||
my $lower = [
|
||||
Slic3r::ExPolygon->new(
|
||||
Slic3r::Polygon->new_scale([10,10],[10,20],[20,20],[20,30],[0,30],[0,10]),
|
||||
),
|
||||
];
|
||||
$_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview
|
||||
|
||||
ok check_angle($lower, $bridge, 45, undef, $bridge->area/2), 'correct bridge angle for square overhang with L-shaped anchors';
|
||||
}
|
||||
|
||||
sub check_angle {
|
||||
my ($lower, $bridge, $expected, $tolerance, $expected_coverage) = @_;
|
||||
|
||||
$expected_coverage //= -1;
|
||||
$expected_coverage = $bridge->area if $expected_coverage == -1;
|
||||
|
||||
my $bd = Slic3r::Layer::BridgeDetector->new(
|
||||
expolygon => $bridge,
|
||||
lower_slices => $lower,
|
||||
extrusion_width => scale 0.5,
|
||||
);
|
||||
|
||||
$tolerance //= rad2deg($bd->resolution) + epsilon;
|
||||
my $result = $bd->detect_angle;
|
||||
my $coverage = $bd->coverage;
|
||||
is sum(map $_->area, @$coverage), $expected_coverage, 'correct coverage area';
|
||||
|
||||
# our epsilon is equal to the steps used by the bridge detection algorithm
|
||||
###use XXX; YYY [ rad2deg($result), $expected ];
|
||||
# returned value must be non-negative, check for that too
|
||||
my $delta=rad2deg($result) - $expected;
|
||||
$delta-=180 if $delta>=180 - epsilon;
|
||||
return defined $result && $result>=0 && abs($delta) < $tolerance;
|
||||
}
|
||||
|
||||
__END__
|
22
t/clipper.t
22
t/clipper.t
|
@ -2,7 +2,7 @@ use Test::More;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan tests => 3;
|
||||
plan tests => 6;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
|
@ -11,7 +11,7 @@ BEGIN {
|
|||
|
||||
use List::Util qw(sum);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex diff_pl);
|
||||
|
||||
{
|
||||
my $square = [ # ccw
|
||||
|
@ -68,3 +68,21 @@ use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex);
|
|||
'difference of a cw from two ccw is a contour with one hole';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my $square = Slic3r::Polygon->new_scale( # ccw
|
||||
[10, 10],
|
||||
[20, 10],
|
||||
[20, 20],
|
||||
[10, 20],
|
||||
);
|
||||
my $square_pl = $square->split_at_first_point;
|
||||
|
||||
my $res = diff_pl([$square_pl], []);
|
||||
is scalar(@$res), 1, 'no-op diff_pl returns the right number of polylines';
|
||||
isa_ok $res->[0], 'Slic3r::Polyline', 'no-op diff_pl result';
|
||||
is scalar(@{$res->[0]}), scalar(@$square_pl), 'no-op diff_pl returns the unmodified input polyline';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 6;
|
||||
use Test::More tests => 13;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -46,7 +46,7 @@ use Slic3r::Test;
|
|||
{
|
||||
my $parser = Slic3r::GCode::PlaceholderParser->new;
|
||||
$parser->apply_config(my $config = Slic3r::Config->new_from_defaults);
|
||||
is $parser->process('[temperature_[foo]]', { foo => '0' }),
|
||||
is $parser->process('[temperature_[foo]]', { foo => '1' }),
|
||||
$config->temperature->[0],
|
||||
"nested config options";
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ use Slic3r::Test;
|
|||
$config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n");
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
|
||||
my $output_file = $print->expanded_output_filepath;
|
||||
my $output_file = $print->print->expanded_output_filepath;
|
||||
ok $output_file !~ /\[travel_speed\]/, 'print config options are replaced in output filename';
|
||||
ok $output_file !~ /\[layer_height\]/, 'region config options are replaced in output filename';
|
||||
|
||||
|
@ -67,4 +67,41 @@ use Slic3r::Test;
|
|||
ok $gcode =~ /HEIGHT:$h/, 'region config options are replaced in custom G-code';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('extruder', 2);
|
||||
$config->set('first_layer_temperature', [200,205]);
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder';
|
||||
ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored';
|
||||
}
|
||||
|
||||
$config->set('infill_extruder', 1);
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder';
|
||||
ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder';
|
||||
}
|
||||
|
||||
$config->set('start_gcode', qq!
|
||||
;__temp0:[first_layer_temperature_0]__
|
||||
;__temp1:[first_layer_temperature_1]__
|
||||
;__temp2:[first_layer_temperature_2]__
|
||||
!);
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
# we use the [infill_extruder] placeholder to make sure this test doesn't
|
||||
# catch a false positive caused by the unparsed start G-code option itself
|
||||
# being embedded in the G-code
|
||||
ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated';
|
||||
ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated';
|
||||
ok $gcode =~ /temp2:200/, 'tempearture placeholder for unused extruder populated with first value';
|
||||
}
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
57
t/fill.t
57
t/fill.t
|
@ -2,17 +2,17 @@ use Test::More;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan tests => 42;
|
||||
plan tests => 43;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use List::Util qw(first sum);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(scale X Y convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(union diff_ex);
|
||||
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(union diff_ex offset);
|
||||
use Slic3r::Surface qw(:types);
|
||||
use Slic3r::Test;
|
||||
|
||||
|
@ -51,7 +51,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||
);
|
||||
foreach my $angle (0, 45) {
|
||||
$surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]);
|
||||
my ($params, @paths) = $filler->fill_surface($surface, flow => $flow, density => 0.4);
|
||||
my ($params, @paths) = $filler->fill_surface($surface, flow => $flow, layer_height => 0.4, density => 0.4);
|
||||
is scalar @paths, 1, 'one continuous path';
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||
my ($params, @paths) = $filler->fill_surface(
|
||||
$surface,
|
||||
flow => $flow,
|
||||
layer_height => 0.4,
|
||||
density => $density // 1,
|
||||
);
|
||||
|
||||
|
@ -170,6 +171,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||
for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('fill_pattern', $pattern);
|
||||
$config->set('solid_fill_pattern', $pattern);
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('fill_density', 20);
|
||||
|
@ -200,8 +202,46 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
|
|||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('infill_only_where_needed', 1);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
ok my $gcode = Slic3r::Test::gcode($print), "successful G-code generation when infill_only_where_needed is set";
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('infill_extrusion_width', 0.5);
|
||||
$config->set('cooling', 0); # for preventing speeds from being altered
|
||||
$config->set('first_layer_speed', '100%'); # for preventing speeds from being altered
|
||||
|
||||
my $test = sub {
|
||||
my $print = Slic3r::Test::init_print('pyramid', config => $config);
|
||||
|
||||
my $tool = undef;
|
||||
my @infill_extrusions = (); # array of polylines
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->infill_extruder-1) {
|
||||
push @infill_extrusions, Slic3r::Line->new_scale(
|
||||
[ $self->X, $self->Y ],
|
||||
[ $info->{new_X}, $info->{new_Y} ],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points
|
||||
|
||||
my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]);
|
||||
return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))});
|
||||
};
|
||||
|
||||
my $tolerance = 5; # mm^2
|
||||
|
||||
$config->set('solid_infill_below_area', 0);
|
||||
ok $test->() < $tolerance,
|
||||
'no infill is generated when using infill_only_where_needed on a pyramid';
|
||||
|
||||
$config->set('solid_infill_below_area', 70);
|
||||
ok abs($test->() - $config->solid_infill_below_area) < $tolerance,
|
||||
'infill is only generated under the forced solid shells';
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -235,6 +275,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
|
|||
$config->set('nozzle_diameter', [0.35]);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('infill_extrusion_width', 0.52);
|
||||
$config->set('first_layer_extrusion_width', 0);
|
||||
|
||||
my $print = Slic3r::Test::init_print('A', config => $config);
|
||||
my %infill = (); # Z => [ Line, Line ... ]
|
||||
|
@ -258,7 +299,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
|
|||
my $grow_d = scale($config->infill_extrusion_width)/2;
|
||||
my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
|
||||
my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
|
||||
my $diff = [ grep $_->area >= 2*$grow_d**2, @{diff_ex($layer0_infill, $layer1_infill)} ];
|
||||
my $diff = [ grep $_->area >= 4*($grow_d**2), @{diff_ex($layer0_infill, $layer1_infill)} ];
|
||||
is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
use Test::More tests => 6;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first sum);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(scale PI);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 1);
|
||||
$config->set('brim_width', 2);
|
||||
$config->set('perimeters', 3);
|
||||
$config->set('fill_density', 0.4);
|
||||
$config->set('bottom_solid_layers', 1);
|
||||
$config->set('first_layer_extrusion_width', 2);
|
||||
$config->set('first_layer_height', '100%');
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my @E_per_mm = ();
|
||||
Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($self->Z == $config->layer_height) { # only consider first layer
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
push @E_per_mm, $info->{dist_E} / $info->{dist_XY};
|
||||
}
|
||||
}
|
||||
});
|
||||
my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm;
|
||||
ok !(defined first { abs($_ - $E_per_mm_avg) > 0.01 } @E_per_mm),
|
||||
'first_layer_extrusion_width applies to everything on first layer';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('bridge_speed', 99);
|
||||
$config->set('bridge_flow_ratio', 1);
|
||||
$config->set('cooling', 0); # to prevent speeds from being altered
|
||||
$config->set('first_layer_speed', '100%'); # to prevent speeds from being altered
|
||||
|
||||
my $test = sub {
|
||||
my $print = Slic3r::Test::init_print('overhang', config => $config);
|
||||
my @E_per_mm = ();
|
||||
Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if (($args->{F} // $self->F) == $config->bridge_speed*60) {
|
||||
push @E_per_mm, $info->{dist_E} / $info->{dist_XY};
|
||||
}
|
||||
}
|
||||
});
|
||||
my $expected_mm3_per_mm = ($config->nozzle_diameter->[0]**2) * PI/4 * $config->bridge_flow_ratio;
|
||||
my $expected_E_per_mm = $expected_mm3_per_mm / ((($config->filament_diameter->[0]/2)**2)*PI);
|
||||
ok !(defined first { abs($_ - $expected_E_per_mm) > 0.01 } @E_per_mm),
|
||||
'expected flow when using bridge_flow_ratio = ' . $config->bridge_flow_ratio;
|
||||
};
|
||||
|
||||
$config->set('bridge_flow_ratio', 0.5);
|
||||
$test->();
|
||||
$config->set('bridge_flow_ratio', 2);
|
||||
$test->();
|
||||
$config->set('extrusion_width', 0.4);
|
||||
$config->set('bridge_flow_ratio', 1);
|
||||
$test->();
|
||||
$config->set('bridge_flow_ratio', 0.5);
|
||||
$test->();
|
||||
$config->set('bridge_flow_ratio', 2);
|
||||
$test->();
|
||||
}
|
||||
|
||||
__END__
|
|
@ -0,0 +1,60 @@
|
|||
use Test::More tests => 1;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(PI scale unscale convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw();
|
||||
use Slic3r::Surface ':types';
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
$config->set('perimeter_speed', 66);
|
||||
$config->set('external_perimeter_speed', 66);
|
||||
$config->set('small_perimeter_speed', 66);
|
||||
$config->set('gap_fill_speed', 99);
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('cooling', 0); # to prevent speeds from being altered
|
||||
$config->set('first_layer_speed', '100%'); # to prevent speeds from being altered
|
||||
$config->set('perimeter_extrusion_width', 0.35);
|
||||
$config->set('first_layer_extrusion_width', 0.35);
|
||||
|
||||
my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config);
|
||||
my @perimeter_points = ();
|
||||
my $last = ''; # perimeter | gap
|
||||
my $gap_fills_outside_last_perimeters = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
my $F = $args->{F} // $self->F;
|
||||
my $point = Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y});
|
||||
if ($F == $config->perimeter_speed*60) {
|
||||
if ($last eq 'gap') {
|
||||
@perimeter_points = ();
|
||||
}
|
||||
push @perimeter_points, $point;
|
||||
$last = 'perimeter';
|
||||
} elsif ($F == $config->gap_fill_speed*60) {
|
||||
my $convex_hull = convex_hull(\@perimeter_points);
|
||||
if (!$convex_hull->contains_point($point)) {
|
||||
$gap_fills_outside_last_perimeters++;
|
||||
}
|
||||
|
||||
$last = 'gap';
|
||||
}
|
||||
}
|
||||
});
|
||||
is $gap_fills_outside_last_perimeters, 0, 'gap fills are printed before leaving islands';
|
||||
}
|
||||
|
||||
__END__
|
69
t/gcode.t
69
t/gcode.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 8;
|
||||
use Test::More tests => 11;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -9,7 +9,7 @@ BEGIN {
|
|||
|
||||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(scale);
|
||||
use Slic3r::Geometry qw(scale convex_hull);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
|
@ -46,6 +46,7 @@ use Slic3r::Test;
|
|||
# - complete objects does not crash
|
||||
# - no hard-coded "E" are generated
|
||||
# - Z moves are correctly generated for both objects
|
||||
# - no travel moves go outside skirt
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('gcode_comments', 1);
|
||||
$config->set('complete_objects', 1);
|
||||
|
@ -56,16 +57,30 @@ use Slic3r::Test;
|
|||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
|
||||
ok my $gcode = Slic3r::Test::gcode($print), "complete_objects";
|
||||
my @z_moves = ();
|
||||
my @travel_moves = (); # array of scaled points
|
||||
my @extrusions = (); # array of scaled points
|
||||
Slic3r::GCode::Reader->new->parse($gcode, sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
fail 'unexpected E argument' if defined $args->{E};
|
||||
if (defined $args->{Z}) {
|
||||
push @z_moves, $args->{Z};
|
||||
}
|
||||
|
||||
if ($info->{dist_XY}) {
|
||||
if ($info->{extruding} || $args->{A}) {
|
||||
push @extrusions, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y});
|
||||
} else {
|
||||
push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y})
|
||||
if @extrusions; # skip initial travel move to first skirt point
|
||||
}
|
||||
}
|
||||
});
|
||||
my $layer_count = 20/0.4; # cube is 20mm tall
|
||||
is scalar(@z_moves), 2*$layer_count, 'complete_objects generates the correct number of Z moves';
|
||||
is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves';
|
||||
|
||||
my $convex_hull = convex_hull(\@extrusions);
|
||||
ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt';
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -78,24 +93,46 @@ use Slic3r::Test;
|
|||
|
||||
|
||||
});
|
||||
ok $print->total_used_filament > 0, 'final retraction is not considered in total used filament';
|
||||
ok $print->print->total_used_filament > 0, 'final retraction is not considered in total used filament';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('gcode_flavor', 'sailfish');
|
||||
$config->set('raft_layers', 3);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my @percent = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
my $test = sub {
|
||||
my ($print, $comment) = @_;
|
||||
|
||||
if ($cmd eq 'M73') {
|
||||
push @percent, $args->{P};
|
||||
}
|
||||
});
|
||||
# the extruder heater is turned off when M73 P100 is reached
|
||||
ok !(defined first { $_ > 100 } @percent), 'M73 is never given more than 100%';
|
||||
my @percent = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'M73') {
|
||||
push @percent, $args->{P};
|
||||
}
|
||||
});
|
||||
# the extruder heater is turned off when M73 P100 is reached
|
||||
ok !(defined first { $_ > 100 } @percent), "M73 is never given more than 100% ($comment)";
|
||||
};
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('gcode_flavor', 'sailfish');
|
||||
$config->set('raft_layers', 3);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
$test->($print, 'single object');
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('gcode_flavor', 'sailfish');
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
|
||||
$test->($print, 'two copies of single object');
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('gcode_flavor', 'sailfish');
|
||||
my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config);
|
||||
$test->($print, 'two objects');
|
||||
}
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
37
t/geometry.t
37
t/geometry.t
|
@ -2,7 +2,7 @@ use Test::More;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan tests => 28;
|
||||
plan tests => 33;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
|
@ -13,6 +13,17 @@ use Slic3r;
|
|||
use Slic3r::Geometry qw(PI polygon_is_convex
|
||||
chained_path_from epsilon scale);
|
||||
|
||||
{
|
||||
# this test was failing on Windows (GH #1950)
|
||||
my $polygon = Slic3r::Polygon->new(
|
||||
[207802834,-57084522],[196528149,-37556190],[173626821,-25420928],[171285751,-21366123],
|
||||
[118673592,-21366123],[116332562,-25420928],[93431208,-37556191],[82156517,-57084523],
|
||||
[129714478,-84542120],[160244873,-84542120],
|
||||
);
|
||||
my $point = Slic3r::Point->new(95706562, -57294774);
|
||||
ok $polygon->contains_point($point), 'contains_point';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
my $line1 = [ [5, 15], [30, 15] ];
|
||||
|
@ -178,3 +189,27 @@ my $polygons = [
|
|||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my $square = Slic3r::Polygon->new_scale(
|
||||
[150,100],
|
||||
[200,100],
|
||||
[200,200],
|
||||
[100,200],
|
||||
[100,100],
|
||||
);
|
||||
is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon';
|
||||
is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square';
|
||||
}
|
||||
|
||||
{
|
||||
my $square = Slic3r::Polygon->new_scale(
|
||||
[200,200],
|
||||
[100,200],
|
||||
[100,100],
|
||||
[150,100],
|
||||
[200,100],
|
||||
);
|
||||
is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon';
|
||||
is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square';
|
||||
}
|
||||
|
|
86
t/layers.t
86
t/layers.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 4;
|
||||
use Test::More tests => 5;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -11,49 +11,67 @@ use List::Util qw(first);
|
|||
use Slic3r;
|
||||
use Slic3r::Test qw(_eq);
|
||||
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
|
||||
my $test = sub {
|
||||
my ($conf) = @_;
|
||||
$conf ||= $config;
|
||||
my $test = sub {
|
||||
my ($conf) = @_;
|
||||
$conf ||= $config;
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $conf);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $conf);
|
||||
|
||||
my @z = ();
|
||||
my @increments = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($info->{dist_Z}) {
|
||||
push @z, 1*$args->{Z};
|
||||
push @increments, $info->{dist_Z};
|
||||
}
|
||||
});
|
||||
|
||||
fail 'wrong first layer height'
|
||||
if $z[0] ne $config->get_value('first_layer_height') + $config->z_offset;
|
||||
|
||||
fail 'wrong second layer height'
|
||||
if $z[1] ne $config->get_value('first_layer_height') + $config->get_value('layer_height') + $config->z_offset;
|
||||
|
||||
fail 'wrong layer height'
|
||||
if first { !_eq($_, $config->layer_height) } @increments[1..$#increments];
|
||||
|
||||
1;
|
||||
};
|
||||
|
||||
$config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code
|
||||
$config->set('layer_height', 0.3);
|
||||
$config->set('first_layer_height', 0.2);
|
||||
ok $test->(), "absolute first layer height";
|
||||
|
||||
$config->set('first_layer_height', '60%');
|
||||
ok $test->(), "relative first layer height";
|
||||
|
||||
$config->set('z_offset', 0.9);
|
||||
ok $test->(), "positive Z offset";
|
||||
|
||||
$config->set('z_offset', -0.8);
|
||||
ok $test->(), "negative Z offset";
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('fill_density', 0); # just for making the test faster
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
|
||||
|
||||
my @z = ();
|
||||
my @increments = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($info->{dist_Z}) {
|
||||
push @z, 1*$args->{Z};
|
||||
push @increments, $info->{dist_Z};
|
||||
}
|
||||
});
|
||||
|
||||
fail 'wrong first layer height'
|
||||
if $z[0] ne $config->get_value('first_layer_height') + $config->z_offset;
|
||||
|
||||
fail 'wrong second layer height'
|
||||
if $z[1] ne $config->get_value('first_layer_height') + $config->get_value('layer_height') + $config->z_offset;
|
||||
|
||||
fail 'wrong layer height'
|
||||
if first { !_eq($_, $config->layer_height) } @increments[1..$#increments];
|
||||
|
||||
1;
|
||||
};
|
||||
|
||||
$config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code
|
||||
$config->set('layer_height', 0.3);
|
||||
$config->set('first_layer_height', 0.2);
|
||||
ok $test->(), "absolute first layer height";
|
||||
|
||||
$config->set('first_layer_height', '60%');
|
||||
ok $test->(), "relative first layer height";
|
||||
|
||||
$config->set('z_offset', 0.9);
|
||||
ok $test->(), "positive Z offset";
|
||||
|
||||
$config->set('z_offset', -0.8);
|
||||
ok $test->(), "negative Z offset";
|
||||
ok $z[-1] > 20*1.8 && $z[-1] < 20*2.2, 'resulting G-code has reasonable height';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
83
t/multi.t
83
t/multi.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 6;
|
||||
use Test::More tests => 13;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -10,6 +10,7 @@ BEGIN {
|
|||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(scale convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(offset);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
|
@ -39,9 +40,11 @@ use Slic3r::Test;
|
|||
: $config->temperature->[$tool];
|
||||
die 'standby temperature was not set before toolchange'
|
||||
if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta;
|
||||
|
||||
# ignore initial toolchange
|
||||
push @toolchange_points, Slic3r::Point->new_scale($self->X, $self->Y);
|
||||
}
|
||||
$tool = $1;
|
||||
push @toolchange_points, Slic3r::Point->new_scale($self->X, $self->Y);
|
||||
} elsif ($cmd eq 'M104' || $cmd eq 'M109') {
|
||||
my $t = $args->{T} // $tool;
|
||||
if ($tool_temp[$t] == 0) {
|
||||
|
@ -55,7 +58,12 @@ use Slic3r::Test;
|
|||
}
|
||||
});
|
||||
my $convex_hull = convex_hull(\@extrusion_points);
|
||||
ok !(first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt';
|
||||
ok !(defined first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt';
|
||||
|
||||
# offset the skirt by the maximum displacement between extruders plus a safety extra margin
|
||||
my $delta = scale(20 * sqrt(2) + 1);
|
||||
my $outer_convex_hull = offset([$convex_hull], +$delta)->[0];
|
||||
ok !(defined first { !$outer_convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen within expected area';
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -67,13 +75,28 @@ use Slic3r::Test;
|
|||
}
|
||||
|
||||
{
|
||||
my $model = Slic3r::Model->new;
|
||||
my $object = $model->add_object;
|
||||
my $lower_config = $model->set_material('lower')->config;
|
||||
my $upper_config = $model->set_material('upper')->config;
|
||||
$object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube'), material_id => 'lower');
|
||||
$object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube', translate => [0,0,20]), material_id => 'upper');
|
||||
$object->add_instance(offset => [0,0]);
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('extruder', 2);
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
like Slic3r::Test::gcode($print), qr/ T1/, 'extruder shortcut';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('perimeter_extruder', 2);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('support_material_extruder', 2);
|
||||
$config->set('support_material_interface_extruder', 2);
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
ok Slic3r::Test::gcode($print), 'no errors when using multiple skirts with a single, non-zero, extruder';
|
||||
}
|
||||
|
||||
{
|
||||
my $model = stacked_cubes();
|
||||
my $lower_config = $model->get_material('lower')->config;
|
||||
my $upper_config = $model->get_material('upper')->config;
|
||||
|
||||
$lower_config->set('extruder', 1);
|
||||
$lower_config->set('bottom_solid_layers', 0);
|
||||
|
@ -123,4 +146,44 @@ use Slic3r::Test;
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $model = stacked_cubes();
|
||||
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
my $print = Slic3r::Test::init_print($model, config => $config);
|
||||
|
||||
is $model->get_material('lower')->config->extruder, 1, 'auto_assign_extruders() assigned correct extruder to first volume';
|
||||
is $model->get_material('upper')->config->extruder, 2, 'auto_assign_extruders() assigned correct extruder to second volume';
|
||||
|
||||
my $tool = undef;
|
||||
my %T0 = my %T1 = (); # Z => 1
|
||||
Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == 0) {
|
||||
$T0{$self->Z} = 1;
|
||||
} elsif ($tool == 1) {
|
||||
$T1{$self->Z} = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ok !(defined first { $_ > 20 } keys %T0), 'T0 is never used for upper object';
|
||||
ok !(defined first { $_ < 20 } keys %T1), 'T1 is never used for lower object';
|
||||
}
|
||||
|
||||
sub stacked_cubes {
|
||||
my $model = Slic3r::Model->new;
|
||||
my $object = $model->add_object;
|
||||
$object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube'), material_id => 'lower');
|
||||
$object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube', translate => [0,0,20]), material_id => 'upper');
|
||||
$object->add_instance(offset => Slic3r::Pointf->new(0,0));
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
154
t/perimeters.t
154
t/perimeters.t
File diff suppressed because one or more lines are too long
18
t/print.t
18
t/print.t
|
@ -38,21 +38,21 @@ use Slic3r::Test;
|
|||
my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config);
|
||||
|
||||
# user sets a per-region option
|
||||
$print->objects->[0]->model_object->config->set('fill_density', 100);
|
||||
$print->reload_object(0);
|
||||
$print->print->objects->[0]->model_object->config->set('fill_density', 100);
|
||||
$print->print->reload_object(0);
|
||||
|
||||
# user exports G-code, thus the default config is reapplied
|
||||
$print->apply_config($config);
|
||||
$print->print->apply_config($config);
|
||||
|
||||
is $print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings';
|
||||
is $print->print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings';
|
||||
|
||||
# user assigns object extruders
|
||||
$print->objects->[0]->model_object->config->set('extruder', 3);
|
||||
$print->objects->[0]->model_object->config->set('perimeter_extruder', 2);
|
||||
$print->reload_object(0);
|
||||
$print->print->objects->[0]->model_object->config->set('extruder', 3);
|
||||
$print->print->objects->[0]->model_object->config->set('perimeter_extruder', 2);
|
||||
$print->print->reload_object(0);
|
||||
|
||||
is $print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded';
|
||||
is $print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders';
|
||||
is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded';
|
||||
is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
242
t/retraction.t
242
t/retraction.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 16;
|
||||
use Test::More tests => 17;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -10,123 +10,149 @@ BEGIN {
|
|||
use Slic3r;
|
||||
use Slic3r::Test qw(_eq);
|
||||
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
my $duplicate = 1;
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
my $duplicate = 1;
|
||||
|
||||
my $test = sub {
|
||||
my ($conf) = @_;
|
||||
$conf ||= $config;
|
||||
my $test = sub {
|
||||
my ($conf) = @_;
|
||||
$conf ||= $config;
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $conf, duplicate => $duplicate);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $conf, duplicate => $duplicate);
|
||||
|
||||
my $tool = 0;
|
||||
my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
|
||||
my @retracted = (1); # ignore the first travel move from home to first point
|
||||
my @retracted_length = (0);
|
||||
my $lifted = 0;
|
||||
my $changed_tool = 0;
|
||||
my $wait_for_toolchange = 0;
|
||||
my $tool = 0;
|
||||
my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
|
||||
my @retracted = (1); # ignore the first travel move from home to first point
|
||||
my @retracted_length = (0);
|
||||
my $lifted = 0;
|
||||
my $changed_tool = 0;
|
||||
my $wait_for_toolchange = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
$changed_tool = 1;
|
||||
$wait_for_toolchange = 0;
|
||||
$toolchange_count[$tool] //= 0;
|
||||
$toolchange_count[$tool]++;
|
||||
} elsif ($cmd =~ /^G[01]$/ && !$args->{Z}) { # ignore lift taking place after retraction
|
||||
fail 'toolchange happens right after retraction' if $wait_for_toolchange;
|
||||
}
|
||||
|
||||
if ($info->{dist_Z}) {
|
||||
# lift move or lift + change layer
|
||||
if (_eq($info->{dist_Z}, $print->print->config->get_at('retract_lift', $tool))
|
||||
|| (_eq($info->{dist_Z}, $conf->layer_height + $print->print->config->get_at('retract_lift', $tool)) && $print->print->config->get_at('retract_lift', $tool) > 0)) {
|
||||
fail 'only lifting while retracted' if !$retracted[$tool] && !($conf->g0 && $info->{retracting});
|
||||
fail 'double lift' if $lifted;
|
||||
$lifted = 1;
|
||||
}
|
||||
if ($info->{dist_Z} < 0) {
|
||||
fail 'going down only after lifting' if !$lifted;
|
||||
fail 'going down by the same amount of the lift or by the amount needed to get to next layer'
|
||||
if !_eq($info->{dist_Z}, -$print->print->config->get_at('retract_lift', $tool))
|
||||
&& !_eq($info->{dist_Z}, -$print->print->config->get_at('retract_lift', $tool) + $conf->layer_height);
|
||||
$lifted = 0;
|
||||
}
|
||||
fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60;
|
||||
}
|
||||
if ($info->{retracting}) {
|
||||
$retracted[$tool] = 1;
|
||||
$retracted_length[$tool] += -$info->{dist_E};
|
||||
if (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length', $tool))) {
|
||||
# okay
|
||||
} elsif (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length_toolchange', $tool))) {
|
||||
$wait_for_toolchange = 1;
|
||||
} else {
|
||||
fail 'retracted by the correct amount';
|
||||
}
|
||||
fail 'combining retraction and travel with G0'
|
||||
if $cmd ne 'G0' && $conf->g0 && ($info->{dist_Z} || $info->{dist_XY});
|
||||
}
|
||||
if ($info->{extruding}) {
|
||||
fail 'only extruding while not lifted' if $lifted;
|
||||
if ($retracted[$tool]) {
|
||||
my $expected_amount = $retracted_length[$tool] + $print->print->config->get_at('retract_restart_extra', $tool);
|
||||
if ($changed_tool && $toolchange_count[$tool] > 1) {
|
||||
$expected_amount = $print->print->config->get_at('retract_length_toolchange', $tool) + $print->print->config->get_at('retract_restart_extra_toolchange', $tool);
|
||||
$changed_tool = 0;
|
||||
}
|
||||
fail 'unretracted by the correct amount'
|
||||
if !_eq($info->{dist_E}, $expected_amount);
|
||||
$retracted[$tool] = 0;
|
||||
$retracted_length[$tool] = 0;
|
||||
}
|
||||
}
|
||||
if ($info->{travel} && $info->{dist_XY} >= $print->print->config->get_at('retract_before_travel', $tool)) {
|
||||
fail 'retracted before long travel move' if !$retracted[$tool];
|
||||
}
|
||||
});
|
||||
|
||||
1;
|
||||
};
|
||||
|
||||
$config->set('first_layer_height', $config->layer_height);
|
||||
$config->set('first_layer_speed', '100%');
|
||||
$config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code
|
||||
$config->set('retract_length', [1.5]);
|
||||
$config->set('retract_before_travel', [3]);
|
||||
$config->set('only_retract_when_crossing_perimeters', 0);
|
||||
|
||||
my $retract_tests = sub {
|
||||
my ($descr) = @_;
|
||||
|
||||
ok $test->(), "retraction$descr";
|
||||
|
||||
my $conf = $config->clone;
|
||||
$conf->set('retract_restart_extra', [1]);
|
||||
ok $test->($conf), "restart extra length$descr";
|
||||
|
||||
$conf->set('retract_restart_extra', [-1]);
|
||||
ok $test->($conf), "negative restart extra length$descr";
|
||||
|
||||
$conf->set('retract_lift', [1]);
|
||||
ok $test->($conf), "lift$descr";
|
||||
};
|
||||
|
||||
$retract_tests->('');
|
||||
|
||||
$duplicate = 2;
|
||||
$retract_tests->(' (duplicate)');
|
||||
|
||||
$config->set('g0', 1);
|
||||
$retract_tests->(' (G0 and duplicate)');
|
||||
|
||||
$duplicate = 1;
|
||||
$config->set('g0', 0);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('skirts', 4);
|
||||
$config->set('skirt_height', 3);
|
||||
$retract_tests->(' (dual extruder with multiple skirt layers)');
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('retract_layer_change', [0]);
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $retracted = 0;
|
||||
my $layer_changes_with_retraction = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
$changed_tool = 1;
|
||||
$wait_for_toolchange = 0;
|
||||
$toolchange_count[$tool] //= 0;
|
||||
$toolchange_count[$tool]++;
|
||||
} elsif ($cmd =~ /^G[01]$/ && !$args->{Z}) { # ignore lift taking place after retraction
|
||||
fail 'toolchange happens right after retraction' if $wait_for_toolchange;
|
||||
if ($info->{retracting}) {
|
||||
$retracted = 1;
|
||||
} elsif ($info->{extruding} && $retracted) {
|
||||
$retracted = 0;
|
||||
}
|
||||
|
||||
if ($info->{dist_Z}) {
|
||||
# lift move or lift + change layer
|
||||
if (_eq($info->{dist_Z}, $print->config->get_at('retract_lift', $tool))
|
||||
|| (_eq($info->{dist_Z}, $conf->layer_height + $print->config->get_at('retract_lift', $tool)) && $print->config->get_at('retract_lift', $tool) > 0)) {
|
||||
fail 'only lifting while retracted' if !$retracted[$tool] && !($conf->g0 && $info->{retracting});
|
||||
fail 'double lift' if $lifted;
|
||||
$lifted = 1;
|
||||
}
|
||||
if ($info->{dist_Z} < 0) {
|
||||
fail 'going down only after lifting' if !$lifted;
|
||||
fail 'going down by the same amount of the lift or by the amount needed to get to next layer'
|
||||
if !_eq($info->{dist_Z}, -$print->config->get_at('retract_lift', $tool))
|
||||
&& !_eq($info->{dist_Z}, -$print->config->get_at('retract_lift', $tool) + $conf->layer_height);
|
||||
$lifted = 0;
|
||||
}
|
||||
fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60;
|
||||
}
|
||||
if ($info->{retracting}) {
|
||||
$retracted[$tool] = 1;
|
||||
$retracted_length[$tool] += -$info->{dist_E};
|
||||
if (_eq($retracted_length[$tool], $print->config->get_at('retract_length', $tool))) {
|
||||
# okay
|
||||
} elsif (_eq($retracted_length[$tool], $print->config->get_at('retract_length_toolchange', $tool))) {
|
||||
$wait_for_toolchange = 1;
|
||||
} else {
|
||||
fail 'retracted by the correct amount';
|
||||
}
|
||||
fail 'combining retraction and travel with G0'
|
||||
if $cmd ne 'G0' && $conf->g0 && ($info->{dist_Z} || $info->{dist_XY});
|
||||
}
|
||||
if ($info->{extruding}) {
|
||||
fail 'only extruding while not lifted' if $lifted;
|
||||
if ($retracted[$tool]) {
|
||||
my $expected_amount = $retracted_length[$tool] + $print->config->get_at('retract_restart_extra', $tool);
|
||||
if ($changed_tool && $toolchange_count[$tool] > 1) {
|
||||
$expected_amount = $print->config->get_at('retract_length_toolchange', $tool) + $print->config->get_at('retract_restart_extra_toolchange', $tool);
|
||||
$changed_tool = 0;
|
||||
}
|
||||
fail 'unretracted by the correct amount'
|
||||
if !_eq($info->{dist_E}, $expected_amount);
|
||||
$retracted[$tool] = 0;
|
||||
$retracted_length[$tool] = 0;
|
||||
}
|
||||
}
|
||||
if ($info->{travel} && $info->{dist_XY} >= $print->config->get_at('retract_before_travel', $tool)) {
|
||||
fail 'retracted before long travel move' if !$retracted[$tool];
|
||||
if ($info->{dist_Z} && $retracted) {
|
||||
$layer_changes_with_retraction++;
|
||||
}
|
||||
});
|
||||
|
||||
1;
|
||||
};
|
||||
|
||||
$config->set('first_layer_height', $config->layer_height);
|
||||
$config->set('first_layer_speed', '100%');
|
||||
$config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code
|
||||
$config->set('retract_length', [1.5]);
|
||||
$config->set('retract_before_travel', [3]);
|
||||
$config->set('only_retract_when_crossing_perimeters', 0);
|
||||
|
||||
my $retract_tests = sub {
|
||||
my ($descr) = @_;
|
||||
|
||||
ok $test->(), "retraction$descr";
|
||||
|
||||
my $conf = $config->clone;
|
||||
$conf->set('retract_restart_extra', [1]);
|
||||
ok $test->($conf), "restart extra length$descr";
|
||||
|
||||
$conf->set('retract_restart_extra', [-1]);
|
||||
ok $test->($conf), "negative restart extra length$descr";
|
||||
|
||||
$conf->set('retract_lift', [1]);
|
||||
ok $test->($conf), "lift$descr";
|
||||
};
|
||||
|
||||
$retract_tests->('');
|
||||
|
||||
$duplicate = 2;
|
||||
$retract_tests->(' (duplicate)');
|
||||
|
||||
$config->set('g0', 1);
|
||||
$retract_tests->(' (G0 and duplicate)');
|
||||
|
||||
$duplicate = 1;
|
||||
$config->set('g0', 0);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('skirts', 4);
|
||||
$config->set('skirt_height', 3);
|
||||
$retract_tests->(' (dual extruder with multiple skirt layers)');
|
||||
is $layer_changes_with_retraction, 0, 'no retraction on layer change';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
39
t/shells.t
39
t/shells.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 10;
|
||||
use Test::More tests => 17;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -208,11 +208,16 @@ use Slic3r::Test;
|
|||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('spiral_vase', 1);
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('fill_density', 0);
|
||||
$config->set('top_solid_layers', 0);
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('retract_layer_change', [0]);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('first_layer_height', '100%');
|
||||
$config->set('layer_height', 0.4);
|
||||
$config->set('start_gcode', '');
|
||||
$config->validate;
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $z_moves = 0;
|
||||
|
@ -224,6 +229,7 @@ use Slic3r::Test;
|
|||
my $sum_of_partial_z_equals_to_layer_height = 0;
|
||||
my $all_layer_segments_have_same_slope = 0;
|
||||
my $horizontal_extrusions = 0;
|
||||
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
|
@ -247,11 +253,11 @@ use Slic3r::Test;
|
|||
my $total_dist_XY = sum(map $_->[1], @this_layer);
|
||||
$sum_of_partial_z_equals_to_layer_height = 1
|
||||
if abs(sum(map $_->[0], @this_layer) - $config->layer_height) > epsilon;
|
||||
exit if $sum_of_partial_z_equals_to_layer_height;
|
||||
|
||||
foreach my $segment (@this_layer) {
|
||||
# check that segment's dist_Z is proportioned to its dist_XY
|
||||
$all_layer_segments_have_same_slope = 1
|
||||
if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > epsilon;
|
||||
if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.1;
|
||||
}
|
||||
|
||||
@this_layer = ();
|
||||
|
@ -270,4 +276,31 @@ use Slic3r::Test;
|
|||
ok !$horizontal_extrusions, 'no horizontal extrusions';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('fill_density', 0);
|
||||
$config->set('top_solid_layers', 0);
|
||||
$config->set('spiral_vase', 1);
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('first_layer_height', '100%');
|
||||
$config->set('start_gcode', '');
|
||||
|
||||
my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config);
|
||||
my $diagonal_moves = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1') {
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($info->{dist_Z} > 0) {
|
||||
$diagonal_moves++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
is $diagonal_moves, 0, 'no spiral moves on two-island object';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 2;
|
||||
use Test::More tests => 3;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -16,7 +16,7 @@ use Slic3r::Test;
|
|||
$config->set('skirts', 1);
|
||||
$config->set('skirt_height', 2);
|
||||
$config->set('perimeters', 0);
|
||||
$config->set('perimeter_speed', 99);
|
||||
$config->set('support_material_speed', 99);
|
||||
$config->set('cooling', 0); # to prevent speeds to be altered
|
||||
$config->set('first_layer_speed', '100%'); # to prevent speeds to be altered
|
||||
|
||||
|
@ -33,7 +33,7 @@ use Slic3r::Test;
|
|||
if (defined $self->Z) {
|
||||
$layers_with_skirt{$self->Z} //= 0;
|
||||
$layers_with_skirt{$self->Z} = 1
|
||||
if $info->{extruding} && ($args->{F} // $self->F) == $config->perimeter_speed*60;
|
||||
if $info->{extruding} && ($args->{F} // $self->F) == $config->support_material_speed*60;
|
||||
}
|
||||
});
|
||||
fail "wrong number of layers with skirt"
|
||||
|
@ -50,6 +50,7 @@ use Slic3r::Test;
|
|||
$config->set('top_solid_layers', 0); # to prevent solid shells and their speeds
|
||||
$config->set('bottom_solid_layers', 0); # to prevent solid shells and their speeds
|
||||
$config->set('brim_width', 5);
|
||||
$config->set('support_material_speed', 99);
|
||||
$config->set('cooling', 0); # to prevent speeds to be altered
|
||||
$config->set('first_layer_speed', '100%'); # to prevent speeds to be altered
|
||||
|
||||
|
@ -68,4 +69,13 @@ use Slic3r::Test;
|
|||
is scalar(grep $_, values %layers_with_brim), 1, "brim is generated";
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 1);
|
||||
$config->set('brim_width', 10);
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
ok Slic3r::Test::gcode($print), 'successful G-code generation when skirt is smaller than brim width';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
54
t/support.t
54
t/support.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 15;
|
||||
use Test::More tests => 18;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -20,12 +20,12 @@ use Slic3r::Test;
|
|||
|
||||
my $test = sub {
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
$print->init_extruders;
|
||||
my $flow = $print->objects->[0]->support_material_flow;
|
||||
$print->print->init_extruders;
|
||||
my $flow = $print->print->objects->[0]->support_material_flow;
|
||||
my $support_z = Slic3r::Print::SupportMaterial
|
||||
->new(
|
||||
object_config => $print->objects->[0]->config,
|
||||
print_config => $print->config,
|
||||
object_config => $print->print->objects->[0]->config,
|
||||
print_config => $print->print->config,
|
||||
flow => $flow,
|
||||
interface_flow => $flow,
|
||||
first_layer_flow => $flow,
|
||||
|
@ -133,28 +133,40 @@ use Slic3r::Test;
|
|||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
$config->set('raft_layers', 2);
|
||||
$config->set('layer_height', 0.35);
|
||||
$config->set('first_layer_height', 0.3);
|
||||
$config->set('nozzle_diameter', [0.5]);
|
||||
$config->set('support_material_extruder', 2);
|
||||
$config->set('support_material_interface_extruder', 2);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my %raft_z = (); # z => 1
|
||||
my $tool = undef;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->support_material_extruder-1) {
|
||||
$raft_z{$self->Z} = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated';
|
||||
my $test = sub {
|
||||
my ($raft_layers) = @_;
|
||||
$config->set('raft_layers', $raft_layers);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my %raft_z = (); # z => 1
|
||||
my $tool = undef;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->support_material_extruder-1) {
|
||||
$raft_z{$self->Z} = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated';
|
||||
};
|
||||
|
||||
$test->(2);
|
||||
$test->(70);
|
||||
|
||||
$config->set('layer_height', 0.4);
|
||||
$config->set('first_layer_height', 0.35);
|
||||
$test->(3);
|
||||
$test->(70);
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
16
t/svg.t
16
t/svg.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 1;
|
||||
use Test::More tests => 2;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -14,10 +14,22 @@ use Slic3r::Test;
|
|||
my $print = Slic3r::Test::init_print('20mm_cube');
|
||||
eval {
|
||||
my $fh = IO::Scalar->new(\my $gcode);
|
||||
$print->export_svg(output_fh => $fh, quiet => 1);
|
||||
$print->print->export_svg(output_fh => $fh, quiet => 1);
|
||||
$fh->close;
|
||||
};
|
||||
die $@ if $@;
|
||||
ok !$@, 'successful SVG export';
|
||||
}
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('two_hollow_squares');
|
||||
eval {
|
||||
my $fh = IO::Scalar->new(\my $gcode);
|
||||
$print->print->export_svg(output_fh => $fh, quiet => 1);
|
||||
$fh->close;
|
||||
};
|
||||
die $@ if $@;
|
||||
ok !$@, 'successful SVG export of object with two islands';
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
6
t/thin.t
6
t/thin.t
|
@ -1,4 +1,4 @@
|
|||
use Test::More tests => 11;
|
||||
use Test::More tests => 13;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -62,7 +62,9 @@ if (0) {
|
|||
);
|
||||
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
|
||||
my $res = $expolygon->medial_axis(scale 1, scale 0.5);
|
||||
is scalar(@$res), 1, 'medial axis of a square shape is a single closed loop';
|
||||
is scalar(@$res), 1, 'medial axis of a square shape is a single path';
|
||||
isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline';
|
||||
ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop';
|
||||
ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length,
|
||||
'medial axis loop has reasonable length';
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ $ARGV[0] or usage(1);
|
|||
|
||||
if (-e $ARGV[0]) {
|
||||
my $model = Slic3r::Format::STL->read_file($ARGV[0]);
|
||||
$model->objects->[0]->add_instance(offset => [0,0]);
|
||||
$model->objects->[0]->add_instance(offset => Slic3r::Pointf->new(0,0));
|
||||
my $mesh = $model->mesh;
|
||||
$mesh->repair;
|
||||
printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices};
|
||||
|
|
|
@ -12,7 +12,7 @@ BEGIN {
|
|||
use Getopt::Long qw(:config no_auto_abbrev);
|
||||
use PDF::API2;
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(unscale X Y);
|
||||
use Slic3r::Geometry qw(scale unscale X Y);
|
||||
|
||||
use constant mm => 25.4 / 72;
|
||||
|
||||
|
@ -31,16 +31,21 @@ my %opt = ();
|
|||
# prepare config
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('layer_height', $opt{layer_height}) if $opt{layer_height};
|
||||
$config->set('print_center', [0,0]);
|
||||
|
||||
# read model
|
||||
my $model = Slic3r::Model->read_from_file(my $input_file = $ARGV[0]);
|
||||
|
||||
# init print object
|
||||
my $sprint = Slic3r::Print::Simple->new;
|
||||
$sprint->apply_config($config);
|
||||
$sprint->set_model(Slic3r::Model->read_from_file(my $input_file = $ARGV[0]));
|
||||
$sprint->set_model($model);
|
||||
my $print = $sprint->_print;
|
||||
|
||||
# compute sizes
|
||||
my $bb = $print->bounding_box;
|
||||
my $mediabox = [ map unscale($_)/mm, @{$bb->size} ];
|
||||
my $size = $bb->size;
|
||||
my $mediabox = [ map unscale($_)/mm, @{$size} ];
|
||||
|
||||
# init PDF
|
||||
my $pdf = PDF::API2->new();
|
||||
|
@ -48,20 +53,27 @@ my %opt = ();
|
|||
|
||||
# slice and build output geometry
|
||||
$_->slice for @{$print->objects};
|
||||
foreach my $layer (@{ $print->objects->[0]->layers }) {
|
||||
my $page = $pdf->page();
|
||||
$page->mediabox(@$mediabox);
|
||||
my $content = $page->gfx;
|
||||
$content->fillcolor($color, 1);
|
||||
foreach my $object (@{ $print->objects }) {
|
||||
my $shift = $object->_shifted_copies->[0];
|
||||
$shift->translate(map $_/2, @$size);
|
||||
|
||||
foreach my $expolygon (@{$layer->slices}) {
|
||||
$content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @{$expolygon->contour}); #)
|
||||
$content->close;
|
||||
foreach my $hole (@{$expolygon->holes}) {
|
||||
$content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @$hole); #)
|
||||
foreach my $layer (@{ $object->layers }) {
|
||||
my $page = $pdf->page();
|
||||
$page->mediabox(@$mediabox);
|
||||
my $content = $page->gfx;
|
||||
$content->fillcolor($color, 1);
|
||||
|
||||
foreach my $expolygon (@{$layer->slices}) {
|
||||
$expolygon = $expolygon->clone;
|
||||
$expolygon->translate(@$shift);
|
||||
$content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @{$expolygon->contour}); #)
|
||||
$content->close;
|
||||
foreach my $hole (@{$expolygon->holes}) {
|
||||
$content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @$hole); #)
|
||||
$content->close;
|
||||
}
|
||||
$content->fill; # non-zero by default
|
||||
}
|
||||
$content->fill; # non-zero by default
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
xs/Build.PL
24
xs/Build.PL
|
@ -3,8 +3,27 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use ExtUtils::CppGuess;
|
||||
use Module::Build::WithXSpp;
|
||||
|
||||
# _GLIBCXX_USE_C99 : to get the long long type for g++
|
||||
# HAS_BOOL : stops Perl/lib/CORE/handy.h from doing "# define bool char" for MSVC
|
||||
# NOGDI : prevents inclusion of wingdi.h which defines functions Polygon() and Polyline() in global namespace
|
||||
my @cflags = qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS);
|
||||
if ($ENV{SLIC3R_DEBUG}) {
|
||||
# only on newer GCCs: -ftemplate-backtrace-limit=0
|
||||
push @cflags, qw(-DSLIC3R_DEBUG -g);
|
||||
}
|
||||
if (ExtUtils::CppGuess->new->is_gcc) {
|
||||
# check whether we're dealing with a buggy GCC version
|
||||
# see https://github.com/alexrj/Slic3r/issues/1965
|
||||
if (`cc --version` =~ / 4\.7\.[012]/) {
|
||||
# Workaround suggested by Boost devs:
|
||||
# https://svn.boost.org/trac/boost/ticket/8695
|
||||
push @cflags, qw(-fno-inline-small-functions);
|
||||
}
|
||||
}
|
||||
|
||||
my $build = Module::Build::WithXSpp->new(
|
||||
module_name => 'Slic3r::XS',
|
||||
dist_abstract => 'XS code for Slic3r',
|
||||
|
@ -21,10 +40,7 @@ my $build = Module::Build::WithXSpp->new(
|
|||
Module::Build 0.38
|
||||
Module::Build::WithXSpp 0.13
|
||||
)},
|
||||
# _GLIBCXX_USE_C99 : to get the long long type for g++
|
||||
# HAS_BOOL : stops Perl/lib/CORE/handy.h from doing "# define bool char" for MSVC
|
||||
# NOGDI : prevents inclusion of wingdi.h which defines functions Polygon() and Polyline() in global namespace
|
||||
extra_compiler_flags => [qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS), ($ENV{SLIC3R_DEBUG} ? ' -DSLIC3R_DEBUG -g' : '')],
|
||||
extra_compiler_flags => \@cflags,
|
||||
|
||||
# Provides extra C typemaps that are auto-merged
|
||||
extra_typemap_modules => {
|
||||
|
|
34
xs/MANIFEST
34
xs/MANIFEST
|
@ -1660,34 +1660,56 @@ src/ExPolygon.cpp
|
|||
src/ExPolygon.hpp
|
||||
src/ExPolygonCollection.cpp
|
||||
src/ExPolygonCollection.hpp
|
||||
src/Extruder.cpp
|
||||
src/Extruder.hpp
|
||||
src/ExtrusionEntity.cpp
|
||||
src/ExtrusionEntity.hpp
|
||||
src/ExtrusionEntityCollection.cpp
|
||||
src/ExtrusionEntityCollection.hpp
|
||||
src/Flow.cpp
|
||||
src/Flow.hpp
|
||||
src/GCode.hpp
|
||||
src/Geometry.cpp
|
||||
src/Geometry.hpp
|
||||
src/Layer.cpp
|
||||
src/Layer.hpp
|
||||
src/Line.cpp
|
||||
src/Line.hpp
|
||||
src/Model.cpp
|
||||
src/Model.hpp
|
||||
src/MultiPoint.cpp
|
||||
src/MultiPoint.hpp
|
||||
src/myinit.h
|
||||
src/perlglue.hpp
|
||||
src/PlaceholderParser.cpp
|
||||
src/PlaceholderParser.hpp
|
||||
src/Point.cpp
|
||||
src/Point.hpp
|
||||
src/poly2tri/common/shapes.cc
|
||||
src/poly2tri/common/shapes.h
|
||||
src/poly2tri/common/utils.h
|
||||
src/poly2tri/poly2tri.h
|
||||
src/poly2tri/sweep/advancing_front.cc
|
||||
src/poly2tri/sweep/advancing_front.h
|
||||
src/poly2tri/sweep/cdt.cc
|
||||
src/poly2tri/sweep/cdt.h
|
||||
src/poly2tri/sweep/sweep.cc
|
||||
src/poly2tri/sweep/sweep.h
|
||||
src/poly2tri/sweep/sweep_context.cc
|
||||
src/poly2tri/sweep/sweep_context.h
|
||||
src/Polygon.cpp
|
||||
src/Polygon.hpp
|
||||
src/Polyline.cpp
|
||||
src/Polyline.hpp
|
||||
src/PolylineCollection.cpp
|
||||
src/PolylineCollection.hpp
|
||||
src/PrintConfig.cpp
|
||||
src/PrintConfig.hpp
|
||||
src/polypartition.cpp
|
||||
src/polypartition.h
|
||||
src/ppport.h
|
||||
src/Print.cpp
|
||||
src/Print.hpp
|
||||
src/ppport.h
|
||||
src/PrintConfig.cpp
|
||||
src/PrintConfig.hpp
|
||||
src/Surface.cpp
|
||||
src/Surface.hpp
|
||||
src/SurfaceCollection.cpp
|
||||
|
@ -1713,19 +1735,25 @@ t/14_geometry.t
|
|||
t/15_config.t
|
||||
t/16_flow.t
|
||||
t/17_boundingbox.t
|
||||
t/19_model.t
|
||||
t/20_print.t
|
||||
xsp/BoundingBox.xsp
|
||||
xsp/Clipper.xsp
|
||||
xsp/Config.xsp
|
||||
xsp/ExPolygon.xsp
|
||||
xsp/ExPolygonCollection.xsp
|
||||
xsp/Extruder.xsp
|
||||
xsp/ExtrusionEntityCollection.xsp
|
||||
xsp/ExtrusionLoop.xsp
|
||||
xsp/ExtrusionPath.xsp
|
||||
xsp/Flow.xsp
|
||||
xsp/Geometry.xsp
|
||||
xsp/Layer.xsp
|
||||
xsp/Line.xsp
|
||||
xsp/Model.xsp
|
||||
xsp/my.map
|
||||
xsp/mytype.map
|
||||
xsp/PlaceholderParser.xsp
|
||||
xsp/Point.xsp
|
||||
xsp/Polygon.xsp
|
||||
xsp/Polyline.xsp
|
||||
|
|
|
@ -13,20 +13,20 @@ use overload
|
|||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
package Slic3r::Line::Ref;
|
||||
our @ISA = 'Slic3r::Line';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::Point;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
package Slic3r::Point::Ref;
|
||||
our @ISA = 'Slic3r::Point';
|
||||
package Slic3r::Point3;
|
||||
use overload
|
||||
'@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #,
|
||||
'fallback' => 1;
|
||||
|
||||
sub DESTROY {}
|
||||
package Slic3r::Pointf;
|
||||
use overload
|
||||
'@{}' => sub { [ $_[0]->x, $_[0]->y ] }, #,
|
||||
'fallback' => 1;
|
||||
|
||||
package Slic3r::Pointf3;
|
||||
use overload
|
||||
|
@ -38,21 +38,11 @@ use overload
|
|||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
package Slic3r::ExPolygon::Ref;
|
||||
our @ISA = 'Slic3r::ExPolygon';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::Polyline;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
package Slic3r::Polyline::Ref;
|
||||
our @ISA = 'Slic3r::Polyline';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::Polyline::Collection;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
|
@ -63,11 +53,6 @@ use overload
|
|||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
package Slic3r::Polygon::Ref;
|
||||
our @ISA = 'Slic3r::Polygon';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::ExPolygon::Collection;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
|
@ -86,41 +71,19 @@ sub new {
|
|||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::ExtrusionPath::Collection::Ref;
|
||||
our @ISA = 'Slic3r::ExtrusionPath::Collection';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::ExtrusionLoop;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
sub new {
|
||||
my ($class, %args) = @_;
|
||||
sub new_from_paths {
|
||||
my ($class, @paths) = @_;
|
||||
|
||||
return $class->_new(
|
||||
$args{polygon}, # required
|
||||
$args{role}, # required
|
||||
$args{mm3_per_mm} // -1,
|
||||
);
|
||||
my $loop = $class->new;
|
||||
$loop->append($_) for @paths;
|
||||
return $loop;
|
||||
}
|
||||
|
||||
sub clone {
|
||||
my ($self, %args) = @_;
|
||||
|
||||
return __PACKAGE__->_new(
|
||||
$args{polygon} // $self->polygon,
|
||||
$args{role} // $self->role,
|
||||
$args{mm3_per_mm} // $self->mm3_per_mm,
|
||||
);
|
||||
}
|
||||
|
||||
package Slic3r::ExtrusionLoop::Ref;
|
||||
our @ISA = 'Slic3r::ExtrusionLoop';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::ExtrusionPath;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
|
@ -132,7 +95,9 @@ sub new {
|
|||
return $class->_new(
|
||||
$args{polyline}, # required
|
||||
$args{role}, # required
|
||||
$args{mm3_per_mm} // -1,
|
||||
$args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionPath constructor"),
|
||||
$args{width} // -1,
|
||||
$args{height} // -1,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -143,14 +108,11 @@ sub clone {
|
|||
$args{polyline} // $self->polyline,
|
||||
$args{role} // $self->role,
|
||||
$args{mm3_per_mm} // $self->mm3_per_mm,
|
||||
$args{width} // $self->width,
|
||||
$args{height} // $self->height,
|
||||
);
|
||||
}
|
||||
|
||||
package Slic3r::ExtrusionPath::Ref;
|
||||
our @ISA = 'Slic3r::ExtrusionPath';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::Flow;
|
||||
|
||||
sub new {
|
||||
|
@ -211,14 +173,57 @@ sub clone {
|
|||
);
|
||||
}
|
||||
|
||||
package Slic3r::Surface::Ref;
|
||||
our @ISA = 'Slic3r::Surface';
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
package Slic3r::Surface::Collection;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
package main;
|
||||
for my $class (qw(
|
||||
Slic3r::Config
|
||||
Slic3r::Config::Full
|
||||
Slic3r::Config::Print
|
||||
Slic3r::Config::PrintObject
|
||||
Slic3r::Config::PrintRegion
|
||||
Slic3r::ExPolygon
|
||||
Slic3r::ExPolygon::Collection
|
||||
Slic3r::Extruder
|
||||
Slic3r::ExtrusionLoop
|
||||
Slic3r::ExtrusionPath
|
||||
Slic3r::ExtrusionPath::Collection
|
||||
Slic3r::Flow
|
||||
Slic3r::GCode::PlaceholderParser
|
||||
Slic3r::Geometry::BoundingBox
|
||||
Slic3r::Geometry::BoundingBoxf
|
||||
Slic3r::Geometry::BoundingBoxf3
|
||||
Slic3r::Layer
|
||||
Slic3r::Layer::Region
|
||||
Slic3r::Layer::Support
|
||||
Slic3r::Line
|
||||
Slic3r::Model
|
||||
Slic3r::Model::Instance
|
||||
Slic3r::Model::Material
|
||||
Slic3r::Model::Object
|
||||
Slic3r::Model::Volume
|
||||
Slic3r::Point
|
||||
Slic3r::Point3
|
||||
Slic3r::Pointf
|
||||
Slic3r::Pointf3
|
||||
Slic3r::Polygon
|
||||
Slic3r::Polyline
|
||||
Slic3r::Polyline::Collection
|
||||
Slic3r::Print
|
||||
Slic3r::Print::Object
|
||||
Slic3r::Print::Region
|
||||
Slic3r::Print::State
|
||||
Slic3r::Surface
|
||||
Slic3r::Surface::Collection
|
||||
Slic3r::TriangleMesh
|
||||
))
|
||||
{
|
||||
no strict 'refs';
|
||||
my $ref_class = $class . "::Ref";
|
||||
eval "package $ref_class; our \@ISA = '$class'; sub DESTROY {};";
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Slic3r {
|
|||
template <class PointClass>
|
||||
BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
{
|
||||
if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min.x = this->max.x = it->x;
|
||||
this->min.y = this->max.y = it->y;
|
||||
|
@ -22,6 +23,7 @@ template <class PointClass>
|
|||
BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points)
|
||||
: BoundingBoxBase<PointClass>(points)
|
||||
{
|
||||
if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min.z = this->max.z = it->z;
|
||||
for (++it; it != points.end(); ++it) {
|
||||
|
@ -154,4 +156,10 @@ BoundingBox3Base<PointClass>::center() const
|
|||
}
|
||||
template Pointf3 BoundingBox3Base<Pointf3>::center() const;
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox");
|
||||
REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf");
|
||||
REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -59,14 +59,12 @@ ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolyg
|
|||
|
||||
// perform union
|
||||
clipper.AddPaths(input, ClipperLib::ptSubject, true);
|
||||
ClipperLib::PolyTree* polytree = new ClipperLib::PolyTree();
|
||||
clipper.Execute(ClipperLib::ctUnion, *polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
|
||||
ClipperLib::PolyTree polytree;
|
||||
clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
|
||||
|
||||
// write to ExPolygons object
|
||||
output.clear();
|
||||
PolyTreeToExPolygons(*polytree, output);
|
||||
|
||||
delete polytree;
|
||||
PolyTreeToExPolygons(polytree, output);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -168,12 +166,11 @@ offset(const Slic3r::Polylines &polylines, Slic3r::Polygons &retval, const float
|
|||
double scale, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
// perform offset
|
||||
ClipperLib::Paths* output = new ClipperLib::Paths();
|
||||
offset(polylines, *output, delta, scale, joinType, miterLimit);
|
||||
ClipperLib::Paths output;
|
||||
offset(polylines, output, delta, scale, joinType, miterLimit);
|
||||
|
||||
// convert into ExPolygons
|
||||
ClipperPaths_to_Slic3rMultiPoints(*output, retval);
|
||||
delete output;
|
||||
ClipperPaths_to_Slic3rMultiPoints(output, retval);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -199,12 +196,11 @@ offset_ex(const Slic3r::Polygons &polygons, Slic3r::ExPolygons &retval, const fl
|
|||
double scale, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
// perform offset
|
||||
ClipperLib::Paths* output = new ClipperLib::Paths();
|
||||
offset(polygons, *output, delta, scale, joinType, miterLimit);
|
||||
ClipperLib::Paths output;
|
||||
offset(polygons, output, delta, scale, joinType, miterLimit);
|
||||
|
||||
// convert into ExPolygons
|
||||
ClipperPaths_to_Slic3rExPolygons(*output, retval);
|
||||
delete output;
|
||||
ClipperPaths_to_Slic3rExPolygons(output, retval);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -245,12 +241,11 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons &retval, const float
|
|||
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
|
||||
{
|
||||
// perform offset
|
||||
ClipperLib::Paths* output = new ClipperLib::Paths();
|
||||
offset2(polygons, *output, delta1, delta2, scale, joinType, miterLimit);
|
||||
ClipperLib::Paths output;
|
||||
offset2(polygons, output, delta1, delta2, scale, joinType, miterLimit);
|
||||
|
||||
// convert into ExPolygons
|
||||
ClipperPaths_to_Slic3rMultiPoints(*output, retval);
|
||||
delete output;
|
||||
ClipperPaths_to_Slic3rMultiPoints(output, retval);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -258,12 +253,11 @@ offset2_ex(const Slic3r::Polygons &polygons, Slic3r::ExPolygons &retval, const f
|
|||
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
|
||||
{
|
||||
// perform offset
|
||||
ClipperLib::Paths* output = new ClipperLib::Paths();
|
||||
offset2(polygons, *output, delta1, delta2, scale, joinType, miterLimit);
|
||||
ClipperLib::Paths output;
|
||||
offset2(polygons, output, delta1, delta2, scale, joinType, miterLimit);
|
||||
|
||||
// convert into ExPolygons
|
||||
ClipperPaths_to_Slic3rExPolygons(*output, retval);
|
||||
delete output;
|
||||
ClipperPaths_to_Slic3rExPolygons(output, retval);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -271,17 +265,16 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &su
|
|||
const Slic3r::Polygons &clip, T &retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
|
||||
{
|
||||
// read input
|
||||
ClipperLib::Paths* input_subject = new ClipperLib::Paths();
|
||||
ClipperLib::Paths* input_clip = new ClipperLib::Paths();
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, *input_subject);
|
||||
Slic3rMultiPoints_to_ClipperPaths(clip, *input_clip);
|
||||
ClipperLib::Paths input_subject, input_clip;
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, input_subject);
|
||||
Slic3rMultiPoints_to_ClipperPaths(clip, input_clip);
|
||||
|
||||
// perform safety offset
|
||||
if (safety_offset_) {
|
||||
if (clipType == ClipperLib::ctUnion) {
|
||||
safety_offset(input_subject);
|
||||
safety_offset(&input_subject);
|
||||
} else {
|
||||
safety_offset(input_clip);
|
||||
safety_offset(&input_clip);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,33 +283,32 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &su
|
|||
clipper.Clear();
|
||||
|
||||
// add polygons
|
||||
clipper.AddPaths(*input_subject, ClipperLib::ptSubject, true);
|
||||
delete input_subject;
|
||||
clipper.AddPaths(*input_clip, ClipperLib::ptClip, true);
|
||||
delete input_clip;
|
||||
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
|
||||
|
||||
// perform operation
|
||||
clipper.Execute(clipType, retval, fillType, fillType);
|
||||
}
|
||||
|
||||
void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polylines &subject,
|
||||
const Slic3r::Polygons &clip, ClipperLib::PolyTree &retval, const ClipperLib::PolyFillType fillType)
|
||||
const Slic3r::Polygons &clip, ClipperLib::PolyTree &retval, const ClipperLib::PolyFillType fillType,
|
||||
const bool safety_offset_)
|
||||
{
|
||||
// read input
|
||||
ClipperLib::Paths* input_subject = new ClipperLib::Paths();
|
||||
ClipperLib::Paths* input_clip = new ClipperLib::Paths();
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, *input_subject);
|
||||
Slic3rMultiPoints_to_ClipperPaths(clip, *input_clip);
|
||||
ClipperLib::Paths input_subject, input_clip;
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, input_subject);
|
||||
Slic3rMultiPoints_to_ClipperPaths(clip, input_clip);
|
||||
|
||||
// perform safety offset
|
||||
if (safety_offset_) safety_offset(&input_clip);
|
||||
|
||||
// init Clipper
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.Clear();
|
||||
|
||||
// add polygons
|
||||
clipper.AddPaths(*input_subject, ClipperLib::ptSubject, false);
|
||||
delete input_subject;
|
||||
clipper.AddPaths(*input_clip, ClipperLib::ptClip, true);
|
||||
delete input_clip;
|
||||
clipper.AddPaths(input_subject, ClipperLib::ptSubject, false);
|
||||
clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
|
||||
|
||||
// perform operation
|
||||
clipper.Execute(clipType, retval, fillType, fillType);
|
||||
|
@ -326,64 +318,104 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
|||
const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_)
|
||||
{
|
||||
// perform operation
|
||||
ClipperLib::Paths* output = new ClipperLib::Paths();
|
||||
_clipper_do<ClipperLib::Paths>(clipType, subject, clip, *output, ClipperLib::pftNonZero, safety_offset_);
|
||||
ClipperLib::Paths output;
|
||||
_clipper_do<ClipperLib::Paths>(clipType, subject, clip, output, ClipperLib::pftNonZero, safety_offset_);
|
||||
|
||||
// convert into Polygons
|
||||
ClipperPaths_to_Slic3rMultiPoints(*output, retval);
|
||||
delete output;
|
||||
ClipperPaths_to_Slic3rMultiPoints(output, retval);
|
||||
}
|
||||
|
||||
void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
||||
const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_)
|
||||
{
|
||||
// perform operation
|
||||
ClipperLib::PolyTree* polytree = new ClipperLib::PolyTree();
|
||||
_clipper_do<ClipperLib::PolyTree>(clipType, subject, clip, *polytree, ClipperLib::pftNonZero, safety_offset_);
|
||||
ClipperLib::PolyTree polytree;
|
||||
_clipper_do<ClipperLib::PolyTree>(clipType, subject, clip, polytree, ClipperLib::pftNonZero, safety_offset_);
|
||||
|
||||
// convert into ExPolygons
|
||||
PolyTreeToExPolygons(*polytree, retval);
|
||||
delete polytree;
|
||||
PolyTreeToExPolygons(polytree, retval);
|
||||
}
|
||||
|
||||
void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject,
|
||||
const Slic3r::Polygons &clip, Slic3r::Polylines &retval)
|
||||
const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_)
|
||||
{
|
||||
// perform operation
|
||||
ClipperLib::PolyTree polytree;
|
||||
_clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero);
|
||||
_clipper_do(clipType, subject, clip, polytree, ClipperLib::pftNonZero, safety_offset_);
|
||||
|
||||
// convert into Polygons
|
||||
// convert into Polylines
|
||||
ClipperLib::Paths output;
|
||||
ClipperLib::PolyTreeToPaths(polytree, output);
|
||||
ClipperPaths_to_Slic3rMultiPoints(output, retval);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_)
|
||||
void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
||||
const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_)
|
||||
{
|
||||
// transform input polygons into polylines
|
||||
Slic3r::Polylines polylines;
|
||||
polylines.reserve(subject.size());
|
||||
for (Slic3r::Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon)
|
||||
polylines.push_back(*polygon); // implicit call to split_at_first_point()
|
||||
|
||||
// perform clipping
|
||||
_clipper(clipType, polylines, clip, retval, safety_offset_);
|
||||
|
||||
/* If the split_at_first_point() call above happens to split the polygon inside the clipping area
|
||||
we would get two consecutive polylines instead of a single one, so we go through them in order
|
||||
to recombine continuous polylines. */
|
||||
for (size_t i = 0; i < retval.size(); ++i) {
|
||||
for (size_t j = i+1; j < retval.size(); ++j) {
|
||||
if (retval[i].points.back().coincides_with(retval[j].points.front())) {
|
||||
/* If last point of i coincides with first point of j,
|
||||
append points of j to i and delete j */
|
||||
retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end());
|
||||
retval.erase(retval.begin() + j);
|
||||
--j;
|
||||
} else if (retval[i].points.front().coincides_with(retval[j].points.back())) {
|
||||
/* If first point of i coincides with last point of j,
|
||||
prepend points of j to i and delete j */
|
||||
retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1);
|
||||
retval.erase(retval.begin() + j);
|
||||
--j;
|
||||
} else if (retval[i].points.front().coincides_with(retval[j].points.front())) {
|
||||
/* Since Clipper does not preserve orientation of polylines,
|
||||
also check the case when first point of i coincides with first point of j. */
|
||||
retval[j].reverse();
|
||||
retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1);
|
||||
retval.erase(retval.begin() + j);
|
||||
--j;
|
||||
} else if (retval[i].points.back().coincides_with(retval[j].points.back())) {
|
||||
/* Since Clipper does not preserve orientation of polylines,
|
||||
also check the case when last point of i coincides with last point of j. */
|
||||
retval[j].reverse();
|
||||
retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end());
|
||||
retval.erase(retval.begin() + j);
|
||||
--j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class SubjectType, class ResultType>
|
||||
void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_)
|
||||
{
|
||||
_clipper(ClipperLib::ctDifference, subject, clip, retval, safety_offset_);
|
||||
}
|
||||
template void diff<Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_);
|
||||
template void diff<Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_);
|
||||
template void diff<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_);
|
||||
template void diff<Slic3r::Polygons, Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_);
|
||||
template void diff<Slic3r::Polygons, Slic3r::Polylines>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_);
|
||||
template void diff<Slic3r::Polylines, Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_);
|
||||
|
||||
void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval)
|
||||
{
|
||||
_clipper(ClipperLib::ctDifference, subject, clip, retval);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_)
|
||||
template <class SubjectType, class ResultType>
|
||||
void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_)
|
||||
{
|
||||
_clipper(ClipperLib::ctIntersection, subject, clip, retval, safety_offset_);
|
||||
}
|
||||
template void intersection<Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_);
|
||||
template void intersection<Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_);
|
||||
|
||||
void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval)
|
||||
{
|
||||
_clipper(ClipperLib::ctIntersection, subject, clip, retval);
|
||||
}
|
||||
template void intersection<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_);
|
||||
template void intersection<Slic3r::Polygons, Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_);
|
||||
template void intersection<Slic3r::Polygons, Slic3r::Polylines>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_);
|
||||
template void intersection<Slic3r::Polylines, Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_);
|
||||
|
||||
void xor_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval,
|
||||
bool safety_offset_)
|
||||
|
@ -442,39 +474,64 @@ static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons &retval)
|
|||
}
|
||||
}
|
||||
|
||||
void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons &retval)
|
||||
void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons &retval, bool preserve_collinear)
|
||||
{
|
||||
// convert into Clipper polygons
|
||||
ClipperLib::Paths* input_subject = new ClipperLib::Paths();
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, *input_subject);
|
||||
ClipperLib::Paths input_subject, output;
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, input_subject);
|
||||
|
||||
ClipperLib::Paths* output = new ClipperLib::Paths();
|
||||
ClipperLib::SimplifyPolygons(*input_subject, *output, ClipperLib::pftNonZero);
|
||||
delete input_subject;
|
||||
if (preserve_collinear) {
|
||||
ClipperLib::Clipper c;
|
||||
c.PreserveCollinear(true);
|
||||
c.StrictlySimple(true);
|
||||
c.AddPaths(input_subject, ClipperLib::ptSubject, true);
|
||||
c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
} else {
|
||||
ClipperLib::SimplifyPolygons(input_subject, output, ClipperLib::pftNonZero);
|
||||
}
|
||||
|
||||
// convert into Slic3r polygons
|
||||
ClipperPaths_to_Slic3rMultiPoints(*output, retval);
|
||||
delete output;
|
||||
ClipperPaths_to_Slic3rMultiPoints(output, retval);
|
||||
}
|
||||
|
||||
void safety_offset(ClipperLib::Paths* &subject)
|
||||
void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons &retval, bool preserve_collinear)
|
||||
{
|
||||
if (!preserve_collinear) {
|
||||
Polygons polygons;
|
||||
simplify_polygons(subject, polygons, preserve_collinear);
|
||||
union_(polygons, retval);
|
||||
return;
|
||||
}
|
||||
|
||||
// convert into Clipper polygons
|
||||
ClipperLib::Paths input_subject;
|
||||
Slic3rMultiPoints_to_ClipperPaths(subject, input_subject);
|
||||
|
||||
ClipperLib::PolyTree polytree;
|
||||
|
||||
ClipperLib::Clipper c;
|
||||
c.PreserveCollinear(true);
|
||||
c.StrictlySimple(true);
|
||||
c.AddPaths(input_subject, ClipperLib::ptSubject, true);
|
||||
c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
|
||||
// convert into ExPolygons
|
||||
PolyTreeToExPolygons(polytree, retval);
|
||||
}
|
||||
|
||||
void safety_offset(ClipperLib::Paths* paths)
|
||||
{
|
||||
// scale input
|
||||
scaleClipperPolygons(*subject, CLIPPER_OFFSET_SCALE);
|
||||
scaleClipperPolygons(*paths, CLIPPER_OFFSET_SCALE);
|
||||
|
||||
// perform offset (delta = scale 1e-05)
|
||||
ClipperLib::Paths* retval = new ClipperLib::Paths();
|
||||
ClipperLib::ClipperOffset co;
|
||||
co.MiterLimit = 2;
|
||||
co.AddPaths(*subject, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
|
||||
co.Execute(*retval, 10.0 * CLIPPER_OFFSET_SCALE);
|
||||
co.AddPaths(*paths, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
|
||||
co.Execute(*paths, 10.0 * CLIPPER_OFFSET_SCALE);
|
||||
|
||||
// unscale output
|
||||
scaleClipperPolygons(*retval, 1.0/CLIPPER_OFFSET_SCALE);
|
||||
|
||||
// delete original data and switch pointer
|
||||
delete subject;
|
||||
subject = retval;
|
||||
scaleClipperPolygons(*paths, 1.0/CLIPPER_OFFSET_SCALE);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
@ -499,9 +556,9 @@ polynode2perl(const ClipperLib::PolyNode& node)
|
|||
Slic3r::Polygon p;
|
||||
ClipperPath_to_Slic3rMultiPoint(node.Contour, p);
|
||||
if (node.IsHole()) {
|
||||
(void)hv_stores( hv, "hole", p.to_SV_clone_ref() );
|
||||
(void)hv_stores( hv, "hole", Slic3r::perl_to_SV_clone_ref(p) );
|
||||
} else {
|
||||
(void)hv_stores( hv, "outer", p.to_SV_clone_ref() );
|
||||
(void)hv_stores( hv, "outer", Slic3r::perl_to_SV_clone_ref(p) );
|
||||
}
|
||||
(void)hv_stores( hv, "children", polynode_children_2_perl(node) );
|
||||
return (SV*)newRV_noinc((SV*)hv);
|
||||
|
|
|
@ -70,7 +70,7 @@ template <class T>
|
|||
void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
||||
const Slic3r::Polygons &clip, T &retval, bool safety_offset_);
|
||||
void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject,
|
||||
const Slic3r::Polygons &clip, ClipperLib::Paths &retval);
|
||||
const Slic3r::Polygons &clip, ClipperLib::Paths &retval, bool safety_offset_);
|
||||
void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
||||
const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_);
|
||||
void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
||||
|
@ -78,15 +78,11 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject,
|
|||
void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject,
|
||||
const Slic3r::Polygons &clip, Slic3r::Polylines &retval);
|
||||
|
||||
template <class T>
|
||||
void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_ = false);
|
||||
template <class SubjectType, class ResultType>
|
||||
void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_ = false);
|
||||
|
||||
void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval);
|
||||
|
||||
template <class T>
|
||||
void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_ = false);
|
||||
|
||||
void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval);
|
||||
template <class SubjectType, class ResultType>
|
||||
void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_ = false);
|
||||
|
||||
void xor_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval,
|
||||
bool safety_offset_ = false);
|
||||
|
@ -98,9 +94,10 @@ void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree &retval, boo
|
|||
void union_pt_chained(const Slic3r::Polygons &subject, Slic3r::Polygons &retval, bool safety_offset_ = false);
|
||||
static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons &retval);
|
||||
|
||||
void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons &retval);
|
||||
void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons &retval, bool preserve_collinear = false);
|
||||
void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons &retval, bool preserve_collinear = false);
|
||||
|
||||
void safety_offset(ClipperLib::Paths* &subject);
|
||||
void safety_offset(ClipperLib::Paths* paths);
|
||||
|
||||
/////////////////
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) {
|
|||
const size_t len = av_len(av)+1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
SV** elem = av_fetch(av, i, 0);
|
||||
if (!looks_like_number(*elem)) return false;
|
||||
if (elem == NULL || !looks_like_number(*elem)) return false;
|
||||
values.push_back(SvNV(*elem));
|
||||
}
|
||||
optv->values = values;
|
||||
|
@ -193,7 +193,7 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) {
|
|||
const size_t len = av_len(av)+1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
SV** elem = av_fetch(av, i, 0);
|
||||
if (!looks_like_number(*elem)) return false;
|
||||
if (elem == NULL || !looks_like_number(*elem)) return false;
|
||||
values.push_back(SvIV(*elem));
|
||||
}
|
||||
optv->values = values;
|
||||
|
@ -205,6 +205,7 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) {
|
|||
const size_t len = av_len(av)+1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
SV** elem = av_fetch(av, i, 0);
|
||||
if (elem == NULL) return false;
|
||||
optv->values.push_back(std::string(SvPV_nolen(*elem), SvCUR(*elem)));
|
||||
}
|
||||
} else if (ConfigOptionPoint* optv = dynamic_cast<ConfigOptionPoint*>(opt)) {
|
||||
|
@ -216,7 +217,7 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) {
|
|||
for (size_t i = 0; i < len; i++) {
|
||||
SV** elem = av_fetch(av, i, 0);
|
||||
Pointf point;
|
||||
if (!point.from_SV(*elem)) return false;
|
||||
if (elem == NULL || !point.from_SV(*elem)) return false;
|
||||
values.push_back(point);
|
||||
}
|
||||
optv->values = values;
|
||||
|
@ -228,6 +229,7 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) {
|
|||
const size_t len = av_len(av)+1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
SV** elem = av_fetch(av, i, 0);
|
||||
if (elem == NULL) return false;
|
||||
optv->values.push_back(SvTRUE(*elem));
|
||||
}
|
||||
} else {
|
||||
|
@ -235,8 +237,31 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* This method is implemented as a workaround for this typemap bug:
|
||||
https://rt.cpan.org/Public/Bug/Display.html?id=94110 */
|
||||
bool
|
||||
ConfigBase::set_deserialize(const t_config_option_key opt_key, SV* str) {
|
||||
size_t len;
|
||||
const char * c = SvPV(str, len);
|
||||
std::string value(c, len);
|
||||
|
||||
return this->set_deserialize(opt_key, value);
|
||||
}
|
||||
#endif
|
||||
|
||||
DynamicConfig& DynamicConfig::operator= (DynamicConfig other)
|
||||
{
|
||||
this->swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
DynamicConfig::swap(DynamicConfig &other)
|
||||
{
|
||||
std::swap(this->options, other.options);
|
||||
}
|
||||
|
||||
DynamicConfig::~DynamicConfig () {
|
||||
for (t_options_map::iterator it = this->options.begin(); it != this->options.end(); ++it) {
|
||||
ConfigOption* opt = it->second;
|
||||
|
@ -295,6 +320,15 @@ DynamicConfig::option(const t_config_option_key opt_key, bool create) {
|
|||
return this->options[opt_key];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T*
|
||||
DynamicConfig::opt(const t_config_option_key opt_key, bool create) {
|
||||
return dynamic_cast<T*>(this->option(opt_key, create));
|
||||
}
|
||||
template ConfigOptionInt* DynamicConfig::opt<ConfigOptionInt>(const t_config_option_key opt_key, bool create);
|
||||
template ConfigOptionBool* DynamicConfig::opt<ConfigOptionBool>(const t_config_option_key opt_key, bool create);
|
||||
template ConfigOptionBools* DynamicConfig::opt<ConfigOptionBools>(const t_config_option_key opt_key, bool create);
|
||||
|
||||
const ConfigOption*
|
||||
DynamicConfig::option(const t_config_option_key opt_key) const {
|
||||
return const_cast<DynamicConfig*>(this)->option(opt_key, false);
|
||||
|
|
|
@ -33,7 +33,7 @@ class ConfigOptionVector
|
|||
virtual ~ConfigOptionVector() {};
|
||||
std::vector<T> values;
|
||||
|
||||
T get_at(size_t i) {
|
||||
T get_at(size_t i) const {
|
||||
try {
|
||||
return this->values.at(i);
|
||||
} catch (const std::out_of_range& oor) {
|
||||
|
@ -479,6 +479,7 @@ class ConfigBase
|
|||
SV* get(t_config_option_key opt_key);
|
||||
SV* get_at(t_config_option_key opt_key, size_t i);
|
||||
bool set(t_config_option_key opt_key, SV* value);
|
||||
bool set_deserialize(const t_config_option_key opt_key, SV* str);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -487,14 +488,16 @@ class DynamicConfig : public ConfigBase
|
|||
public:
|
||||
DynamicConfig() {};
|
||||
DynamicConfig(const DynamicConfig& other);
|
||||
DynamicConfig& operator= (DynamicConfig other);
|
||||
void swap(DynamicConfig &other);
|
||||
~DynamicConfig();
|
||||
template<class T> T* opt(const t_config_option_key opt_key, bool create = false);
|
||||
ConfigOption* option(const t_config_option_key opt_key, bool create = false);
|
||||
const ConfigOption* option(const t_config_option_key opt_key) const;
|
||||
void keys(t_config_option_keys *keys) const;
|
||||
void erase(const t_config_option_key opt_key);
|
||||
|
||||
private:
|
||||
DynamicConfig& operator= (const DynamicConfig& other); // we disable this by making it private and unimplemented
|
||||
typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
|
||||
t_options_map options;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "polypartition.h"
|
||||
#include "poly2tri/poly2tri.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -47,7 +53,7 @@ ExPolygon::translate(double x, double y)
|
|||
}
|
||||
|
||||
void
|
||||
ExPolygon::rotate(double angle, Point* center)
|
||||
ExPolygon::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
contour.rotate(angle, center);
|
||||
for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) {
|
||||
|
@ -76,10 +82,10 @@ ExPolygon::is_valid() const
|
|||
}
|
||||
|
||||
bool
|
||||
ExPolygon::contains_line(const Line* line) const
|
||||
ExPolygon::contains_line(const Line &line) const
|
||||
{
|
||||
Polylines pl;
|
||||
pl.push_back(*line);
|
||||
pl.push_back(line);
|
||||
|
||||
Polylines pl_out;
|
||||
diff(pl, *this, pl_out);
|
||||
|
@ -87,7 +93,7 @@ ExPolygon::contains_line(const Line* line) const
|
|||
}
|
||||
|
||||
bool
|
||||
ExPolygon::contains_point(const Point* point) const
|
||||
ExPolygon::contains_point(const Point &point) const
|
||||
{
|
||||
if (!this->contour.contains_point(point)) return false;
|
||||
for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) {
|
||||
|
@ -149,7 +155,9 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
|
|||
ma.build(polylines);
|
||||
|
||||
// extend initial and final segments of each polyline (they will be clipped)
|
||||
// unless they represent closed loops
|
||||
for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) {
|
||||
if (polyline->points.front().coincides_with(polyline->points.back())) continue;
|
||||
polyline->extend_start(max_width);
|
||||
polyline->extend_end(max_width);
|
||||
}
|
||||
|
@ -158,35 +166,214 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
|
|||
intersection(*polylines, *this, *polylines);
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::get_trapezoids(Polygons* polygons) const
|
||||
{
|
||||
ExPolygons expp;
|
||||
expp.push_back(*this);
|
||||
boost::polygon::get_trapezoids(*polygons, expp);
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::get_trapezoids(Polygons* polygons, double angle) const
|
||||
{
|
||||
ExPolygon clone = *this;
|
||||
clone.rotate(PI/2 - angle, Point(0,0));
|
||||
clone.get_trapezoids(polygons);
|
||||
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
|
||||
polygon->rotate(-(PI/2 - angle), Point(0,0));
|
||||
}
|
||||
|
||||
// This algorithm may return more trapezoids than necessary
|
||||
// (i.e. it may break a single trapezoid in several because
|
||||
// other parts of the object have x coordinates in the middle)
|
||||
void
|
||||
ExPolygon::get_trapezoids2(Polygons* polygons) const
|
||||
{
|
||||
// get all points of this ExPolygon
|
||||
Points pp = *this;
|
||||
|
||||
// build our bounding box
|
||||
BoundingBox bb(pp);
|
||||
|
||||
// get all x coordinates
|
||||
std::vector<coord_t> xx;
|
||||
xx.reserve(pp.size());
|
||||
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p)
|
||||
xx.push_back(p->x);
|
||||
std::sort(xx.begin(), xx.end());
|
||||
|
||||
// find trapezoids by looping from first to next-to-last coordinate
|
||||
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end()-1; ++x) {
|
||||
coord_t next_x = *(x + 1);
|
||||
if (*x == next_x) continue;
|
||||
|
||||
// build rectangle
|
||||
Polygon poly;
|
||||
poly.points.resize(4);
|
||||
poly[0].x = *x;
|
||||
poly[0].y = bb.min.y;
|
||||
poly[1].x = next_x;
|
||||
poly[1].y = bb.min.y;
|
||||
poly[2].x = next_x;
|
||||
poly[2].y = bb.max.y;
|
||||
poly[3].x = *x;
|
||||
poly[3].y = bb.max.y;
|
||||
|
||||
// intersect with this expolygon
|
||||
Polygons trapezoids;
|
||||
intersection<Polygons,Polygons>(poly, *this, trapezoids);
|
||||
|
||||
// append results to return value
|
||||
polygons->insert(polygons->end(), trapezoids.begin(), trapezoids.end());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const
|
||||
{
|
||||
ExPolygon clone = *this;
|
||||
clone.rotate(PI/2 - angle, Point(0,0));
|
||||
clone.get_trapezoids2(polygons);
|
||||
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
|
||||
polygon->rotate(-(PI/2 - angle), Point(0,0));
|
||||
}
|
||||
|
||||
// While this triangulates successfully, it's NOT a constrained triangulation
|
||||
// as it will create more vertices on the boundaries than the ones supplied.
|
||||
void
|
||||
ExPolygon::triangulate(Polygons* polygons) const
|
||||
{
|
||||
// first make trapezoids
|
||||
Polygons trapezoids;
|
||||
this->get_trapezoids2(&trapezoids);
|
||||
|
||||
// then triangulate each trapezoid
|
||||
for (Polygons::iterator polygon = trapezoids.begin(); polygon != trapezoids.end(); ++polygon)
|
||||
polygon->triangulate_convex(polygons);
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::triangulate_pp(Polygons* polygons) const
|
||||
{
|
||||
// convert polygons
|
||||
std::list<TPPLPoly> input;
|
||||
|
||||
Polygons pp = *this;
|
||||
simplify_polygons(pp, pp, true);
|
||||
ExPolygons expp;
|
||||
union_(pp, expp);
|
||||
|
||||
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
|
||||
// contour
|
||||
{
|
||||
TPPLPoly p;
|
||||
p.Init(ex->contour.points.size());
|
||||
//printf("%zu\n0\n", ex->contour.points.size());
|
||||
for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) {
|
||||
p[ point-ex->contour.points.begin() ].x = point->x;
|
||||
p[ point-ex->contour.points.begin() ].y = point->y;
|
||||
//printf("%ld %ld\n", point->x, point->y);
|
||||
}
|
||||
p.SetHole(false);
|
||||
input.push_back(p);
|
||||
}
|
||||
|
||||
// holes
|
||||
for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) {
|
||||
TPPLPoly p;
|
||||
p.Init(hole->points.size());
|
||||
//printf("%zu\n1\n", hole->points.size());
|
||||
for (Points::const_iterator point = hole->points.begin(); point != hole->points.end(); ++point) {
|
||||
p[ point-hole->points.begin() ].x = point->x;
|
||||
p[ point-hole->points.begin() ].y = point->y;
|
||||
//printf("%ld %ld\n", point->x, point->y);
|
||||
}
|
||||
p.SetHole(true);
|
||||
input.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
// perform triangulation
|
||||
std::list<TPPLPoly> output;
|
||||
int res = TPPLPartition().Triangulate_MONO(&input, &output);
|
||||
if (res != 1) CONFESS("Triangulation failed");
|
||||
|
||||
// convert output polygons
|
||||
for (std::list<TPPLPoly>::iterator poly = output.begin(); poly != output.end(); ++poly) {
|
||||
long num_points = poly->GetNumPoints();
|
||||
Polygon p;
|
||||
p.points.resize(num_points);
|
||||
for (long i = 0; i < num_points; ++i) {
|
||||
p.points[i].x = (*poly)[i].x;
|
||||
p.points[i].y = (*poly)[i].y;
|
||||
}
|
||||
polygons->push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::triangulate_p2t(Polygons* polygons) const
|
||||
{
|
||||
ExPolygons expp;
|
||||
simplify_polygons(*this, expp, true);
|
||||
|
||||
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
|
||||
p2t::CDT* cdt;
|
||||
|
||||
// TODO: prevent duplicate points
|
||||
|
||||
// contour
|
||||
{
|
||||
std::vector<p2t::Point*> points;
|
||||
for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) {
|
||||
points.push_back(new p2t::Point(point->x, point->y));
|
||||
}
|
||||
cdt = new p2t::CDT(points);
|
||||
}
|
||||
|
||||
// holes
|
||||
for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) {
|
||||
std::vector<p2t::Point*> points;
|
||||
for (Points::const_iterator point = hole->points.begin(); point != hole->points.end(); ++point) {
|
||||
points.push_back(new p2t::Point(point->x, point->y));
|
||||
}
|
||||
cdt->AddHole(points);
|
||||
}
|
||||
|
||||
// perform triangulation
|
||||
cdt->Triangulate();
|
||||
std::vector<p2t::Triangle*> triangles = cdt->GetTriangles();
|
||||
|
||||
for (std::vector<p2t::Triangle*>::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle) {
|
||||
Polygon p;
|
||||
for (int i = 0; i <= 2; ++i) {
|
||||
p2t::Point* point = (*triangle)->GetPoint(i);
|
||||
p.points.push_back(Point(point->x, point->y));
|
||||
}
|
||||
polygons->push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
|
||||
REGISTER_CLASS(ExPolygon, "ExPolygon");
|
||||
|
||||
SV*
|
||||
ExPolygon::to_AV() {
|
||||
const unsigned int num_holes = this->holes.size();
|
||||
AV* av = newAV();
|
||||
av_extend(av, num_holes); // -1 +1
|
||||
|
||||
av_store(av, 0, this->contour.to_SV_ref());
|
||||
av_store(av, 0, perl_to_SV_ref(this->contour));
|
||||
|
||||
for (unsigned int i = 0; i < num_holes; i++) {
|
||||
av_store(av, i+1, this->holes[i].to_SV_ref());
|
||||
av_store(av, i+1, perl_to_SV_ref(this->holes[i]));
|
||||
}
|
||||
return newRV_noinc((SV*)av);
|
||||
}
|
||||
|
||||
SV*
|
||||
ExPolygon::to_SV_ref() {
|
||||
SV* sv = newSV(0);
|
||||
sv_setref_pv( sv, "Slic3r::ExPolygon::Ref", this );
|
||||
return sv;
|
||||
}
|
||||
|
||||
SV*
|
||||
ExPolygon::to_SV_clone_ref() const {
|
||||
SV* sv = newSV(0);
|
||||
sv_setref_pv( sv, "Slic3r::ExPolygon", new ExPolygon(*this) );
|
||||
return sv;
|
||||
}
|
||||
|
||||
SV*
|
||||
ExPolygon::to_SV_pureperl() const
|
||||
{
|
||||
|
@ -219,8 +406,8 @@ void
|
|||
ExPolygon::from_SV_check(SV* expoly_sv)
|
||||
{
|
||||
if (sv_isobject(expoly_sv) && (SvTYPE(SvRV(expoly_sv)) == SVt_PVMG)) {
|
||||
if (!sv_isa(expoly_sv, "Slic3r::ExPolygon") && !sv_isa(expoly_sv, "Slic3r::ExPolygon::Ref"))
|
||||
CONFESS("Not a valid Slic3r::ExPolygon object");
|
||||
if (!sv_isa(expoly_sv, perl_class_name(this)) && !sv_isa(expoly_sv, perl_class_name_ref(this)))
|
||||
CONFESS("Not a valid %s object", perl_class_name(this));
|
||||
// a XS ExPolygon was supplied
|
||||
*this = *(ExPolygon *)SvIV((SV*)SvRV( expoly_sv ));
|
||||
} else {
|
||||
|
|
|
@ -18,26 +18,135 @@ class ExPolygon
|
|||
operator Polygons() const;
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, Point* center);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
double area() const;
|
||||
bool is_valid() const;
|
||||
bool contains_line(const Line* line) const;
|
||||
bool contains_point(const Point* point) const;
|
||||
bool contains_line(const Line &line) const;
|
||||
bool contains_point(const Point &point) const;
|
||||
Polygons simplify_p(double tolerance) const;
|
||||
ExPolygons simplify(double tolerance) const;
|
||||
void simplify(double tolerance, ExPolygons &expolygons) const;
|
||||
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
|
||||
void get_trapezoids(Polygons* polygons) const;
|
||||
void get_trapezoids(Polygons* polygons, double angle) const;
|
||||
void get_trapezoids2(Polygons* polygons) const;
|
||||
void get_trapezoids2(Polygons* polygons, double angle) const;
|
||||
void triangulate(Polygons* polygons) const;
|
||||
void triangulate_pp(Polygons* polygons) const;
|
||||
void triangulate_p2t(Polygons* polygons) const;
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
void from_SV(SV* poly_sv);
|
||||
void from_SV_check(SV* poly_sv);
|
||||
SV* to_AV();
|
||||
SV* to_SV_ref();
|
||||
SV* to_SV_clone_ref() const;
|
||||
SV* to_SV_pureperl() const;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// start Boost
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
namespace boost { namespace polygon {
|
||||
template <>
|
||||
struct polygon_traits<ExPolygon> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Points::const_iterator iterator_type;
|
||||
typedef Point point_type;
|
||||
|
||||
// Get the begin iterator
|
||||
static inline iterator_type begin_points(const ExPolygon& t) {
|
||||
return t.contour.points.begin();
|
||||
}
|
||||
|
||||
// Get the end iterator
|
||||
static inline iterator_type end_points(const ExPolygon& t) {
|
||||
return t.contour.points.end();
|
||||
}
|
||||
|
||||
// Get the number of sides of the polygon
|
||||
static inline std::size_t size(const ExPolygon& t) {
|
||||
return t.contour.points.size();
|
||||
}
|
||||
|
||||
// Get the winding direction of the polygon
|
||||
static inline winding_direction winding(const ExPolygon& t) {
|
||||
return unknown_winding;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_mutable_traits<ExPolygon> {
|
||||
//expects stl style iterators
|
||||
template <typename iT>
|
||||
static inline ExPolygon& set_points(ExPolygon& expolygon, iT input_begin, iT input_end) {
|
||||
expolygon.contour.points.assign(input_begin, input_end);
|
||||
// skip last point since Boost will set last point = first point
|
||||
expolygon.contour.points.pop_back();
|
||||
return expolygon;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct geometry_concept<ExPolygon> { typedef polygon_with_holes_concept type; };
|
||||
|
||||
template <>
|
||||
struct polygon_with_holes_traits<ExPolygon> {
|
||||
typedef Polygons::const_iterator iterator_holes_type;
|
||||
typedef Polygon hole_type;
|
||||
static inline iterator_holes_type begin_holes(const ExPolygon& t) {
|
||||
return t.holes.begin();
|
||||
}
|
||||
static inline iterator_holes_type end_holes(const ExPolygon& t) {
|
||||
return t.holes.end();
|
||||
}
|
||||
static inline unsigned int size_holes(const ExPolygon& t) {
|
||||
return t.holes.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_with_holes_mutable_traits<ExPolygon> {
|
||||
template <typename iT>
|
||||
static inline ExPolygon& set_holes(ExPolygon& t, iT inputBegin, iT inputEnd) {
|
||||
t.holes.assign(inputBegin, inputEnd);
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
//first we register CPolygonSet as a polygon set
|
||||
template <>
|
||||
struct geometry_concept<ExPolygons> { typedef polygon_set_concept type; };
|
||||
|
||||
//next we map to the concept through traits
|
||||
template <>
|
||||
struct polygon_set_traits<ExPolygons> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef ExPolygons::const_iterator iterator_type;
|
||||
typedef ExPolygons operator_arg_type;
|
||||
|
||||
static inline iterator_type begin(const ExPolygons& polygon_set) {
|
||||
return polygon_set.begin();
|
||||
}
|
||||
|
||||
static inline iterator_type end(const ExPolygons& polygon_set) {
|
||||
return polygon_set.end();
|
||||
}
|
||||
|
||||
//don't worry about these, just return false from them
|
||||
static inline bool clean(const ExPolygons& polygon_set) { return false; }
|
||||
static inline bool sorted(const ExPolygons& polygon_set) { return false; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_set_mutable_traits<ExPolygons> {
|
||||
template <typename input_iterator_type>
|
||||
static inline void set(ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) {
|
||||
expolygons.assign(input_begin, input_end);
|
||||
}
|
||||
};
|
||||
} }
|
||||
// end Boost
|
||||
|
||||
#endif
|
||||
|
|
|
@ -32,7 +32,7 @@ ExPolygonCollection::translate(double x, double y)
|
|||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::rotate(double angle, Point* center)
|
||||
ExPolygonCollection::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).rotate(angle, center);
|
||||
|
@ -40,7 +40,7 @@ ExPolygonCollection::rotate(double angle, Point* center)
|
|||
}
|
||||
|
||||
bool
|
||||
ExPolygonCollection::contains_point(const Point* point) const
|
||||
ExPolygonCollection::contains_point(const Point &point) const
|
||||
{
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
if (it->contains_point(point)) return true;
|
||||
|
@ -67,4 +67,8 @@ ExPolygonCollection::convex_hull(Polygon* hull) const
|
|||
Slic3r::Geometry::convex_hull(pp, hull);
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ class ExPolygonCollection
|
|||
operator Polygons() const;
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, Point* center);
|
||||
bool contains_point(const Point* point) const;
|
||||
void rotate(double angle, const Point ¢er);
|
||||
bool contains_point(const Point &point) const;
|
||||
void simplify(double tolerance);
|
||||
void convex_hull(Polygon* hull) const;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
#include "Extruder.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Extruder::Extruder(int id, PrintConfig *config)
|
||||
: id(id),
|
||||
config(config)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void
|
||||
Extruder::reset()
|
||||
{
|
||||
this->E = 0;
|
||||
this->absolute_E = 0;
|
||||
this->retracted = 0;
|
||||
this->restart_extra = 0;
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::extrude(double dE)
|
||||
{
|
||||
if (this->config->use_relative_e_distances) {
|
||||
this->E = 0;
|
||||
}
|
||||
|
||||
this->E += dE;
|
||||
this->absolute_E += dE;
|
||||
return this->E;
|
||||
}
|
||||
|
||||
Pointf
|
||||
Extruder::extruder_offset() const
|
||||
{
|
||||
return this->config->extruder_offset.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::nozzle_diameter() const
|
||||
{
|
||||
return this->config->nozzle_diameter.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::filament_diameter() const
|
||||
{
|
||||
return this->config->filament_diameter.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::extrusion_multiplier() const
|
||||
{
|
||||
return this->config->extrusion_multiplier.get_at(this->id);
|
||||
}
|
||||
|
||||
int
|
||||
Extruder::temperature() const
|
||||
{
|
||||
return this->config->temperature.get_at(this->id);
|
||||
}
|
||||
|
||||
int
|
||||
Extruder::first_layer_temperature() const
|
||||
{
|
||||
return this->config->first_layer_temperature.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::retract_length() const
|
||||
{
|
||||
return this->config->retract_length.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::retract_lift() const
|
||||
{
|
||||
return this->config->retract_lift.get_at(this->id);
|
||||
}
|
||||
|
||||
int
|
||||
Extruder::retract_speed() const
|
||||
{
|
||||
return this->config->retract_speed.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::retract_restart_extra() const
|
||||
{
|
||||
return this->config->retract_restart_extra.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::retract_before_travel() const
|
||||
{
|
||||
return this->config->retract_before_travel.get_at(this->id);
|
||||
}
|
||||
|
||||
bool
|
||||
Extruder::retract_layer_change() const
|
||||
{
|
||||
return this->config->retract_layer_change.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::retract_length_toolchange() const
|
||||
{
|
||||
return this->config->retract_length_toolchange.get_at(this->id);
|
||||
}
|
||||
|
||||
double
|
||||
Extruder::retract_restart_extra_toolchange() const
|
||||
{
|
||||
return this->config->retract_restart_extra_toolchange.get_at(this->id);
|
||||
}
|
||||
|
||||
bool
|
||||
Extruder::wipe() const
|
||||
{
|
||||
return this->config->wipe.get_at(this->id);
|
||||
}
|
||||
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(Extruder, "Extruder");
|
||||
#endif
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef slic3r_Extruder_hpp_
|
||||
#define slic3r_Extruder_hpp_
|
||||
|
||||
#include <myinit.h>
|
||||
#include "Point.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Extruder
|
||||
{
|
||||
public:
|
||||
Extruder(int id, PrintConfig *config);
|
||||
virtual ~Extruder() {}
|
||||
void reset();
|
||||
double extrude(double dE);
|
||||
|
||||
|
||||
Pointf extruder_offset() const;
|
||||
double nozzle_diameter() const;
|
||||
double filament_diameter() const;
|
||||
double extrusion_multiplier() const;
|
||||
int temperature() const;
|
||||
int first_layer_temperature() const;
|
||||
double retract_length() const;
|
||||
double retract_lift() const;
|
||||
int retract_speed() const;
|
||||
double retract_restart_extra() const;
|
||||
double retract_before_travel() const;
|
||||
bool retract_layer_change() const;
|
||||
double retract_length_toolchange() const;
|
||||
double retract_restart_extra_toolchange() const;
|
||||
bool wipe() const;
|
||||
|
||||
int id;
|
||||
double E;
|
||||
double absolute_E;
|
||||
double retracted;
|
||||
double restart_extra;
|
||||
|
||||
PrintConfig *config;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -2,34 +2,11 @@
|
|||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include <sstream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool
|
||||
ExtrusionEntity::is_perimeter() const
|
||||
{
|
||||
return this->role == erPerimeter
|
||||
|| this->role == erExternalPerimeter
|
||||
|| this->role == erOverhangPerimeter
|
||||
|| this->role == erContourInternalPerimeter;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionEntity::is_fill() const
|
||||
{
|
||||
return this->role == erFill
|
||||
|| this->role == erSolidFill
|
||||
|| this->role == erTopSolidFill;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionEntity::is_bridge() const
|
||||
{
|
||||
return this->role == erBrige
|
||||
|| this->role == erInternalBridge
|
||||
|| this->role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
ExtrusionPath*
|
||||
ExtrusionPath::clone() const
|
||||
{
|
||||
|
@ -42,16 +19,16 @@ ExtrusionPath::reverse()
|
|||
this->polyline.reverse();
|
||||
}
|
||||
|
||||
Point*
|
||||
Point
|
||||
ExtrusionPath::first_point() const
|
||||
{
|
||||
return new Point(this->polyline.points.front());
|
||||
return this->polyline.points.front();
|
||||
}
|
||||
|
||||
Point*
|
||||
Point
|
||||
ExtrusionPath::last_point() const
|
||||
{
|
||||
return new Point(this->polyline.points.back());
|
||||
return this->polyline.points.back();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -59,7 +36,7 @@ ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, Extru
|
|||
{
|
||||
// perform clipping
|
||||
Polylines clipped;
|
||||
intersection(this->polyline, collection, clipped);
|
||||
intersection<Polylines,Polylines>(this->polyline, collection, clipped);
|
||||
return this->_inflate_collection(clipped, retval);
|
||||
}
|
||||
|
||||
|
@ -68,7 +45,7 @@ ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, Extrus
|
|||
{
|
||||
// perform clipping
|
||||
Polylines clipped;
|
||||
diff(this->polyline, collection, clipped);
|
||||
diff<Polylines,Polylines>(this->polyline, collection, clipped);
|
||||
return this->_inflate_collection(clipped, retval);
|
||||
}
|
||||
|
||||
|
@ -90,6 +67,29 @@ ExtrusionPath::length() const
|
|||
return this->polyline.length();
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionPath::is_perimeter() const
|
||||
{
|
||||
return this->role == erPerimeter
|
||||
|| this->role == erExternalPerimeter
|
||||
|| this->role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionPath::is_fill() const
|
||||
{
|
||||
return this->role == erInternalInfill
|
||||
|| this->role == erSolidInfill
|
||||
|| this->role == erTopSolidInfill;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionPath::is_bridge() const
|
||||
{
|
||||
return this->role == erBridgeInfill
|
||||
|| this->role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
|
||||
{
|
||||
|
@ -100,54 +100,236 @@ ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCo
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ExtrusionPath, "ExtrusionPath");
|
||||
#endif
|
||||
|
||||
std::string
|
||||
ExtrusionPath::gcode(Extruder* extruder, double e, double F,
|
||||
double xofs, double yofs, std::string extrusion_axis,
|
||||
std::string gcode_line_suffix) const
|
||||
{
|
||||
dSP;
|
||||
|
||||
std::stringstream stream;
|
||||
stream.setf(std::ios::fixed);
|
||||
|
||||
double local_F = F;
|
||||
|
||||
Lines lines = this->polyline.lines();
|
||||
for (Lines::const_iterator line_it = lines.begin();
|
||||
line_it != lines.end(); ++line_it)
|
||||
{
|
||||
const double line_length = line_it->length() * SCALING_FACTOR;
|
||||
|
||||
// calculate extrusion length for this line
|
||||
double E = (e == 0) ? 0 : extruder->extrude(e * line_length);
|
||||
|
||||
// compose G-code line
|
||||
|
||||
Point point = line_it->b;
|
||||
const double x = point.x * SCALING_FACTOR + xofs;
|
||||
const double y = point.y * SCALING_FACTOR + yofs;
|
||||
stream.precision(3);
|
||||
stream << "G1 X" << x << " Y" << y;
|
||||
|
||||
if (E != 0) {
|
||||
stream.precision(5);
|
||||
stream << " " << extrusion_axis << E;
|
||||
}
|
||||
|
||||
if (local_F != 0) {
|
||||
stream.precision(3);
|
||||
stream << " F" << local_F;
|
||||
local_F = 0;
|
||||
}
|
||||
|
||||
stream << gcode_line_suffix;
|
||||
stream << "\n";
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
ExtrusionLoop::operator Polygon() const
|
||||
{
|
||||
Polygon polygon;
|
||||
this->polygon(&polygon);
|
||||
return polygon;
|
||||
}
|
||||
|
||||
ExtrusionLoop*
|
||||
ExtrusionLoop::clone() const
|
||||
{
|
||||
return new ExtrusionLoop (*this);
|
||||
}
|
||||
|
||||
ExtrusionPath*
|
||||
ExtrusionLoop::split_at_index(int index) const
|
||||
bool
|
||||
ExtrusionLoop::make_clockwise()
|
||||
{
|
||||
Polyline* poly = this->polygon.split_at_index(index);
|
||||
|
||||
ExtrusionPath* path = new ExtrusionPath();
|
||||
path->polyline = *poly;
|
||||
path->role = this->role;
|
||||
path->mm3_per_mm = this->mm3_per_mm;
|
||||
|
||||
delete poly;
|
||||
return path;
|
||||
}
|
||||
|
||||
ExtrusionPath*
|
||||
ExtrusionLoop::split_at_first_point() const
|
||||
{
|
||||
return this->split_at_index(0);
|
||||
Polygon polygon = *this;
|
||||
bool was_ccw = polygon.is_counter_clockwise();
|
||||
if (was_ccw) this->reverse();
|
||||
return was_ccw;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionLoop::make_counter_clockwise()
|
||||
{
|
||||
return this->polygon.make_counter_clockwise();
|
||||
Polygon polygon = *this;
|
||||
bool was_cw = polygon.is_clockwise();
|
||||
if (was_cw) this->reverse();
|
||||
return was_cw;
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionLoop::reverse()
|
||||
{
|
||||
// no-op
|
||||
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
|
||||
path->reverse();
|
||||
std::reverse(this->paths.begin(), this->paths.end());
|
||||
}
|
||||
|
||||
Point*
|
||||
Point
|
||||
ExtrusionLoop::first_point() const
|
||||
{
|
||||
return new Point(this->polygon.points.front());
|
||||
return this->paths.front().polyline.points.front();
|
||||
}
|
||||
|
||||
Point*
|
||||
Point
|
||||
ExtrusionLoop::last_point() const
|
||||
{
|
||||
return new Point(this->polygon.points.front()); // in polygons, first == last
|
||||
return this->paths.back().polyline.points.back(); // which coincides with first_point(), by the way
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionLoop::polygon(Polygon* polygon) const
|
||||
{
|
||||
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
|
||||
// for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
|
||||
polygon->points.insert(polygon->points.end(), path->polyline.points.begin(), path->polyline.points.end()-1);
|
||||
}
|
||||
}
|
||||
|
||||
double
|
||||
ExtrusionLoop::length() const
|
||||
{
|
||||
double len = 0;
|
||||
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
|
||||
len += path->polyline.length();
|
||||
return len;
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionLoop::split_at_vertex(const Point &point)
|
||||
{
|
||||
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
|
||||
int idx = path->polyline.find_point(point);
|
||||
if (idx != -1) {
|
||||
if (this->paths.size() == 1) {
|
||||
// just change the order of points
|
||||
path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1);
|
||||
path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx);
|
||||
} else {
|
||||
// if we have multiple paths we assume they have different types, so no need to
|
||||
// check for continuity as we do for the single path case above
|
||||
|
||||
// new paths list starts with the second half of current path
|
||||
ExtrusionPaths new_paths;
|
||||
{
|
||||
ExtrusionPath p = *path;
|
||||
p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx);
|
||||
if (p.polyline.is_valid()) new_paths.push_back(p);
|
||||
}
|
||||
|
||||
// then we add all paths until the end of current path list
|
||||
new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path
|
||||
|
||||
// then we add all paths since the beginning of current list up to the previous one
|
||||
new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path
|
||||
|
||||
// finally we add the first half of current path
|
||||
{
|
||||
ExtrusionPath p = *path;
|
||||
p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end());
|
||||
if (p.polyline.is_valid()) new_paths.push_back(p);
|
||||
}
|
||||
// we can now override the old path list with the new one and stop looping
|
||||
this->paths = new_paths;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
CONFESS("Point not found");
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionLoop::split_at(const Point &point)
|
||||
{
|
||||
if (this->paths.empty()) return;
|
||||
|
||||
// find the closest path and closest point
|
||||
size_t path_idx = 0;
|
||||
Point p = this->paths.front().first_point();
|
||||
double min = point.distance_to(p);
|
||||
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
|
||||
Point p_tmp = point.projection_onto(path->polyline);
|
||||
double dist = point.distance_to(p_tmp);
|
||||
if (dist < min) {
|
||||
p = p_tmp;
|
||||
min = dist;
|
||||
path_idx = path - this->paths.begin();
|
||||
}
|
||||
}
|
||||
|
||||
// now split path_idx in two parts
|
||||
ExtrusionPath p1 = this->paths[path_idx];
|
||||
ExtrusionPath p2 = p1;
|
||||
this->paths[path_idx].polyline.split_at(p, &p1.polyline, &p2.polyline);
|
||||
|
||||
// install the two paths
|
||||
this->paths.erase(this->paths.begin() + path_idx);
|
||||
if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2);
|
||||
if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1);
|
||||
|
||||
// split at the new vertex
|
||||
this->split_at_vertex(p);
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
|
||||
{
|
||||
*paths = this->paths;
|
||||
|
||||
while (distance > 0 && !paths->empty()) {
|
||||
ExtrusionPath &last = paths->back();
|
||||
double len = last.length();
|
||||
if (len <= distance) {
|
||||
paths->pop_back();
|
||||
distance -= len;
|
||||
} else {
|
||||
last.polyline.clip_end(distance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ExtrusionLoop::has_overhang_point(const Point &point) const
|
||||
{
|
||||
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
|
||||
int pos = path->polyline.find_point(point);
|
||||
if (pos != -1) {
|
||||
// point belongs to this path
|
||||
// we consider it overhang only if it's not an endpoint
|
||||
return (path->is_bridge() && pos > 0 && pos != path->polyline.points.size()-1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -9,20 +9,28 @@ namespace Slic3r {
|
|||
|
||||
class ExPolygonCollection;
|
||||
class ExtrusionEntityCollection;
|
||||
class Extruder;
|
||||
|
||||
/* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */
|
||||
enum ExtrusionRole {
|
||||
erPerimeter,
|
||||
erExternalPerimeter,
|
||||
erOverhangPerimeter,
|
||||
erContourInternalPerimeter,
|
||||
erFill,
|
||||
erSolidFill,
|
||||
erTopSolidFill,
|
||||
erBrige,
|
||||
erInternalBridge,
|
||||
erInternalInfill,
|
||||
erSolidInfill,
|
||||
erTopSolidInfill,
|
||||
erBridgeInfill,
|
||||
erGapFill,
|
||||
erSkirt,
|
||||
erSupportMaterial,
|
||||
erGapFill,
|
||||
erSupportMaterialInterface,
|
||||
};
|
||||
|
||||
/* Special flags describing loop */
|
||||
enum ExtrusionLoopRole {
|
||||
elrDefault,
|
||||
elrExternalPerimeter,
|
||||
elrContourInternalPerimeter,
|
||||
};
|
||||
|
||||
class ExtrusionEntity
|
||||
|
@ -30,14 +38,9 @@ class ExtrusionEntity
|
|||
public:
|
||||
virtual ExtrusionEntity* clone() const = 0;
|
||||
virtual ~ExtrusionEntity() {};
|
||||
ExtrusionRole role;
|
||||
double mm3_per_mm; // mm^3 of plastic per mm of linear head motion
|
||||
virtual void reverse() = 0;
|
||||
virtual Point* first_point() const = 0;
|
||||
virtual Point* last_point() const = 0;
|
||||
bool is_perimeter() const;
|
||||
bool is_fill() const;
|
||||
bool is_bridge() const;
|
||||
virtual Point first_point() const = 0;
|
||||
virtual Point last_point() const = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
|
||||
|
@ -45,31 +48,55 @@ typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
|
|||
class ExtrusionPath : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
ExtrusionPath* clone() const;
|
||||
Polyline polyline;
|
||||
ExtrusionRole role;
|
||||
double mm3_per_mm; // mm^3 of plastic per mm of linear head motion
|
||||
float width;
|
||||
float height;
|
||||
|
||||
ExtrusionPath(ExtrusionRole role) : role(role), mm3_per_mm(-1), width(-1), height(-1) {};
|
||||
ExtrusionPath* clone() const;
|
||||
void reverse();
|
||||
Point* first_point() const;
|
||||
Point* last_point() const;
|
||||
Point first_point() const;
|
||||
Point last_point() const;
|
||||
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
void clip_end(double distance);
|
||||
void simplify(double tolerance);
|
||||
double length() const;
|
||||
bool is_perimeter() const;
|
||||
bool is_fill() const;
|
||||
bool is_bridge() const;
|
||||
std::string gcode(Extruder* extruder, double e, double F,
|
||||
double xofs, double yofs, std::string extrusion_axis,
|
||||
std::string gcode_line_suffix) const;
|
||||
|
||||
private:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
};
|
||||
|
||||
typedef std::vector<ExtrusionPath> ExtrusionPaths;
|
||||
|
||||
class ExtrusionLoop : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
ExtrusionPaths paths;
|
||||
ExtrusionLoopRole role;
|
||||
|
||||
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {};
|
||||
operator Polygon() const;
|
||||
ExtrusionLoop* clone() const;
|
||||
Polygon polygon;
|
||||
ExtrusionPath* split_at_index(int index) const;
|
||||
ExtrusionPath* split_at_first_point() const;
|
||||
bool make_clockwise();
|
||||
bool make_counter_clockwise();
|
||||
void reverse();
|
||||
Point* first_point() const;
|
||||
Point* last_point() const;
|
||||
Point first_point() const;
|
||||
Point last_point() const;
|
||||
void polygon(Polygon* polygon) const;
|
||||
double length() const;
|
||||
void split_at_vertex(const Point &point);
|
||||
void split_at(const Point &point);
|
||||
void clip_end(double distance, ExtrusionPaths* paths) const;
|
||||
bool has_overhang_point(const Point &point) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,16 +1,36 @@
|
|||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionEntityCollection& collection)
|
||||
: no_sort(collection.no_sort), orig_indices(collection.orig_indices)
|
||||
{
|
||||
this->entities.reserve(collection.entities.size());
|
||||
for (ExtrusionEntitiesPtr::const_iterator it = collection.entities.begin(); it != collection.entities.end(); ++it)
|
||||
this->entities.push_back((*it)->clone());
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other)
|
||||
{
|
||||
ExtrusionEntityCollection tmp(other);
|
||||
this->swap(tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c)
|
||||
{
|
||||
std::swap(this->entities, c.entities);
|
||||
std::swap(this->orig_indices, c.orig_indices);
|
||||
std::swap(this->no_sort, c.no_sort);
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection*
|
||||
ExtrusionEntityCollection::clone() const
|
||||
{
|
||||
ExtrusionEntityCollection* collection = new ExtrusionEntityCollection (*this);
|
||||
for (ExtrusionEntitiesPtr::iterator it = collection->entities.begin(); it != collection->entities.end(); ++it) {
|
||||
*it = (*it)->clone();
|
||||
}
|
||||
return collection;
|
||||
return new ExtrusionEntityCollection(*this);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -22,32 +42,32 @@ ExtrusionEntityCollection::reverse()
|
|||
std::reverse(this->entities.begin(), this->entities.end());
|
||||
}
|
||||
|
||||
Point*
|
||||
Point
|
||||
ExtrusionEntityCollection::first_point() const
|
||||
{
|
||||
return this->entities.front()->first_point();
|
||||
}
|
||||
|
||||
Point*
|
||||
Point
|
||||
ExtrusionEntityCollection::last_point() const
|
||||
{
|
||||
return this->entities.back()->last_point();
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection*
|
||||
ExtrusionEntityCollection::chained_path(bool no_reverse, std::vector<size_t>* orig_indices) const
|
||||
void
|
||||
ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, std::vector<size_t>* orig_indices) const
|
||||
{
|
||||
if (this->entities.empty()) {
|
||||
return new ExtrusionEntityCollection ();
|
||||
}
|
||||
return this->chained_path_from(this->entities.front()->first_point(), no_reverse, orig_indices);
|
||||
if (this->entities.empty()) return;
|
||||
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, orig_indices);
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection*
|
||||
ExtrusionEntityCollection::chained_path_from(Point* start_near, bool no_reverse, std::vector<size_t>* orig_indices) const
|
||||
void
|
||||
ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, std::vector<size_t>* orig_indices) const
|
||||
{
|
||||
if (this->no_sort) return this->clone();
|
||||
ExtrusionEntityCollection* retval = new ExtrusionEntityCollection;
|
||||
if (this->no_sort) {
|
||||
*retval = *this;
|
||||
return;
|
||||
}
|
||||
retval->entities.reserve(this->entities.size());
|
||||
retval->orig_indices.reserve(this->entities.size());
|
||||
|
||||
|
@ -63,17 +83,17 @@ ExtrusionEntityCollection::chained_path_from(Point* start_near, bool no_reverse,
|
|||
|
||||
Points endpoints;
|
||||
for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) {
|
||||
endpoints.push_back(*(*it)->first_point());
|
||||
endpoints.push_back((*it)->first_point());
|
||||
if (no_reverse) {
|
||||
endpoints.push_back(*(*it)->first_point());
|
||||
endpoints.push_back((*it)->first_point());
|
||||
} else {
|
||||
endpoints.push_back(*(*it)->last_point());
|
||||
endpoints.push_back((*it)->last_point());
|
||||
}
|
||||
}
|
||||
|
||||
while (!my_paths.empty()) {
|
||||
// find nearest point
|
||||
int start_index = start_near->nearest_point_index(endpoints);
|
||||
int start_index = start_near.nearest_point_index(endpoints);
|
||||
int path_index = start_index/2;
|
||||
ExtrusionEntity* entity = my_paths.at(path_index);
|
||||
if (start_index % 2 && !no_reverse) {
|
||||
|
@ -85,8 +105,11 @@ ExtrusionEntityCollection::chained_path_from(Point* start_near, bool no_reverse,
|
|||
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
|
||||
start_near = retval->entities.back()->last_point();
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
// there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection
|
||||
REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -14,11 +14,14 @@ class ExtrusionEntityCollection : public ExtrusionEntity
|
|||
std::vector<size_t> orig_indices; // handy for XS
|
||||
bool no_sort;
|
||||
ExtrusionEntityCollection(): no_sort(false) {};
|
||||
ExtrusionEntityCollection* chained_path(bool no_reverse, std::vector<size_t>* orig_indices = NULL) const;
|
||||
ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, std::vector<size_t>* orig_indices = NULL) const;
|
||||
ExtrusionEntityCollection(const ExtrusionEntityCollection &collection);
|
||||
ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other);
|
||||
void swap (ExtrusionEntityCollection &c);
|
||||
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
||||
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
||||
void reverse();
|
||||
Point* first_point() const;
|
||||
Point* last_point() const;
|
||||
Point first_point() const;
|
||||
Point last_point() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
122
xs/src/Flow.cpp
122
xs/src/Flow.cpp
|
@ -3,47 +3,96 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
/* This constructor builds a Flow object from an extrusion width config setting
|
||||
and other context properties. */
|
||||
Flow
|
||||
Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio) {
|
||||
// we need layer height unless it's a bridge
|
||||
if (height <= 0 && bridge_flow_ratio == 0) CONFESS("Invalid flow height supplied to new_from_config_width()");
|
||||
|
||||
float w;
|
||||
if (!width.percent && width.value == 0) {
|
||||
w = Flow::_width(role, nozzle_diameter, height, bridge_flow_ratio);
|
||||
if (bridge_flow_ratio > 0) {
|
||||
// if bridge flow was requested, calculate bridge width
|
||||
w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio);
|
||||
} else if (!width.percent && width.value == 0) {
|
||||
// if user left option to 0, calculate a sane default width
|
||||
w = Flow::_auto_width(role, nozzle_diameter, height);
|
||||
} else {
|
||||
// if user set a manual value, use it
|
||||
w = width.get_abs_value(height);
|
||||
}
|
||||
|
||||
Flow flow(w, Flow::_spacing(w, nozzle_diameter, height, bridge_flow_ratio), nozzle_diameter);
|
||||
if (bridge_flow_ratio > 0) flow.bridge = true;
|
||||
return flow;
|
||||
return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0);
|
||||
}
|
||||
|
||||
/* This constructor builds a Flow object from a given centerline spacing. */
|
||||
Flow
|
||||
Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) {
|
||||
// we need layer height unless it's a bridge
|
||||
if (height <= 0 && !bridge) CONFESS("Invalid flow height supplied to new_from_spacing()");
|
||||
|
||||
float w = Flow::_width_from_spacing(spacing, nozzle_diameter, height, bridge);
|
||||
Flow flow(w, spacing, nozzle_diameter);
|
||||
flow.bridge = bridge;
|
||||
return flow;
|
||||
}
|
||||
|
||||
double
|
||||
Flow::mm3_per_mm(float h) {
|
||||
if (this->bridge) {
|
||||
return (this->width * this->width) * PI/4.0;
|
||||
} else if (this->width >= (this->nozzle_diameter + h)) {
|
||||
// rectangle with semicircles at the ends
|
||||
return this->width * h + (h*h) / 4.0 * (PI-4.0);
|
||||
} else {
|
||||
// rectangle with shrunk semicircles at the ends
|
||||
return this->nozzle_diameter * h * (1 - PI/4.0) + h * this->width * PI/4.0;
|
||||
}
|
||||
return Flow(w, height, nozzle_diameter, bridge);
|
||||
}
|
||||
|
||||
/* This method returns the centerline spacing between two adjacent extrusions
|
||||
having the same extrusion width (and other properties). */
|
||||
float
|
||||
Flow::_width(FlowRole role, float nozzle_diameter, float height, float bridge_flow_ratio) {
|
||||
if (bridge_flow_ratio > 0) {
|
||||
return sqrt(bridge_flow_ratio * (nozzle_diameter*nozzle_diameter));
|
||||
Flow::spacing() const {
|
||||
if (this->bridge) {
|
||||
return this->width + BRIDGE_EXTRA_SPACING;
|
||||
}
|
||||
|
||||
float min_flow_spacing;
|
||||
if (this->width >= (this->nozzle_diameter + this->height)) {
|
||||
// rectangle with semicircles at the ends
|
||||
min_flow_spacing = this->width - this->height * (1 - PI/4.0);
|
||||
} else {
|
||||
// rectangle with shrunk semicircles at the ends
|
||||
min_flow_spacing = this->nozzle_diameter * (1 - PI/4.0) + this->width * PI/4.0;
|
||||
}
|
||||
return this->width - OVERLAP_FACTOR * (this->width - min_flow_spacing);
|
||||
}
|
||||
|
||||
/* This method returns the centerline spacing between an extrusion using this
|
||||
flow and another one using another flow.
|
||||
this->spacing(other) shall return the same value as other.spacing(*this) */
|
||||
float
|
||||
Flow::spacing(const Flow &other) const {
|
||||
assert(this->height == other.height);
|
||||
assert(this->bridge == other.bridge);
|
||||
|
||||
if (this->bridge) {
|
||||
return this->width/2 + other.width/2 + BRIDGE_EXTRA_SPACING;
|
||||
}
|
||||
|
||||
return this->spacing()/2 + other.spacing()/2;
|
||||
}
|
||||
|
||||
/* This method returns extrusion volume per head move unit. */
|
||||
double
|
||||
Flow::mm3_per_mm() const {
|
||||
if (this->bridge) {
|
||||
return (this->width * this->width) * PI/4.0;
|
||||
} else if (this->width >= (this->nozzle_diameter + this->height)) {
|
||||
// rectangle with semicircles at the ends
|
||||
return this->width * this->height + (this->height*this->height) / 4.0 * (PI-4.0);
|
||||
} else {
|
||||
// rectangle with shrunk semicircles at the ends
|
||||
return this->nozzle_diameter * this->height * (1 - PI/4.0) + this->height * this->width * PI/4.0;
|
||||
}
|
||||
}
|
||||
|
||||
/* This static method returns bridge width for a given nozzle diameter. */
|
||||
float
|
||||
Flow::_bridge_width(float nozzle_diameter, float bridge_flow_ratio) {
|
||||
if (bridge_flow_ratio == 1) return nozzle_diameter; // optimization to avoid sqrt()
|
||||
return sqrt(bridge_flow_ratio * (nozzle_diameter*nozzle_diameter));
|
||||
}
|
||||
|
||||
/* This static method returns a sane extrusion width default. */
|
||||
float
|
||||
Flow::_auto_width(FlowRole role, float nozzle_diameter, float height) {
|
||||
// here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate
|
||||
float volume = (nozzle_diameter*nozzle_diameter) * PI/4.0;
|
||||
float shape_threshold = nozzle_diameter * height + (height*height) * PI/4.0;
|
||||
|
@ -58,9 +107,9 @@ Flow::_width(FlowRole role, float nozzle_diameter, float height, float bridge_fl
|
|||
|
||||
float min = nozzle_diameter * 1.05;
|
||||
float max = -1;
|
||||
if (role == frPerimeter || role == frSupportMaterial) {
|
||||
if (role == frExternalPerimeter || role == frSupportMaterial) {
|
||||
min = max = nozzle_diameter;
|
||||
} else if (role != frInfill) {
|
||||
} else /*if (role != frInfill)*/ {
|
||||
// do not limit width for sparse infill so that we use full native flow for it
|
||||
max = nozzle_diameter * 1.7;
|
||||
}
|
||||
|
@ -70,7 +119,7 @@ Flow::_width(FlowRole role, float nozzle_diameter, float height, float bridge_fl
|
|||
return width;
|
||||
}
|
||||
|
||||
|
||||
/* This static method returns the extrusion width value corresponding to the supplied centerline spacing. */
|
||||
float
|
||||
Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) {
|
||||
if (bridge) {
|
||||
|
@ -89,21 +138,8 @@ Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bo
|
|||
}
|
||||
}
|
||||
|
||||
float
|
||||
Flow::_spacing(float width, float nozzle_diameter, float height, float bridge_flow_ratio) {
|
||||
if (bridge_flow_ratio > 0) {
|
||||
return width + BRIDGE_EXTRA_SPACING;
|
||||
}
|
||||
|
||||
float min_flow_spacing;
|
||||
if (width >= (nozzle_diameter + height)) {
|
||||
// rectangle with semicircles at the ends
|
||||
min_flow_spacing = width - height * (1 - PI/4.0);
|
||||
} else {
|
||||
// rectangle with shrunk semicircles at the ends
|
||||
min_flow_spacing = nozzle_diameter * (1 - PI/4.0) + width * PI/4.0;
|
||||
}
|
||||
return width - OVERLAP_FACTOR * (width - min_flow_spacing);
|
||||
}
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(Flow, "Flow");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Slic3r {
|
|||
#define OVERLAP_FACTOR 1.0
|
||||
|
||||
enum FlowRole {
|
||||
frExternalPerimeter,
|
||||
frPerimeter,
|
||||
frInfill,
|
||||
frSolidInfill,
|
||||
|
@ -22,23 +23,27 @@ enum FlowRole {
|
|||
class Flow
|
||||
{
|
||||
public:
|
||||
float width;
|
||||
float spacing;
|
||||
float nozzle_diameter;
|
||||
float width, height, nozzle_diameter;
|
||||
bool bridge;
|
||||
coord_t scaled_width;
|
||||
coord_t scaled_spacing;
|
||||
|
||||
Flow(float _w, float _s, float _nd): width(_w), spacing(_s), nozzle_diameter(_nd), bridge(false) {
|
||||
this->scaled_width = scale_(this->width);
|
||||
this->scaled_spacing = scale_(this->spacing);
|
||||
Flow(float _w, float _h, float _nd, bool _bridge = false)
|
||||
: width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {};
|
||||
float spacing() const;
|
||||
float spacing(const Flow &other) const;
|
||||
double mm3_per_mm() const;
|
||||
coord_t scaled_width() const {
|
||||
return scale_(this->width);
|
||||
};
|
||||
double mm3_per_mm(float h);
|
||||
coord_t scaled_spacing() const {
|
||||
return scale_(this->spacing());
|
||||
};
|
||||
|
||||
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
|
||||
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
|
||||
|
||||
private:
|
||||
static float _width(FlowRole role, float nozzle_diameter, float height, float bridge_flow_ratio);
|
||||
static float _bridge_width(float nozzle_diameter, float bridge_flow_ratio);
|
||||
static float _auto_width(FlowRole role, float nozzle_diameter, float height);
|
||||
static float _width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
|
||||
static float _spacing(float width, float nozzle_diameter, float height, float bridge_flow_ratio);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef slic3r_GCode_hpp_
|
||||
#define slic3r_GCode_hpp_
|
||||
|
||||
#include <myinit.h>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// draft for a binary representation of a G-code line
|
||||
|
||||
enum GCodeCmdType {
|
||||
gcctSyncMotion,
|
||||
gcctExtrude,
|
||||
gcctResetE,
|
||||
gcctSetTemp,
|
||||
gcctSetTempWait,
|
||||
gcctToolchange,
|
||||
gcctCustom
|
||||
};
|
||||
|
||||
class GCodeCmd {
|
||||
public:
|
||||
GCodeCmdType type;
|
||||
float X, Y, Z, E, F;
|
||||
unsigned short T, S;
|
||||
std::string custom, comment;
|
||||
float xy_dist; // cache
|
||||
|
||||
GCodeCmd(GCodeCmdType type)
|
||||
: type(type), X(0), Y(0), Z(0), E(0), F(0), T(-1), S(0), xy_dist(-1) {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -93,6 +93,14 @@ chained_path_items(Points &points, T &items, T &retval)
|
|||
}
|
||||
template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval);
|
||||
|
||||
bool
|
||||
directions_parallel(double angle1, double angle2, double max_diff)
|
||||
{
|
||||
double diff = fabs(angle1 - angle2);
|
||||
max_diff += EPSILON;
|
||||
return diff < max_diff || fabs(diff - PI) < max_diff;
|
||||
}
|
||||
|
||||
Line
|
||||
MedialAxis::edge_to_line(const VD::edge_type &edge) const
|
||||
{
|
||||
|
@ -115,6 +123,21 @@ MedialAxis::build(Polylines* polylines)
|
|||
|
||||
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
|
||||
|
||||
/*
|
||||
// DEBUG: dump all Voronoi edges
|
||||
{
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
if (edge->is_infinite()) continue;
|
||||
|
||||
Polyline polyline;
|
||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||
// note: this keeps twins, so it contains twice the number of the valid edges
|
||||
this->edges.clear();
|
||||
|
|
|
@ -15,6 +15,7 @@ void convex_hull(Points &points, Polygon* hull);
|
|||
void chained_path(Points &points, std::vector<Points::size_type> &retval, Point start_near);
|
||||
void chained_path(Points &points, std::vector<Points::size_type> &retval);
|
||||
template<class T> void chained_path_items(Points &points, T &items, T &retval);
|
||||
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
|
||||
|
||||
class MedialAxis {
|
||||
public:
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
#include "Layer.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
LayerRegion::LayerRegion(Layer *layer, PrintRegion *region)
|
||||
: _layer(layer),
|
||||
_region(region)
|
||||
{
|
||||
}
|
||||
|
||||
LayerRegion::~LayerRegion()
|
||||
{
|
||||
}
|
||||
|
||||
Layer*
|
||||
LayerRegion::layer()
|
||||
{
|
||||
return this->_layer;
|
||||
}
|
||||
|
||||
PrintRegion*
|
||||
LayerRegion::region()
|
||||
{
|
||||
return this->_region;
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(LayerRegion, "Layer::Region");
|
||||
#endif
|
||||
|
||||
|
||||
Layer::Layer(int id, PrintObject *object, coordf_t height, coordf_t print_z,
|
||||
coordf_t slice_z)
|
||||
: _id(id),
|
||||
_object(object),
|
||||
upper_layer(NULL),
|
||||
lower_layer(NULL),
|
||||
regions(),
|
||||
slicing_errors(false),
|
||||
slice_z(slice_z),
|
||||
print_z(print_z),
|
||||
height(height),
|
||||
slices()
|
||||
{
|
||||
}
|
||||
|
||||
Layer::~Layer()
|
||||
{
|
||||
// remove references to self
|
||||
if (NULL != this->upper_layer) {
|
||||
this->upper_layer->lower_layer = NULL;
|
||||
}
|
||||
|
||||
if (NULL != this->lower_layer) {
|
||||
this->lower_layer->upper_layer = NULL;
|
||||
}
|
||||
|
||||
this->clear_regions();
|
||||
}
|
||||
|
||||
int
|
||||
Layer::id()
|
||||
{
|
||||
return this->_id;
|
||||
}
|
||||
|
||||
PrintObject*
|
||||
Layer::object()
|
||||
{
|
||||
return this->_object;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Layer::region_count()
|
||||
{
|
||||
return this->regions.size();
|
||||
}
|
||||
|
||||
void
|
||||
Layer::clear_regions()
|
||||
{
|
||||
for (int i = this->regions.size()-1; i >= 0; --i)
|
||||
this->delete_region(i);
|
||||
}
|
||||
|
||||
LayerRegion*
|
||||
Layer::get_region(int idx)
|
||||
{
|
||||
return this->regions.at(idx);
|
||||
}
|
||||
|
||||
LayerRegion*
|
||||
Layer::add_region(PrintRegion* print_region)
|
||||
{
|
||||
LayerRegion* region = new LayerRegion(this, print_region);
|
||||
this->regions.push_back(region);
|
||||
return region;
|
||||
}
|
||||
|
||||
void
|
||||
Layer::delete_region(int idx)
|
||||
{
|
||||
LayerRegionPtrs::iterator i = this->regions.begin() + idx;
|
||||
LayerRegion* item = *i;
|
||||
this->regions.erase(i);
|
||||
delete item;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(Layer, "Layer");
|
||||
#endif
|
||||
|
||||
|
||||
SupportLayer::SupportLayer(int id, PrintObject *object, coordf_t height,
|
||||
coordf_t print_z, coordf_t slice_z)
|
||||
: Layer(id, object, height, print_z, slice_z)
|
||||
{
|
||||
}
|
||||
|
||||
SupportLayer::~SupportLayer()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(SupportLayer, "Layer::Support");
|
||||
#endif
|
||||
|
||||
|
||||
}
|
106
xs/src/Layer.hpp
106
xs/src/Layer.hpp
|
@ -2,12 +2,118 @@
|
|||
#define slic3r_Layer_hpp_
|
||||
|
||||
#include <myinit.h>
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "PolylineCollection.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
typedef std::pair<coordf_t,coordf_t> t_layer_height_range;
|
||||
typedef std::map<t_layer_height_range,coordf_t> t_layer_height_ranges;
|
||||
|
||||
class Layer;
|
||||
class PrintRegion;
|
||||
class PrintObject;
|
||||
|
||||
|
||||
// TODO: make stuff private
|
||||
class LayerRegion
|
||||
{
|
||||
friend class Layer;
|
||||
|
||||
public:
|
||||
Layer* layer();
|
||||
PrintRegion* region();
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
SurfaceCollection slices;
|
||||
|
||||
// collection of extrusion paths/loops filling gaps
|
||||
ExtrusionEntityCollection thin_fills;
|
||||
|
||||
// collection of surfaces for infill generation
|
||||
SurfaceCollection fill_surfaces;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
ExPolygonCollection bridged;
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
PolylineCollection unsupported_bridge_edges;
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
ExtrusionEntityCollection perimeters;
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
ExtrusionEntityCollection fills;
|
||||
|
||||
private:
|
||||
Layer *_layer;
|
||||
PrintRegion *_region;
|
||||
|
||||
LayerRegion(Layer *layer, PrintRegion *region);
|
||||
~LayerRegion();
|
||||
};
|
||||
|
||||
|
||||
typedef std::vector<LayerRegion*> LayerRegionPtrs;
|
||||
|
||||
class Layer {
|
||||
friend class PrintObject;
|
||||
|
||||
public:
|
||||
int id();
|
||||
PrintObject* object();
|
||||
|
||||
Layer *upper_layer;
|
||||
Layer *lower_layer;
|
||||
LayerRegionPtrs regions;
|
||||
bool slicing_errors;
|
||||
coordf_t slice_z; // Z used for slicing in unscaled coordinates
|
||||
coordf_t print_z; // Z used for printing in unscaled coordinates
|
||||
coordf_t height; // layer height in unscaled coordinates
|
||||
|
||||
// collection of expolygons generated by slicing the original geometry;
|
||||
// also known as 'islands' (all regions and surface types are merged here)
|
||||
ExPolygonCollection slices;
|
||||
|
||||
|
||||
size_t region_count();
|
||||
LayerRegion* get_region(int idx);
|
||||
LayerRegion* add_region(PrintRegion* print_region);
|
||||
|
||||
protected:
|
||||
int _id; // sequential number of layer, 0-based
|
||||
PrintObject *_object;
|
||||
|
||||
|
||||
Layer(int id, PrintObject *object, coordf_t height, coordf_t print_z,
|
||||
coordf_t slice_z);
|
||||
virtual ~Layer();
|
||||
|
||||
void clear_regions();
|
||||
void delete_region(int idx);
|
||||
};
|
||||
|
||||
|
||||
class SupportLayer : public Layer {
|
||||
friend class PrintObject;
|
||||
|
||||
public:
|
||||
ExPolygonCollection support_islands;
|
||||
ExtrusionEntityCollection support_fills;
|
||||
ExtrusionEntityCollection support_interface_fills;
|
||||
|
||||
protected:
|
||||
SupportLayer(int id, PrintObject *object, coordf_t height, coordf_t print_z,
|
||||
coordf_t slice_z);
|
||||
virtual ~SupportLayer();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Geometry.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include <algorithm>
|
||||
|
@ -38,7 +39,7 @@ Line::translate(double x, double y)
|
|||
}
|
||||
|
||||
void
|
||||
Line::rotate(double angle, Point* center)
|
||||
Line::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
this->a.rotate(angle, center);
|
||||
this->b.rotate(angle, center);
|
||||
|
@ -53,7 +54,7 @@ Line::reverse()
|
|||
double
|
||||
Line::length() const
|
||||
{
|
||||
return this->a.distance_to(&(this->b));
|
||||
return this->a.distance_to(this->b);
|
||||
}
|
||||
|
||||
Point*
|
||||
|
@ -82,15 +83,15 @@ Line::point_at(double distance) const
|
|||
}
|
||||
|
||||
bool
|
||||
Line::coincides_with(const Line* line) const
|
||||
Line::coincides_with(const Line &line) const
|
||||
{
|
||||
return this->a.coincides_with(&line->a) && this->b.coincides_with(&line->b);
|
||||
return this->a.coincides_with(line.a) && this->b.coincides_with(line.b);
|
||||
}
|
||||
|
||||
double
|
||||
Line::distance_to(const Point* point) const
|
||||
Line::distance_to(const Point &point) const
|
||||
{
|
||||
return point->distance_to(this);
|
||||
return point.distance_to(*this);
|
||||
}
|
||||
|
||||
double
|
||||
|
@ -108,6 +109,16 @@ Line::direction() const
|
|||
: atan2;
|
||||
}
|
||||
|
||||
bool
|
||||
Line::parallel_to(double angle) const {
|
||||
return Slic3r::Geometry::directions_parallel(this->direction(), angle);
|
||||
}
|
||||
|
||||
bool
|
||||
Line::parallel_to(const Line &line) const {
|
||||
return this->parallel_to(line.direction());
|
||||
}
|
||||
|
||||
Vector
|
||||
Line::vector() const
|
||||
{
|
||||
|
@ -115,6 +126,9 @@ Line::vector() const
|
|||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
|
||||
REGISTER_CLASS(Line, "Line");
|
||||
|
||||
void
|
||||
Line::from_SV(SV* line_sv)
|
||||
{
|
||||
|
@ -127,8 +141,8 @@ void
|
|||
Line::from_SV_check(SV* line_sv)
|
||||
{
|
||||
if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) {
|
||||
if (!sv_isa(line_sv, "Slic3r::Line") && !sv_isa(line_sv, "Slic3r::Line::Ref"))
|
||||
CONFESS("Not a valid Slic3r::Line object");
|
||||
if (!sv_isa(line_sv, perl_class_name(this)) && !sv_isa(line_sv, perl_class_name_ref(this)))
|
||||
CONFESS("Not a valid %s object", perl_class_name(this));
|
||||
*this = *(Line*)SvIV((SV*)SvRV( line_sv ));
|
||||
} else {
|
||||
this->from_SV(line_sv);
|
||||
|
@ -140,31 +154,12 @@ Line::to_AV() {
|
|||
AV* av = newAV();
|
||||
av_extend(av, 1);
|
||||
|
||||
SV* sv = newSV(0);
|
||||
sv_setref_pv( sv, "Slic3r::Point::Ref", &(this->a) );
|
||||
av_store(av, 0, sv);
|
||||
|
||||
sv = newSV(0);
|
||||
sv_setref_pv( sv, "Slic3r::Point::Ref", &(this->b) );
|
||||
av_store(av, 1, sv);
|
||||
av_store(av, 0, perl_to_SV_ref(this->a));
|
||||
av_store(av, 1, perl_to_SV_ref(this->b));
|
||||
|
||||
return newRV_noinc((SV*)av);
|
||||
}
|
||||
|
||||
SV*
|
||||
Line::to_SV_ref() {
|
||||
SV* sv = newSV(0);
|
||||
sv_setref_pv( sv, "Slic3r::Line::Ref", this );
|
||||
return sv;
|
||||
}
|
||||
|
||||
SV*
|
||||
Line::to_SV_clone_ref() const {
|
||||
SV* sv = newSV(0);
|
||||
sv_setref_pv( sv, "Slic3r::Line", new Line(*this) );
|
||||
return sv;
|
||||
}
|
||||
|
||||
SV*
|
||||
Line::to_SV_pureperl() const {
|
||||
AV* av = newAV();
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include <myinit.h>
|
||||
#include "Point.hpp"
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -21,14 +20,16 @@ class Line
|
|||
operator Polyline() const;
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, Point* center);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
void reverse();
|
||||
double length() const;
|
||||
Point* midpoint() const;
|
||||
void point_at(double distance, Point* point) const;
|
||||
Point point_at(double distance) const;
|
||||
bool coincides_with(const Line* line) const;
|
||||
double distance_to(const Point* point) const;
|
||||
bool coincides_with(const Line &line) const;
|
||||
double distance_to(const Point &point) const;
|
||||
bool parallel_to(double angle) const;
|
||||
bool parallel_to(const Line &line) const;
|
||||
double atan2_() const;
|
||||
double direction() const;
|
||||
Vector vector() const;
|
||||
|
@ -37,8 +38,6 @@ class Line
|
|||
void from_SV(SV* line_sv);
|
||||
void from_SV_check(SV* line_sv);
|
||||
SV* to_AV();
|
||||
SV* to_SV_ref();
|
||||
SV* to_SV_clone_ref() const;
|
||||
SV* to_SV_pureperl() const;
|
||||
#endif
|
||||
};
|
||||
|
@ -48,6 +47,7 @@ typedef std::vector<Line> Lines;
|
|||
}
|
||||
|
||||
// start Boost
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
namespace boost { namespace polygon {
|
||||
template <>
|
||||
struct geometry_concept<Line> { typedef segment_concept type; };
|
||||
|
|
|
@ -0,0 +1,367 @@
|
|||
#include "Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Model::Model() {}
|
||||
|
||||
Model::Model(const Model &other)
|
||||
{
|
||||
// copy materials
|
||||
for (ModelMaterialMap::const_iterator i = other.materials.begin(); i != other.materials.end(); ++i)
|
||||
this->add_material(i->first, *i->second);
|
||||
|
||||
// copy objects
|
||||
this->objects.reserve(other.objects.size());
|
||||
for (ModelObjectPtrs::const_iterator i = other.objects.begin(); i != other.objects.end(); ++i)
|
||||
this->add_object(**i);
|
||||
}
|
||||
|
||||
Model& Model::operator= (Model other)
|
||||
{
|
||||
this->swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
Model::swap(Model &other)
|
||||
{
|
||||
std::swap(this->materials, other.materials);
|
||||
std::swap(this->objects, other.objects);
|
||||
}
|
||||
|
||||
Model::~Model()
|
||||
{
|
||||
this->clear_objects();
|
||||
this->clear_materials();
|
||||
}
|
||||
|
||||
ModelObject*
|
||||
Model::add_object()
|
||||
{
|
||||
ModelObject* new_object = new ModelObject(this);
|
||||
this->objects.push_back(new_object);
|
||||
return new_object;
|
||||
}
|
||||
|
||||
ModelObject*
|
||||
Model::add_object(const ModelObject &other)
|
||||
{
|
||||
ModelObject* new_object = new ModelObject(this, other);
|
||||
this->objects.push_back(new_object);
|
||||
return new_object;
|
||||
}
|
||||
|
||||
void
|
||||
Model::delete_object(size_t idx)
|
||||
{
|
||||
ModelObjectPtrs::iterator i = this->objects.begin() + idx;
|
||||
delete *i;
|
||||
this->objects.erase(i);
|
||||
}
|
||||
|
||||
void
|
||||
Model::clear_objects()
|
||||
{
|
||||
// int instead of size_t because it can be -1 when vector is empty
|
||||
for (int i = this->objects.size()-1; i >= 0; --i)
|
||||
this->delete_object(i);
|
||||
}
|
||||
|
||||
void
|
||||
Model::delete_material(t_model_material_id material_id)
|
||||
{
|
||||
ModelMaterialMap::iterator i = this->materials.find(material_id);
|
||||
if (i != this->materials.end()) {
|
||||
delete i->second;
|
||||
this->materials.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Model::clear_materials()
|
||||
{
|
||||
while (!this->materials.empty())
|
||||
this->delete_material( this->materials.begin()->first );
|
||||
}
|
||||
|
||||
ModelMaterial*
|
||||
Model::add_material(t_model_material_id material_id)
|
||||
{
|
||||
ModelMaterial* material = this->get_material(material_id);
|
||||
if (material == NULL) {
|
||||
material = this->materials[material_id] = new ModelMaterial(this);
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
ModelMaterial*
|
||||
Model::add_material(t_model_material_id material_id, const ModelMaterial &other)
|
||||
{
|
||||
// delete existing material if any
|
||||
ModelMaterial* material = this->get_material(material_id);
|
||||
if (material != NULL) {
|
||||
delete material;
|
||||
}
|
||||
|
||||
// set new material
|
||||
material = new ModelMaterial(this, other);
|
||||
this->materials[material_id] = material;
|
||||
return material;
|
||||
}
|
||||
|
||||
ModelMaterial*
|
||||
Model::get_material(t_model_material_id material_id)
|
||||
{
|
||||
ModelMaterialMap::iterator i = this->materials.find(material_id);
|
||||
if (i == this->materials.end()) {
|
||||
return NULL;
|
||||
} else {
|
||||
return i->second;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void
|
||||
Model::duplicate_objects_grid(unsigned int x, unsigned int y, coordf_t distance)
|
||||
{
|
||||
if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects";
|
||||
if (this->objects.empty()) throw "No objects!";
|
||||
|
||||
ModelObject* object = this->objects.front();
|
||||
object->clear_instances();
|
||||
|
||||
BoundingBoxf3 bb;
|
||||
object->bounding_box(&bb);
|
||||
Sizef3 size = bb.size();
|
||||
|
||||
for (unsigned int x_copy = 1; x_copy <= x; ++x_copy) {
|
||||
for (unsigned int y_copy = 1; y_copy <= y; ++y_copy) {
|
||||
ModelInstance* instance = object->add_instance();
|
||||
instance->offset.x = (size.x + distance) * (x_copy-1);
|
||||
instance->offset.y = (size.y + distance) * (y_copy-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
bool
|
||||
Model::has_objects_with_no_instances() const
|
||||
{
|
||||
for (ModelObjectPtrs::const_iterator i = this->objects.begin();
|
||||
i != this->objects.end(); ++i)
|
||||
{
|
||||
if ((*i)->instances.empty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(Model, "Model");
|
||||
#endif
|
||||
|
||||
|
||||
ModelMaterial::ModelMaterial(Model *model) : model(model) {}
|
||||
ModelMaterial::ModelMaterial(Model *model, const ModelMaterial &other)
|
||||
: model(model), config(other.config), attributes(other.attributes)
|
||||
{}
|
||||
|
||||
void
|
||||
ModelMaterial::apply(const t_model_material_attributes &attributes)
|
||||
{
|
||||
this->attributes.insert(attributes.begin(), attributes.end());
|
||||
}
|
||||
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ModelMaterial, "Model::Material");
|
||||
#endif
|
||||
|
||||
|
||||
ModelObject::ModelObject(Model *model)
|
||||
: model(model)
|
||||
{}
|
||||
|
||||
ModelObject::ModelObject(Model *model, const ModelObject &other)
|
||||
: model(model),
|
||||
input_file(other.input_file),
|
||||
instances(),
|
||||
volumes(),
|
||||
config(other.config),
|
||||
layer_height_ranges(other.layer_height_ranges),
|
||||
origin_translation(other.origin_translation),
|
||||
_bounding_box(other._bounding_box),
|
||||
_bounding_box_valid(other._bounding_box_valid)
|
||||
{
|
||||
|
||||
this->volumes.reserve(other.volumes.size());
|
||||
for (ModelVolumePtrs::const_iterator i = other.volumes.begin(); i != other.volumes.end(); ++i)
|
||||
this->add_volume(**i);
|
||||
|
||||
this->instances.reserve(other.instances.size());
|
||||
for (ModelInstancePtrs::const_iterator i = other.instances.begin(); i != other.instances.end(); ++i)
|
||||
this->add_instance(**i);
|
||||
}
|
||||
|
||||
ModelObject& ModelObject::operator= (ModelObject other)
|
||||
{
|
||||
this->swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::swap(ModelObject &other)
|
||||
{
|
||||
std::swap(this->input_file, other.input_file);
|
||||
std::swap(this->instances, other.instances);
|
||||
std::swap(this->volumes, other.volumes);
|
||||
std::swap(this->config, other.config);
|
||||
std::swap(this->layer_height_ranges, other.layer_height_ranges);
|
||||
std::swap(this->origin_translation, other.origin_translation);
|
||||
std::swap(this->_bounding_box, other._bounding_box);
|
||||
std::swap(this->_bounding_box_valid, other._bounding_box_valid);
|
||||
}
|
||||
|
||||
ModelObject::~ModelObject()
|
||||
{
|
||||
this->clear_volumes();
|
||||
this->clear_instances();
|
||||
}
|
||||
|
||||
ModelVolume*
|
||||
ModelObject::add_volume(const TriangleMesh &mesh)
|
||||
{
|
||||
ModelVolume* v = new ModelVolume(this, mesh);
|
||||
this->volumes.push_back(v);
|
||||
this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
||||
ModelVolume*
|
||||
ModelObject::add_volume(const ModelVolume &other)
|
||||
{
|
||||
ModelVolume* v = new ModelVolume(this, other);
|
||||
this->volumes.push_back(v);
|
||||
this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::delete_volume(size_t idx)
|
||||
{
|
||||
ModelVolumePtrs::iterator i = this->volumes.begin() + idx;
|
||||
delete *i;
|
||||
this->volumes.erase(i);
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::clear_volumes()
|
||||
{
|
||||
// int instead of size_t because it can be -1 when vector is empty
|
||||
for (int i = this->volumes.size()-1; i >= 0; --i)
|
||||
this->delete_volume(i);
|
||||
}
|
||||
|
||||
ModelInstance*
|
||||
ModelObject::add_instance()
|
||||
{
|
||||
ModelInstance* i = new ModelInstance(this);
|
||||
this->instances.push_back(i);
|
||||
this->invalidate_bounding_box();
|
||||
return i;
|
||||
}
|
||||
|
||||
ModelInstance*
|
||||
ModelObject::add_instance(const ModelInstance &other)
|
||||
{
|
||||
ModelInstance* i = new ModelInstance(this, other);
|
||||
this->instances.push_back(i);
|
||||
this->invalidate_bounding_box();
|
||||
return i;
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::delete_instance(size_t idx)
|
||||
{
|
||||
ModelInstancePtrs::iterator i = this->instances.begin() + idx;
|
||||
delete *i;
|
||||
this->instances.erase(i);
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::delete_last_instance()
|
||||
{
|
||||
this->delete_instance(this->instances.size() - 1);
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::clear_instances()
|
||||
{
|
||||
for (size_t i = 0; i < this->instances.size(); ++i)
|
||||
this->delete_instance(i);
|
||||
}
|
||||
|
||||
void
|
||||
ModelObject::invalidate_bounding_box()
|
||||
{
|
||||
this->_bounding_box_valid = false;
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ModelObject, "Model::Object");
|
||||
#endif
|
||||
|
||||
|
||||
ModelVolume::ModelVolume(ModelObject* object, const TriangleMesh &mesh)
|
||||
: object(object), mesh(mesh), modifier(false)
|
||||
{}
|
||||
|
||||
ModelVolume::ModelVolume(ModelObject* object, const ModelVolume &other)
|
||||
: object(object), mesh(other.mesh), modifier(other.modifier)
|
||||
{
|
||||
this->material_id(other.material_id());
|
||||
}
|
||||
|
||||
t_model_material_id
|
||||
ModelVolume::material_id() const
|
||||
{
|
||||
return this->_material_id;
|
||||
}
|
||||
|
||||
void
|
||||
ModelVolume::material_id(t_model_material_id material_id)
|
||||
{
|
||||
this->_material_id = material_id;
|
||||
|
||||
// ensure this->_material_id references an existing material
|
||||
(void)this->object->get_model()->add_material(material_id);
|
||||
}
|
||||
|
||||
ModelMaterial*
|
||||
ModelVolume::material() const
|
||||
{
|
||||
return this->object->get_model()->get_material(this->_material_id);
|
||||
}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ModelVolume, "Model::Volume");
|
||||
#endif
|
||||
|
||||
|
||||
ModelInstance::ModelInstance(ModelObject *object)
|
||||
: object(object), rotation(0), scaling_factor(1)
|
||||
{}
|
||||
|
||||
ModelInstance::ModelInstance(ModelObject *object, const ModelInstance &other)
|
||||
: object(object), rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset)
|
||||
{}
|
||||
|
||||
#ifdef SLIC3RXS
|
||||
REGISTER_CLASS(ModelInstance, "Model::Instance");
|
||||
#endif
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue