Compare commits

...

314 Commits

Author SHA1 Message Date
Vitaliy Filippov 0753f16e9e Limit flow for sparse infill... 2014-06-28 11:59:12 +04:00
Vitaliy Filippov 896bf15ea2 Include perimeters when propagating horizontal shells 2014-06-27 21:58:40 +04:00
Vitaliy Filippov e73d76e771 Fix BadDrawable crash that occurs when opening object settings dialog 2014-06-16 02:39:48 +04:00
Vitaliy Filippov 3d85730b4e Generate contact layer normally, without any additional "equally spaced circles" or something... O_o 2014-06-13 21:38:30 +04:00
Vitaliy Filippov f1b5b5c13b Draw perimeters around interface 2014-06-13 21:38:25 +04:00
Vitaliy Filippov 7b938b0002 Fix raft/support contact and printing with both raft and support 2014-06-13 17:38:29 +04:00
Vitaliy Filippov 54b80e1109 I do not know if it is correct, but I use M135 toolchange code for Sailfish & GPX 2014-06-13 17:35:45 +04:00
Vitaliy Filippov 87ac675d14 Print perimeter on first support layer 2014-06-13 17:35:45 +04:00
Vitaliy Filippov 207b55eec0 Don't affect top/bottom/bridge surfaces when propagating solid corners
of horizontal shells. Fixes issue alexrj/Slic3r#1852
2014-06-13 17:33:38 +04:00
Alessandro Ranellucci 97231327e0 Have Print::apply_config() return true if any step was invalidated 2014-06-12 09:29:26 +02:00
Alessandro Ranellucci 5e80d7a388 Readd use strict and use warnings 2014-06-12 09:23:10 +02:00
Alessandro Ranellucci 6194cbf530 Refactoring: new method in Flow for calculating spacing between extrusions having different width 2014-06-12 09:17:37 +02:00
Alessandro Ranellucci 8ee11b3239 Refactoring: keep height in Flow object and calculate spacing on demand 2014-06-12 01:00:13 +02:00
Alessandro Ranellucci 9bff6ccde7 Fixed tests after recent fixes to concave_points 2014-06-11 22:21:30 +02:00
Alessandro Ranellucci 89ff1a50b2 Removed debugging line 2014-06-11 22:20:59 +02:00
Alessandro Ranellucci 34750d47fe Move external_perimeters_first to PrintRegionConfig 2014-06-11 22:15:28 +02:00
Alessandro Ranellucci e158d1e612 Move bridge_flow_ratio to PrintRegionConfig 2014-06-11 22:10:33 +02:00
Alessandro Ranellucci 7eaba2d132 Move PrintConfigDef::build_def() to .cpp 2014-06-11 22:04:19 +02:00
Alessandro Ranellucci 5dcc1eab79 Bugfix: seam_position = aligned failed in certaim circumstances because of faulty concave points detection. Includes regression test 2014-06-11 21:58:41 +02:00
Alessandro Ranellucci 1674108bac Don't apply pillars to raft layers 2014-06-11 21:58:35 +02:00
Alessandro Ranellucci 82e2dd4e62 Bugfix: wrong number of raft layers in some situations due to a numerical error 2014-06-11 21:58:28 +02:00
Alessandro Ranellucci 1f497c0f7c Make support material generation idempotent 2014-06-11 18:02:51 +02:00
Alessandro Ranellucci 7ccfd2f024 More work on incremental slicing 2014-06-11 17:51:18 +02:00
Alessandro Ranellucci d2ca4c1b13 Some initial work for incremental slicing 2014-06-11 00:15:02 +02:00
Alessandro Ranellucci 5c54acf9ac Use ->ptr() for CoolingBuffer too 2014-06-10 16:17:34 +02:00
Alessandro Ranellucci 2d14ddb955 Enable step caching 2014-06-10 16:12:14 +02:00
Alessandro Ranellucci c5c069b9a0 Update MANIFEST 2014-06-10 16:03:43 +02:00
Alessandro Ranellucci 3f6360ee8f Finish porting Print classes to XS 2014-06-10 16:01:57 +02:00
Alessandro Ranellucci ba8148f4ad Merge branch 'printcpp' of github.com:sapir/Slic3r into sapir-printcpp
Conflicts:
	lib/Slic3r/GCode.pm
	lib/Slic3r/Print.pm
	lib/Slic3r/Print/Object.pm
	lib/Slic3r/Print/Region.pm
2014-06-10 14:46:40 +02:00
Alessandro Ranellucci e3e69251cb New XY Size Compensation option. #581 #56 2014-06-10 13:28:57 +02:00
Alessandro Ranellucci af1a47c461 Minor optimization 2014-06-10 00:54:37 +02:00
Alessandro Ranellucci 3599bd0bae Distinct extrusion width for external perimeters 2014-06-09 21:22:15 +02:00
Alessandro Ranellucci d1511f4a00 Merge remote-tracking branch 'origin/newplater' 2014-06-09 18:57:17 +02:00
Alessandro Ranellucci d56bb940e7 Bump dev version number 2014-06-09 18:55:59 +02:00
Alessandro Ranellucci 2f7443169b Merge branch 'stable' 2014-06-09 18:54:20 +02:00
Alessandro Ranellucci d8e1437014 Releasing 1.1.4 2014-06-05 18:07:03 +02:00
Alessandro Ranellucci 64853d5661 Bugfix: M73 was exceeding 100% when multiple objects were printed together. #1912 2014-06-05 16:24:47 +02:00
Alessandro Ranellucci 342513a4de Change: --save will now only save the specified options, without the defaults 2014-06-04 00:08:23 +02:00
Alessandro Ranellucci 28695c719c Fixed regression causing bridge flow to be incorrect when a manual default extrusion width was specified. Includes several regression tests. #2027 2014-06-03 23:59:03 +02:00
Alessandro Ranellucci 39b8ac80ee Ignore holes in object footprint when generating raft 2014-05-29 14:54:47 +02:00
Alessandro Ranellucci defe0a4279 Move 2D canvas to a distinct class for better isolation 2014-05-28 12:29:43 +02:00
Alessandro Ranellucci ce1e736b6b Bugfix: crash when skirt was contained in brim area. Includes regression test. #2058 2014-05-27 13:38:41 +02:00
Alessandro Ranellucci 98b8936ee2 Automatically disable retract_layer_change when using spiral_vase 2014-05-26 23:51:58 +02:00
Alessandro Ranellucci 2ac40f9547 Revert "Update Clipper to r468. #2028"
This reverts commit 6a6439576b.
2014-05-26 20:00:59 +02:00
Alessandro Ranellucci 6a6439576b Update Clipper to r468. #2028 2014-05-26 17:21:31 +02:00
Alessandro Ranellucci b02e459c4b Attempt to fix compilation issue 2014-05-26 17:13:24 +02:00
Alessandro Ranellucci fb4a971339 Bump version number 2014-05-26 17:12:14 +02:00
Alessandro Ranellucci 5ca81d699e Releasing 1.1.3 2014-05-26 16:45:40 +02:00
Alessandro Ranellucci 3d25b9030c Bugfix: movement between objects in sequential printing mode was going too far away. #2013 #2007 2014-05-26 15:19:13 +02:00
Alessandro Ranellucci 8290a006ed Bugfix: a lot of extra support material was generated when using both raft layers and support material. #2030 2014-05-26 14:09:42 +02:00
Alessandro Ranellucci e62672f787 Fix regression in Split from GUI. Might be related to #1998 2014-05-26 13:10:58 +02:00
Alessandro Ranellucci 147385203c Make XS compilation verbose since most users forget to include the build.log file when reporting issues. This also makes Travis-CI logs more useful 2014-05-26 12:50:59 +02:00
Alessandro Ranellucci 2bce8bb745 Bugfix: detect thin fill loops so that they can be started at the nearest point without unnecessary loops. #1990 2014-05-26 12:09:13 +02:00
Alessandro Ranellucci abdf6531f1 Bugfix: wrong logic for concave_points() and convex_points() 2014-05-26 11:50:42 +02:00
Alessandro Ranellucci 7ea09a0071 Bugfix: failure when exporting SVG from object containing multiple islands. Includes regression test. #2050 2014-05-26 11:13:53 +02:00
Alessandro Ranellucci 0decbbf910 Revert "Updated Clipper to r467 but it doesn't seem to fix #2028 yet"
This reverts commit a62457d6b5.
2014-05-25 23:27:26 +02:00
Alessandro Ranellucci 4e5f7d74ff Bugfix: wrong handling of large number of raft layers. #2041 2014-05-25 23:17:00 +02:00
Alessandro Ranellucci a62457d6b5 Updated Clipper to r467 but it doesn't seem to fix #2028 yet 2014-05-25 22:48:58 +02:00
Y. Sapir 19d18bdd84 Attempt to solve conflict with a3bd1b5 by adding id to PrintObject and using that as a hash key. 2014-05-25 03:00:29 +03:00
Y. Sapir 9247f21ff8 Add missing include "BoundingBox.hpp" to Polygon.xsp. 2014-05-25 00:55:13 +03:00
Y. Sapir d2e51242d9 Really fix total_used_filament / total_extruded_volume. 2014-05-25 00:53:47 +03:00
Y. Sapir 07dd799967 Fix inherited Layer::Support id and object accessors. 2014-05-25 00:50:49 +03:00
Y. Sapir b8b8c746d9 Make clear_regions and delete_region methods private (both Print & Layer), and remove from XSP. 2014-05-25 00:11:17 +03:00
Y. Sapir 85cbbed663 Set ISA manually for Slic3r::Layer::Support. 2014-05-24 23:50:46 +03:00
Y. Sapir 22b05cb187 Make read-only constructor parameters - private. 2014-05-24 23:50:45 +03:00
Y. Sapir 76738dc66b Remove some more Clone typemaps and add some whitespace. 2014-05-24 23:50:45 +03:00
Y. Sapir 0486a7f857 Try to fix _copies_shift scaling. 2014-05-24 23:50:45 +03:00
Y. Sapir db51e4693d Return NULL values from XS as undef. 2014-05-24 23:50:45 +03:00
Y. Sapir d06c300926 Add ::Ref classes for every missing C++ class (going by REGISTER_CLASS calls). 2014-05-24 23:50:44 +03:00
Y. Sapir 8da0bded1d Move Print object storage to C++. (along with its subobjects) 2014-05-24 23:50:29 +03:00
Alessandro Ranellucci 88a2e5c791 Typo seal -> seam :-) 2014-05-24 22:10:28 +02:00
Alessandro Ranellucci 3df2488eca Disable test until Clipper bug 96 is fixed #2028 2014-05-24 00:10:37 +02:00
Alessandro Ranellucci ed58f35fe5 Revert "Fix bad test". Test was actually correct. Clipper's still slightly buggy since it splits polylines
This reverts commit bf2af85da5.
2014-05-24 00:09:04 +02:00
Alessandro Ranellucci bf2af85da5 Fix bad test 2014-05-24 00:01:27 +02:00
Alessandro Ranellucci fcdb462abe Failing test cases for Clipper bug returning empty result set. #2028 2014-05-24 00:00:15 +02:00
Alessandro Ranellucci 70ceb853f1 Update Clipper to last trunk to fix a couple Clipper bugs causing empty intersection results and failure to process polylines with coinciding endpoints. This also caused crashed in some rare circumstances 2014-05-23 23:58:43 +02:00
Alessandro Ranellucci a3bd1b5302 New seal_position option that replaces randomize_start, start_perimeters_at_concave_points and start_perimeters_at_non_overhang. The two latter options are now always on by default. A new "Aligned" seal position value has been added, that forces starting points to be aligned when not randomized. #1741 #925 2014-05-22 19:34:49 +02:00
Alessandro Ranellucci c63bd8165d Fixed minor compilation issue 2014-05-22 13:47:30 +02:00
Alessandro Ranellucci f2c5e799b1 Enforce seam alignment and blend in spiral vase. #2023 2014-05-22 12:28:12 +02:00
Alessandro Ranellucci 254ab29a97 New Point::projection_onto() methods 2014-05-21 20:08:21 +02:00
Alessandro Ranellucci 874c7a6e8b One line missing for support_material_interface_speed declaration 2014-05-21 15:53:41 +02:00
Alessandro Ranellucci 5e6ff952df Fix more regressions in test suite regarding the workaround for Clipper bug 2014-05-21 15:45:16 +02:00
Alessandro Ranellucci 8ca352eb62 Separate speed option for support material interface. #2009 2014-05-21 15:21:20 +02:00
Alessandro Ranellucci 08279ec5d8 Bugfix: thin walls forming a closed loop had overlapping segments at their endpoints. #1948 #1875 2014-05-21 15:03:31 +02:00
Alessandro Ranellucci a8b6e32767 Gracefully handle loading config files having empty strings for multi-value options (like wipe). #2003 2014-05-21 11:38:42 +02:00
Alessandro Ranellucci 85b0a4376a Use last extruder's settings when adding new ones. #1997 2014-05-20 23:37:17 +02:00
Alessandro Ranellucci 0ba685f556 Fix regression causing config validation to be ignored when using the Export G-code button in plater. #2046 2014-05-20 23:29:43 +02:00
Alessandro Ranellucci 63d56c666b Include full config in G-code files. #2047 #2032 2014-05-20 23:25:12 +02:00
Alessandro Ranellucci a00f6c72ed Don't emit temperature commands if standby temperature delta is zero 2014-05-18 23:56:00 +02:00
Alessandro Ranellucci 038076e040 Bugfix: first layer extrusion width wasn't affecting infill. Includes regression test. #2042 2014-05-18 23:36:30 +02:00
Alessandro Ranellucci 3f29a9292a Remove unused method SkeinPanel::init_print() 2014-05-18 18:47:16 +02:00
Alessandro Ranellucci 0ddcefe956 Use support material speed for skirt and brim instead of perimeter speed so that perimeter speed can be set on a per-region basis 2014-05-18 17:02:18 +02:00
Alessandro Ranellucci 58ffaca2df Bugfix: ooze_prevention brought the extruder too far. Includes regression test 2014-05-15 19:23:57 +02:00
Alessandro Ranellucci ac0a91a162 Move many speed settings to PrintRegionConfig 2014-05-15 18:53:05 +02:00
Alessandro Ranellucci ad99b2a0fd Fixed one more regression introduced with Model refactoring. Includes regression test 2014-05-15 16:37:18 +02:00
Alessandro Ranellucci dd1183f19a Some fixed after the recent Slic3r::GCode refactoring 2014-05-15 15:54:16 +02:00
Alessandro Ranellucci ee2c1c6127 Refactored the Slic3r::GCode logic for speeds 2014-05-13 08:34:21 +02:00
Alessandro Ranellucci 5d12a03b82 Move Detect Bridging Perimeters to region config 2014-05-12 23:02:33 +02:00
Alessandro Ranellucci 59f0e76da1 Distinct extrusion role for support material interface 2014-05-12 22:59:49 +02:00
Alessandro Ranellucci baefefc50d One method call not changed after Model refactoring 2014-05-12 22:42:50 +02:00
Alessandro Ranellucci 69002b8ea2 No tests were covering randomize-start, which was not working anymore after recent ExtrusionLoop refactoring. #2028 2014-05-12 21:49:17 +02:00
Alessandro Ranellucci 47940a712d Bugfix: previous brim and skirt were not cleared when disabled after first G-code export. #2024 2014-05-10 20:54:12 +02:00
Alessandro Ranellucci a02a7f1a0f Apply workaround for buggy GCC 4.7.2 #1965 2014-05-10 17:03:05 +02:00
Alessandro Ranellucci 27c73f5983 Some fixes after the recent Model refactoring 2014-05-10 16:59:17 +02:00
Alessandro Ranellucci 65b7d27def Draft for binary representation of G-code commands 2014-05-10 15:08:49 +02:00
Alessandro Ranellucci 7ba08c90cf Refactoring to Model API for making it stricter and safer 2014-05-09 14:24:35 +02:00
Alessandro Ranellucci bc023c2d51 Remove new/delete keywords from ClipperUtils.cpp 2014-05-08 15:00:49 +02:00
Alessandro Ranellucci 76a8ec3d9e Replace to_SV_ref() and to_SV_clone_ref() with templated glue functions 2014-05-08 14:52:48 +02:00
Alessandro Ranellucci f76e2c2222 Several minor fixes to Model 2014-05-08 13:33:43 +02:00
Alessandro Ranellucci 6e207d3830 Merge branch 'sapir-modelcpp' 2014-05-08 11:13:21 +02:00
Alessandro Ranellucci c37ef2f18b New semantics for ExtrusionLoop objects. Early processing of perimeter overhangs for paralellizing such work and making G-code export lighter. Lots of refactoring. This should fix a number of minor bugs, including reversals of perimeter overhangs. 2014-05-08 11:07:37 +02:00
Alessandro Ranellucci d2d885fc53 Turn ExtrusionLoop into a collection of polylines. Includes some changes to the Polygon API to avoid returning newly allocatd objects 2014-05-07 12:02:09 +02:00
Alessandro Ranellucci 931f3114c0 Fix clear_objects() and clear_volumes() 2014-05-07 01:23:14 +02:00
Alessandro Ranellucci 093d1cbe2f Don't transfer ownership of Model to Print::Object 2014-05-07 01:11:49 +02:00
Alessandro Ranellucci 13af16ea24 Use Pointf for origin_translation and pass const refs whenever possible 2014-05-07 00:58:29 +02:00
Alessandro Ranellucci 54a199919b Removed StringMap 2014-05-07 00:22:56 +02:00
Y. Sapir 05b2993769 Translate Model class' storage to C++.
Some code copied from xs-model branch.

Also:
* Generate ::Ref classes programatically.
* Add separate __REGISTER_CLASS macro
    (for use where forward declaration won't work, i.e. typedefs)
2014-05-05 16:30:19 +03:00
Alessandro Ranellucci c72dc13d7e Add perl 5.18 to Travis CI builds 2014-05-04 18:03:28 +02:00
Alessandro Ranellucci b3c9285e7a Bump version number 2014-05-02 22:20:20 +02:00
Alessandro Ranellucci e16ef7a569 Releasing 1.1.2 2014-05-02 19:48:26 +02:00
Alessandro Ranellucci cb1527f7ef Refactoring: move direction math into a single function. Includes some fixes and adjustments 2014-05-02 18:48:11 +02:00
Alessandro Ranellucci 8240f71d07 Merge pull request #2001 from ledvinap/fix-2000
Add missing perlglue.hpp to xsp files
2014-05-02 14:48:39 +02:00
Petr Ledvina 028ef3a868 Add missing perlglue.hpp to xsp files
perlglue was missing in some xsp files. If file ordering ib XS.c is changed, header file could be missing. Maybe solves #2000
2014-05-02 14:08:44 +02:00
Alessandro Ranellucci 31113b8a78 Typo in 0a88492 2014-05-02 13:31:41 +02:00
Alessandro Ranellucci 0a88492fdc Use a more robust parallelism detection 2014-05-02 13:26:59 +02:00
Alessandro Ranellucci fce669dea0 Don't return empty halves after cutting 2014-05-02 13:13:47 +02:00
Alessandro Ranellucci fc3a73afe8 Make OpenGL::Array thread-safe, thus fixing the crash after the cut dialog is closed in threaded perl 2014-05-02 13:13:13 +02:00
Alessandro Ranellucci c81ffc391d Tentative fix for unreproduced compilation error reported 2014-05-02 11:51:07 +02:00
Alessandro Ranellucci 5f88135074 Adjust XY distance of the contact loop 2014-05-02 10:49:14 +02:00
Alessandro Ranellucci 2a3923934e Update timestamp before the Export G-code dialog is shown. #1995 2014-05-01 21:42:12 +02:00
Alessandro Ranellucci 4311f30739 Fixed one typo in BridgeDetector and some improvements to support material 2014-05-01 19:11:53 +02:00
Alessandro Ranellucci edeb0a90dd Use poly2tri for triangulation. This fixes some cases where polyPartition couldn't triangulate successfully. Reported as issue #9 in polyPartition repository. Tested with MotorHalter_0.stl cut at 1.2 2014-05-01 12:07:11 +02:00
Alessandro Ranellucci 60f640f100 Remove self-intersections before supplying polygon data to polyPartition and rename triangulate2() to triangulate_pp() 2014-05-01 10:37:38 +02:00
Alessandro Ranellucci 9734a40647 Fix Polygon::contains_point() overflowing on Windows. #1950 2014-04-30 16:55:20 +02:00
Alessandro Ranellucci 93c1ae92c9 Added failing test case for contains_point() not working on Windows. #1950 2014-04-30 16:44:25 +02:00
Alessandro Ranellucci 010b71e9d4 Improvements to avoid numerical issues in bridge detection and bridge exclusion 2014-04-30 15:16:15 +02:00
Alessandro Ranellucci 581376bf75 Rename EMC to LinuxCNC 2014-04-30 15:05:32 +02:00
Alessandro Ranellucci 83435aebb4 Make sure spiral vase mode is not enabled for multi-island layers. #1938
Conflicts:

	lib/Slic3r/GCode/Layer.pm
	t/shells.t
2014-04-30 14:42:08 +02:00
Alessandro Ranellucci 4680bbdfe2 Make sure spiral vase mode is not enabled for multi-island layers. #1938 2014-04-30 12:23:07 +02:00
Alessandro Ranellucci 8ded268e7e Take into account that $layerm->perimeters also contains ExtrusionPath objects. #1991 2014-04-30 10:47:31 +02:00
Alessandro Ranellucci 1667d1826d Put continuous base flange under pillars support material 2014-04-30 01:51:19 +02:00
Alessandro Ranellucci 1d10cd3da6 Also remove bridged perimeters area from support material 2014-04-30 01:20:18 +02:00
Alessandro Ranellucci 3e3cc4171c TODO about bridges and support material 2014-04-29 23:40:52 +02:00
Alessandro Ranellucci ef2296dc8d Make sure interface and base generation honor the gap between support material and object 2014-04-29 23:29:52 +02:00
Alessandro Ranellucci ed1c6d1aaa Removed debugging line 2014-04-29 23:17:01 +02:00
Alessandro Ranellucci 98e40d3fe4 Store width and height in ExtrusionEntity objects for debugging purposes 2014-04-29 23:16:16 +02:00
Petr Ledvina 78a08e0665 Commented out test (breakpoint) line 2014-04-29 22:55:10 +02:00
Alessandro Ranellucci a31b2e6ca2 Stricter implementation of the overhang detection. Includes unit tests 2014-04-29 19:58:58 +02:00
Alessandro Ranellucci caf7b3f97e Prune bridge angles 2014-04-29 18:36:50 +02:00
Alessandro Ranellucci 7a9dec3720 One more change to multi-value placeholders: indices start from 0. #1899 2014-04-29 17:12:00 +02:00
Alessandro Ranellucci a4b6075600 Fixed regression and ambiguity about multiple-value placeholders like [first_layer_temperature_1]. Includes several unit tests covering regression. #1899 2014-04-29 17:06:31 +02:00
Alessandro Ranellucci 913ab54a2b Bugfix: gap fill was not inserted in the correct order before leaving island. Includes regression test. #1907 2014-04-29 15:25:14 +02:00
Alessandro Ranellucci 0b0ec7be37 Fixed regression causing bad loading of multi-extruder values in GUI. #1984 2014-04-28 22:31:29 +02:00
Alessandro Ranellucci f7421053cc Improvements to bridge angle detection: use coverage test for all cases (including two-sided bridges and C-shaped bridges) and check for all angles equal to directions of bridge sides 2014-04-28 22:15:40 +02:00
Alessandro Ranellucci 24571612c7 Some minor changes to the newly-ported Extruder class 2014-04-28 22:02:34 +02:00
Alessandro Ranellucci 4c330b6c59 Merge pull request #1968 from sapir/extrudercpp
Translate Extruder class to C++
2014-04-28 21:45:53 +02:00
Alessandro Ranellucci 19fdf9b184 Test triangulate2() instead of the faulty triangulate() 2014-04-28 20:19:26 +02:00
Alessandro Ranellucci 2720000a17 New get_trapezoids() implementation. Maybe heavier but it doesn't fail with some versions of GCC like the one provided by Boost.Polygon. #1965 2014-04-28 20:14:20 +02:00
Alessandro Ranellucci d4e97d17d5 Use -O1 with GCC 4.7.0-4.7.2. #1965 2014-04-28 19:24:19 +02:00
Y. Sapir ad03a88733 Add xsp wrapper for Pointf class. 2014-04-28 01:13:50 +03:00
Y. Sapir e005ff32c4 Replace Extruder::config accessor with Perl wrapper, with C++ option accessors. 2014-04-28 00:49:05 +03:00
Y. Sapir 71b0b211ec Constify ConfigOptionVector::get_at. 2014-04-28 00:19:03 +03:00
Y. Sapir d824de6168 Keep pointer to PrintConfig instead of copying it. 2014-04-28 00:03:22 +03:00
Y. Sapir c2b249d059 Add REGISTER_CLASS call for Extruder class. 2014-04-27 23:53:28 +03:00
Y. Sapir 2565d80679 Replace multitude of Extruder fields with querying a PrintConfig object by id. 2014-04-27 23:43:44 +03:00
Y. Sapir b319dc9361 Correctly convert extruder_offset to Point object before passing to C++ code. 2014-04-27 23:43:43 +03:00
Y. Sapir 717d099ae1 Add XS Extruder files to manifest. 2014-04-27 23:43:43 +03:00
Y. Sapir 04fad70cbf Make C++ gcode generator use C++ Extruder object directly. 2014-04-27 23:43:43 +03:00
Y. Sapir 66d23a2416 Convert Extruder object (mostly) to C++ with lots of fields. 2014-04-27 23:42:59 +03:00
Alessandro Ranellucci 2738a304e8 Update credits. Mention Gary Hodgson, Petr Ledvinap, Y. Sapir 2014-04-27 20:04:02 +02:00
Alessandro Ranellucci 5296867a38 Merge pull request #1983 from ledvinap/feature-xs-types-final
Implement type checking for XS objects
2014-04-27 19:58:34 +02:00
Petr Ledvina 115aa6885f Implement type checking for XS objects
Type handling is mainly done using templates.
Template Slic3r::ClassTraits is used to store info about exported types (perl class name). Currently only perl class name and refference name is used.
Template values are initialized by REGISTER_CLASS macro. This macro is used in .cpp file of class ( it needs to be used exactly for each type).

Ref<type> class is used to return value as perl reference. Operator overloading is used to make c++ and XSpp happy, only pointer value should be possible to return.

Clone<type> class is used to return copy of value ( using new and copy constructor). Copy is created on assigment, this should be probably improved (memory leak on multiple assignments).
It is overloaded to be able to return type, type* and type&.

Typechecking in ExtrusionEntityCollection updated to check all passed types.
2014-04-27 19:38:56 +02:00
Alessandro Ranellucci e68b6b6f4c Merge pull request #1980 from sapir/output-dir-msg
Add dir --output msg to README, too.
2014-04-27 14:39:22 +02:00
Y. Sapir 85bca96982 Add dir --output msg to README, too. 2014-04-27 14:21:59 +03:00
Alessandro Ranellucci 611159ae20 Merge pull request #1979 from sapir/output-dir-msg
Add message about directories as parameters for --output.
2014-04-27 13:12:27 +02:00
Y. Sapir 0224e72f8c Add message about directories as parameters for --output. 2014-04-27 13:57:36 +03:00
Alessandro Ranellucci fa81147fee Merge pull request #1976 from sapir/clipperperms
Remove execute permissions for clipper source files.
2014-04-27 12:42:38 +02:00
Y. Sapir 87eee8eea3 Remove execute permissions for clipper source files. 2014-04-27 13:19:11 +03:00
Alessandro Ranellucci ce9cf4d964 Don't put supports under bridges and potential bridges 2014-04-26 17:19:50 +02:00
Alessandro Ranellucci 129b6651f6 More correct test description 2014-04-26 16:09:00 +02:00
Alessandro Ranellucci 148c773319 Exclude support from bridged areas 2014-04-26 16:07:43 +02:00
Alessandro Ranellucci 7e3024d6ad Explain that infill_every_layers can be set to arbitrarily large values in the tooltip 2014-04-25 20:29:37 +02:00
Alessandro Ranellucci 073b7fca1f One more fix after recent fix to default extrusion width 2014-04-25 20:19:02 +02:00
Alessandro Ranellucci 9ee3868f92 Removed debugging line left 2014-04-25 20:05:08 +02:00
Alessandro Ranellucci 6780e930cb Update the [timestamp] placeholder(s) at every G-code export. #1957 2014-04-25 19:47:13 +02:00
Alessandro Ranellucci f7ffc48397 Bump version number 2014-04-25 19:44:04 +02:00
Alessandro Ranellucci 7ac0095018 Fix regression causing default extrusion width to be ignored. #1958 2014-04-25 19:39:27 +02:00
Alessandro Ranellucci 924cb2e558 Update MANIFEST. #1969 2014-04-25 19:14:03 +02:00
Alessandro Ranellucci f6897a346a Fix islands not being ordered efficiently with the logic that completes each of them before moving to the next one (which is now the default behavior). #1137 2014-04-25 19:11:17 +02:00
Alessandro Ranellucci 2a2ba15665 Use a slider for the cutting GUI 2014-04-25 18:36:08 +02:00
Alessandro Ranellucci 8db4913b04 New option for rotating the lower part resulting from the cut so that the flat surface lies on the print bed 2014-04-25 17:50:03 +02:00
Alessandro Ranellucci 4f17c2b7d1 First working implementation of a GUI for cutting 2014-04-25 17:14:39 +02:00
Alessandro Ranellucci 882a98ed44 Initial work for a GUI for the cut feature 2014-04-25 14:54:08 +02:00
Alessandro Ranellucci 334bc4c581 Fix cut() test 2014-04-25 13:27:45 +02:00
Alessandro Ranellucci 4f5d9ca795 Finished --cut implementation 2014-04-25 12:40:21 +02:00
Alessandro Ranellucci fe1691c151 New --cut feature 2014-04-25 10:20:30 +02:00
Alessandro Ranellucci 37c7b958d4 New ExPolygon::triangulate() method 2014-04-24 17:06:16 +02:00
Alessandro Ranellucci bb0ce3cccd New Polygon::triangulate_convex() method 2014-04-24 16:59:36 +02:00
Alessandro Ranellucci ca4d4211c9 Refactored signatures of many C++ methods for more efficient and safer style. Includes a bugfix for Point::nearest_point() which was returning a pointer to freed memory. #1961 2014-04-24 16:40:10 +02:00
Alessandro Ranellucci 6201aacf88 New coverage detection for bridges. Includes implementation of ExPolygon::get_trapezoids() 2014-04-24 13:44:39 +02:00
Alessandro Ranellucci d458a7c4d2 Merge pull request #1956 from ledvinap/fix-bridges
Fix bridges
2014-04-23 12:09:02 +02:00
Petr Ledvina 4c1ffecfc4 Fix test to check for correct direction and accept result close to 180 degrees. 2014-04-23 11:40:37 +02:00
Petr Ledvina 802ebfb0d6 Fix bridge detector
Test line direction is changed to reflect 0=east angle system
Test patern rotation direction is reversed - $angle represents infill direction
Angle test is changed to avoid returning negative value (this does happen otherwise - adding increments to $angle
creates ~ 1e-15 rounding error)
2014-04-23 11:35:58 +02:00
Alessandro Ranellucci 766d743b0b Merge pull request #1954 from willmmiles/fix-arctest-1918
Avoid integer overflow in arc test.  Fixes #1918
2014-04-23 11:18:33 +02:00
Will Miles fbce8e6730 Avoid integer overflow in arc test. Fixes #1918 2014-04-22 23:01:01 -04:00
Alessandro Ranellucci 1fc76b73a4 Merge pull request #1953 from ledvinap/fix-clipper-1834
Apply bug fix from upstream clipper. Fixes #1834
2014-04-22 15:39:42 +02:00
Petr Ledvina c30040b691 Apply bug fix from upstream clipper
Clipper fix in commit [r463] for bug repport #92 (http://sourceforge.net/p/polyclipping/code/463/tree//trunk/cpp/clipper.cpp?diff=504b9404fd48f873331e913b:462)
is applied here.
2014-04-22 15:19:35 +02:00
Alessandro Ranellucci 138c169634 Releasing 1.0.1 2014-04-22 12:03:19 +02:00
Alessandro Ranellucci 370df56f73 Bugfix: version check caused crash 2014-04-22 12:01:47 +02:00
Alessandro Ranellucci 3a49dccf69 Bugfix: version check caused crash 2014-04-22 11:59:01 +02:00
Alessandro Ranellucci c87a21f304 Releasing 1.1.1 2014-04-22 11:20:58 +02:00
Alessandro Ranellucci 9989ebaabd Refactored the BridgeDetector class to expose a cleaner API and make it stateful 2014-04-22 00:59:14 +02:00
Alessandro Ranellucci bc101bd93e Assume bridge angle is stored in radians in infill generation. #1917 2014-04-21 23:21:15 +02:00
Alessandro Ranellucci f7e8a99078 More tests and related fixes to bridge detection. #1917 2014-04-21 20:44:21 +02:00
Alessandro Ranellucci c4bfe64fb8 Test coverage for retract_layer_change 2014-04-19 19:28:43 +02:00
Alessandro Ranellucci 7041ebdd22 Workaround for upstream module bug causing command line option parsing for multi-boolean options like retract-layer-change and wipe 2014-04-19 19:14:41 +02:00
Alessandro Ranellucci 5d10ef514f Workaround wxWidgets not allowing SetValue() inside a EVT_COMBOBOX handler on Windows. #1865 2014-04-19 18:42:52 +02:00
Alessandro Ranellucci 8018eac0f0 Set min temperature to 0 2014-04-19 18:32:04 +02:00
Alessandro Ranellucci a49e786b04 Handle EVT_TEXT on wxSpinCtrl too 2014-04-19 18:29:40 +02:00
Alessandro Ranellucci 6e6fb427fb Fixes for the last commit about kill focus. #1873 2014-04-19 18:16:34 +02:00
Alessandro Ranellucci 10a5a061d4 Bugfix: wxWidgets on Windows needs Skip() on kill focus to prevent nasty focus bugs. #1873 2014-04-19 18:05:01 +02:00
Alessandro Ranellucci 46191bf254 Upgrade ExtUtils::ParseXS dependency because 3.18_04 was removed from CPAN 2014-04-19 17:04:16 +02:00
Alessandro Ranellucci a248c98192 Fixed regression when concentric infill was used as solid infill. Includes regression test. #1650 2014-04-19 16:53:37 +02:00
Alessandro Ranellucci c3bb8a69db Bugfix: crash when extending an array option by 2+ positions. #1908 2014-04-19 16:38:28 +02:00
Alessandro Ranellucci f6e99f1467 Bugfix: [filament_preset] was not replaced in filename when using Quick Slice. #1870 2014-04-19 12:10:15 +02:00
Alessandro Ranellucci f0ff653018 Clarify that we need single configs instead of bundles 2014-04-19 12:01:11 +02:00
Alessandro Ranellucci 1e5dcd8154 Always do one island at time instead of doing that only when avoid_crossing_perimeters is enabled. #1907 2014-04-19 11:57:01 +02:00
Alessandro Ranellucci f51921a11a Default extruder wasn't applied to perimeter extruder. Includes regression test. #1868 2014-04-19 11:43:41 +02:00
Alessandro Ranellucci 0c50ab5323 Removed duplicated method added to Slic3r::Line in caf20664cd. #1945 2014-04-19 10:52:12 +02:00
Alessandro Ranellucci caf20664cd Bugfix: ExPolygon::contains_line() was called with a Polyline object but didn't throw any error. Thanks ledvinap #1945 2014-04-19 01:26:21 +02:00
Alessandro Ranellucci 62e19469bc Fixed regression in --infill-only-where-needed. Includes a minimal test suite for such feature. #1871
Conflicts:

	t/fill.t
2014-04-18 21:48:32 +02:00
Alessandro Ranellucci a0133ba093 Fixed regression in --infill-only-where-needed. Includes a minimal test suite for such feature. #1871 2014-04-18 21:40:27 +02:00
Alessandro Ranellucci 15628a90ed Regression test for numerical issues causing thin gaps between perimeters and infill. #1803 2014-04-18 17:30:35 +02:00
Alessandro Ranellucci 93f4fe05e1 Better logic for gap fill: don't subtract from fill surfaces those gaps that medial axis was unable to fill. Thanks ledvinap for the idea and proof of concept implementation. Includes a tolerance factor for avoiding being too strict when collapsing loops (allow some overlap if that helps avoiding triggering medial axis). #1803 2014-04-15 01:41:40 +02:00
Alessandro Ranellucci e02d33bbce Remove weak test. #1803 2014-04-15 00:57:43 +02:00
Alessandro Ranellucci 43ffb7d3e8 Bugfix: sometimes a tiny gap was left between infill and perimeters. Includes regression test. #1803 2014-04-15 00:22:31 +02:00
Alessandro Ranellucci 859bf46401 Bugfix: sometimes a tiny gap was left between infill and perimeters. Includes regression test. #1803 2014-04-11 23:10:14 +02:00
Alessandro Ranellucci 7569836b3c Merge pull request #1928 from ledvinap/pull-sudo
add --sudo flag
2014-04-11 19:57:54 +02:00
Alessandro Ranellucci f9661b02a6 Bugfix in utils/pdf-slices.pl, wrong shift caused slices to be outside the media box 2014-04-11 19:55:15 +02:00
Petr Ledvina c81d26b960 add --sudo flag
This adds --sudo flag to Build.PL. This flag is passed to cpanm,
so it is possible to build Slicer on linux without beeing root (build files
are owned by user)
2014-04-10 16:02:48 +02:00
Alessandro Ranellucci 2ac3b1fba9 Support the percent format for fill_density introduced in 1.1.x (forward compatibility). #1880 2014-04-09 18:43:48 +02:00
Alessandro Ranellucci 26bdbf0210 Test bridges when infill flow is larger than perimeter flow. #1917 2014-04-09 12:09:25 +02:00
Alessandro Ranellucci 8e5ca0ab76 Fixed a couple typos 2014-04-09 00:36:13 +02:00
Alessandro Ranellucci 627f23d5fe Make angle increment (i.e. angle resolution) configurable 2014-04-08 16:53:32 +02:00
Alessandro Ranellucci 20ca6cea05 Test Clipper's buggy behavior with polylines having coinciding endpoints 2014-04-08 15:26:31 +02:00
Alessandro Ranellucci 38f6e3b643 More unit tests for bridge angle detection 2014-04-08 15:18:37 +02:00
Alessandro Ranellucci 9be57f750d Some fixes to bridge direction detection. Includes regression tests 2014-04-08 14:51:55 +02:00
Alessandro Ranellucci 23e6abff49 Added first unit test for bridge direction 2014-04-08 13:08:42 +02:00
Alessandro Ranellucci 119778caa9 Refactoring: move bridge angle detection to a separate class for easier unit testing 2014-04-07 23:18:11 +02:00
Alessandro Ranellucci 93a7d87fc6 Bugfix: pillars support material crashed when no overhangs were detected 2014-04-07 16:59:22 +02:00
Alessandro Ranellucci 1c020eda78 Merge pull request #1911 from sapir/speed
Rewriting the extrusion path gcode generation in C++
2014-04-07 09:38:21 +02:00
Alessandro Ranellucci 33ade1328f Fix regression causing profile names to be listed in random order. #1864 2014-04-06 22:45:40 +02:00
Alessandro Ranellucci e4709068b9 Some more unit tests for arc fitting 2014-04-06 22:32:53 +02:00
Alessandro Ranellucci 1d35701f99 Incomplete work on arc fitting. Results are still incomplete. More unit tests are needed 2014-04-06 22:10:34 +02:00
Alessandro Ranellucci c7a96a3113 Merge pull request #1881 from sapir/master
Disable some calls to on_presets_changed to speed up switching profiles
2014-04-06 12:52:05 +02:00
Alessandro Ranellucci 20df1023a6 Fixed broken command line scaling. Regression test was in previous commit. #1889 2014-04-05 11:22:05 +02:00
Alessandro Ranellucci e9e23119fc Failing test for broken command line scaling. #1889 2014-04-05 11:21:26 +02:00
Alessandro Ranellucci fc4ac5ded4 Re-enable the threads config option. #1882 2014-04-05 10:58:03 +02:00
Alessandro Ranellucci d6d6a51e0d Fix for incomplete extrusion on solid surfaces 2014-04-05 10:54:24 +02:00
Alessandro Ranellucci d2a6194960 Fix the 'extruder' behavior in CLI and test that Print::Simple accepts an incomplete DynamicConfig object 2014-04-05 10:18:00 +02:00
Alessandro Ranellucci 6f3844c1ba Fix crash when using multiple skirts with a single, non-zero, extruder. Includes regression test 2014-04-05 10:09:05 +02:00
Alessandro Ranellucci a6dd68a2a6 Bugfix: crash when setting number of extruders. #1876 2014-04-05 09:54:43 +02:00
Alessandro Ranellucci b68c55fec0 Bugfix: the Y coordinate of Point config fields was not correctly validated and lead to a crash when entering non-numeric values. Includes regression test. #1906 2014-04-05 09:40:24 +02:00
Alessandro Ranellucci f308a46cd5 Bump version number 2014-04-02 20:25:05 +02:00
Alessandro Ranellucci 45559f87f3 Save the vector used for aligning object volumes to origin and apply it to additional parts added from the object part panel in order to make them coincide 2014-04-02 20:23:26 +02:00
Y. Sapir 4deeff995a Move extrusion path gcode generation to C++ for speed. 2014-04-01 21:11:43 +03:00
Y. Sapir f56206cac3 Precache gcode_comments flag before path loop. 2014-04-01 21:11:43 +03:00
Y. Sapir 9f71ea15cf Precompute X and Y offsets before path loop. 2014-04-01 21:11:43 +03:00
Y. Sapir 576d02a20d Make set_dirty ignore changes that don't do anything. 2014-03-30 00:23:35 +03:00
Y. Sapir 09d7d9b034 Remove call to on_presets_changed from Tab Page's on_change.
(set_dirty is called which handles it anyway.)
2014-03-30 00:21:57 +03:00
Alessandro Ranellucci 017158c877 Update year to 2014 in about dialog 2014-03-27 15:33:17 +01:00
Alessandro Ranellucci 97d9c9f5e7 Releasing 1.0.0 2014-03-27 11:18:48 +01:00
Alessandro Ranellucci 630004d156 Bugfix: wrong inwards moves were calculated for 2+ copies because ExtrusionLoop objects were modified in place. Includes regression test. #1842 2014-03-24 19:56:18 +01:00
Alessandro Ranellucci 2a52a318fe Error in previous commit: Bugfix: prevent crash when user entered Unicode characters in notes. #1590 2014-03-24 17:02:46 +01:00
Alessandro Ranellucci bf1fd0cf9a Bugfix: prevent crash when user entered Unicode characters in notes. #1590 2014-03-24 16:38:20 +01:00
Alessandro Ranellucci 96ad37f6e0 Bugfix: holes were not sorted using the nearest-neighbor search, resulting in inefficient paths. #1785 2014-03-23 19:44:14 +01:00
Alessandro Ranellucci 280a1a369e Bugfix: binary STL export produced corrupt results on Windows. #1814 2014-03-23 19:17:02 +01:00
Alessandro Ranellucci 2d9c399d96 Bugfix: incomplete slicing when a horizontal surface was tangent to the slicing plane and it shared an edge with an adjacent volume. #1672
Conflicts:

	xs/src/TriangleMesh.cpp
2014-03-02 22:18:30 +01:00
Alessandro Ranellucci a712284afb Add a (failing) test for slicing at the same height of a horizontal surface attached to a volume. In this case, the loop isn't completed. #1672
Conflicts:

	xs/t/01_trianglemesh.t
2014-03-02 01:06:59 +01:00
Alessandro Ranellucci fd6b78f7f2 Removed debugging line 2014-03-02 00:43:54 +01:00
Alessandro Ranellucci 8c4a0f23df Better pruning of thin walls to avoid unwanted extra extrusions. Includes regression test. #1794 2014-03-02 00:29:51 +01:00
Alessandro Ranellucci c1a5cddcd2 Use bridge speed for first solid layer above sparse infill. Includes unit test. #1792 2014-03-01 20:34:22 +01:00
Alessandro Ranellucci 71322cc49d Bugfix: crash when using non-consecutive extruders. #1808 2014-02-26 11:36:37 +01:00
Alessandro Ranellucci 9ec7ae08ad Fixed regression when using --infill-only-where-needed. Includes regression test. #1721 2014-02-16 12:44:08 +01:00
Alessandro Ranellucci 35095ff12e Releasing 1.0.0RC3 2014-02-15 16:55:35 +01:00
Alessandro Ranellucci 0c7a1777de Fixed spiral vase regressions. Includes regression tests. #1773 2014-02-13 16:06:52 +01:00
Alessandro Ranellucci 1ec6494d65 Regression test and incomplete fix for bug affecting wrong spiral vase output. #1773 2014-02-13 01:00:17 +01:00
Alessandro Ranellucci ee82e56a4f Bugfix: crashing when concentric infill produced very narrow loops. #1740 2014-02-10 16:02:47 +01:00
Alessandro Ranellucci d099118ca7 Better implementation of previous commit (7ce49fc2b2) 2014-02-10 13:06:42 +01:00
Alessandro Ranellucci 7ce49fc2b2 Perform additional checks before merging solid surfaces (i.e. take flow and fill pattern into account) 2014-02-10 01:27:36 +01:00
Alessandro Ranellucci 7be042567d Bugfix: when processing with fill_density = 0, top/bottom solid shells were missing regions thinner than 3 * extrusion width. Includes regression test. #1602 2014-02-07 01:48:47 +01:00
Alessandro Ranellucci cfbbb539a5 Bugfix: fatal error in the rare case that a bridge has no anchor points. #1607 2014-01-15 00:20:09 +01:00
Alessandro Ranellucci 4da71e8f13 Fix tests in stable after stronger type checking was applied 2014-01-14 12:49:07 +01:00
Alessandro Ranellucci a64f7aa8df Bugfix: lack of strong type checking when passing arrayref objects to XS caused random lack of infill. Now added strong type checking for all XS entities. Also fixes a potential issue with bridges caused by the same error. #1652 2014-01-12 00:46:23 +01:00
Alessandro Ranellucci 130e8dd8e7 Bugfix: incorrect number of raft layers was generated when too few were requested. #1678 2014-01-11 23:16:33 +01:00
Alessandro Ranellucci 26f0fab27a Fix bug in regression infill causing bad clipping at very low layer heights. Includes regression test. #1669 2014-01-11 21:27:37 +01:00
Alessandro Ranellucci b43ead06fe Fix regression introduced by max width check 2014-01-11 18:41:44 +01:00
Alessandro Ranellucci 5c02bfd310 Bugfix: ambiguous semantics of the layers_count() method caused M73 to go beyond 100%. #1670 2014-01-11 17:26:53 +01:00
Alessandro Ranellucci a32f548a23 Put a hard limit on manually configured extrusion widths (10 * the largest nozzle diameter configured) to prevent confusion when a bad value is entered. #1691 2014-01-11 14:30:34 +01:00
Alessandro Ranellucci bb50dfb9ba Test that absolute_E is positive at the end of print (and document that it accounts for the final retraction) 2014-01-11 14:24:40 +01:00
Alessandro Ranellucci 468935c177 Bump version number 2014-01-02 19:22:27 +01:00
Alessandro Ranellucci 713bdd8055 Bugfix: overflow causing full object facets reversal when STL file has large coordinates. #1666 2014-01-02 18:50:54 +01:00
Alessandro Ranellucci 6afc01c3b9 Releasing 1.0.0RC2 2013-12-28 17:55:45 +01:00
Alessandro Ranellucci 354e4ce841 Add Module::Build::WithXSpp to prerequisites. #1640 2013-12-28 16:02:50 +01:00
Alessandro Ranellucci 907e72830f Fix random crashes on 32-bit. Thank you Valgrind 2013-12-28 15:33:47 +01:00
Alessandro Ranellucci 6d8d166eff Backport the GLUquadricObjPtr declaration to prevent errors during Quick Slice. #1636 2013-12-25 12:37:03 +01:00
Alessandro Ranellucci eb5ca20449 Fixed regression causing incomplete gap fill when all configured perimeters were successfully generated. #1589 2013-12-23 21:22:32 +01:00
Alessandro Ranellucci b551852392 Fix regression causing thin walls to come out as zigzag paths. #1573 #1575 2013-12-23 20:36:16 +01:00
Alessandro Ranellucci 74f2f45554 Fix regression preventing raft from being generated for the entire object footprint. #1614 #1567 2013-12-23 20:12:39 +01:00
Alessandro Ranellucci c180a2de57 Bugfix: fan wasn't turned on for bridges when vibration limit or another internal post-processor was enabled. Includes regression test. #1533 2013-12-23 01:19:02 +01:00
Alessandro Ranellucci e403dc16ae Rewrite avoid_crossing_perimeters() to fix a regression and get better performance by choosing regular points along contours. #1531 2013-12-22 18:47:39 +01:00
Alessandro Ranellucci ca16567ba9 Fixed wrong commit f495136. Thanks pdbogen for the report. #1623 2013-12-18 10:20:36 +01:00
Alessandro Ranellucci 7e8841805c Fixed fatal error about missing method thin_walls(). #1607 #1615 2013-12-17 16:15:25 +01:00
Alessandro Ranellucci 69ed69179b Handle read_from_file() exceptions in GUI. #1619 2013-12-17 16:09:07 +01:00
179 changed files with 14621 additions and 4221 deletions

View File

@ -2,8 +2,9 @@ language: perl
install: true
script: perl ./Build.PL
perl:
- "5.14"
- "5.12"
- "5.14"
- "5.18"
branches:
only:
- master

View File

@ -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";
}

View File

@ -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

View File

@ -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

View File

@ -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 {};

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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,
);
}

View File

@ -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 {

View File

@ -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);

View File

@ -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->{$_};

View File

@ -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),
);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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)};

View File

@ -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} ],
)})
) {

View File

@ -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;

View File

@ -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";
}

View File

@ -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;
}

View File

@ -47,12 +47,13 @@ sub new {
'<html>' .
'<body bgcolor="#ffffff" link="#808080">' .
'<font color="#808080">' .
'Copyright &copy; 2011-2013 Alessandro Ranellucci. All rights reserved. ' .
'Copyright &copy; 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>';

View File

@ -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);
}
}

View File

@ -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';

284
lib/Slic3r/GUI/Plater/2D.pm Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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
);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) = @_;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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),
);
}

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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],

109
t/bridges.t Normal file
View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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';
}

79
t/flow.t Normal file
View File

@ -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__

60
t/gaps.t Normal file
View File

@ -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__

View File

@ -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__

View File

@ -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';
}

View File

@ -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__

View File

@ -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__

File diff suppressed because one or more lines are too long

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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
View File

@ -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__

View File

@ -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';
}

View File

@ -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};

View File

@ -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
}
}

View File

@ -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 => {

View File

@ -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

View File

@ -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;

View File

@ -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
}

View File

@ -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);

View File

@ -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);
/////////////////

View File

@ -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);

View File

@ -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;
};

View File

@ -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 &center)
{
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 {

View File

@ -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 &center);
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

View File

@ -32,7 +32,7 @@ ExPolygonCollection::translate(double x, double y)
}
void
ExPolygonCollection::rotate(double angle, Point* center)
ExPolygonCollection::rotate(double angle, const Point &center)
{
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
}

View File

@ -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 &center);
bool contains_point(const Point &point) const;
void simplify(double tolerance);
void convex_hull(Polygon* hull) const;
};

128
xs/src/Extruder.cpp Normal file
View File

@ -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
}

46
xs/src/Extruder.hpp Normal file
View File

@ -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

View File

@ -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
}

View File

@ -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;
};
}

View File

@ -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
}

View File

@ -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;
};
}

View File

@ -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
}

View File

@ -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);
};

35
xs/src/GCode.hpp Normal file
View File

@ -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

View File

@ -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();

View File

@ -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:

132
xs/src/Layer.cpp Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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 &center)
{
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();

View File

@ -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 &center);
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; };

367
xs/src/Model.cpp Normal file
View File

@ -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