Slic3r-1.2.9/000077500000000000000000000000001254023100400126515ustar00rootroot00000000000000Slic3r-1.2.9/.gitignore000066400000000000000000000001401254023100400146340ustar00rootroot00000000000000Build Build.bat MYMETA.json MYMETA.yml _build blib xs/buildtmp *.o MANIFEST.bak xs/MANIFEST.bak Slic3r-1.2.9/.travis.yml000066400000000000000000000002111254023100400147540ustar00rootroot00000000000000language: perl install: true script: perl ./Build.PL perl: - "5.14" - "5.18" - "5.20" branches: only: - master - stable Slic3r-1.2.9/Build.PL000066400000000000000000000130161254023100400141460ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Config; use File::Spec; my %prereqs = qw( Encode 0 Encode::Locale 1.05 ExtUtils::MakeMaker 6.80 ExtUtils::ParseXS 3.22 File::Basename 0 File::Spec 0 Getopt::Long 0 Math::PlanePath 53 Module::Build::WithXSpp 0.14 Moo 1.003001 POSIX 0 Scalar::Util 0 Test::More 0 Thread::Semaphore 0 IO::Scalar 0 threads 1.96 Time::HiRes 0 Unicode::Normalize 0 ); my %recommends = qw( Class::XSAccessor 0 XML::SAX::ExpatXS 0 Test::Harness 0 ); my $sudo = grep { $_ eq '--sudo' } @ARGV; my $gui = grep { $_ eq '--gui' } @ARGV; my $xs_only = grep { $_ eq '--xs' } @ARGV; if ($gui) { %prereqs = qw( Class::Accessor 0 Wx 0.9918 Socket 2.016 ); %recommends = qw( Growl::GNTP 0.15 Wx::GLCanvas 0 OpenGL 0 LWP::UserAgent 0 Net::Bonjour 0 ); if ($^O eq 'MSWin32') { # we need an up-to-date Win32::API because older aren't thread-safe (GH #2517) $prereqs{'Win32::API'} = 0.79; } } elsif ($xs_only) { %prereqs = %recommends = (); } my @missing_prereqs = (); if ($ENV{SLIC3R_NO_AUTO}) { foreach my $module (sort keys %prereqs) { my $version = $prereqs{$module}; next if eval "use $module $version; 1"; push @missing_prereqs, $module if exists $prereqs{$module}; print "Missing prerequisite $module $version\n"; } foreach my $module (sort keys %recommends) { my $version = $recommends{$module}; next if eval "use $module $version; 1"; print "Missing optional $module $version\n"; } } else { my @try = ( $ENV{CPANM} // (), File::Spec->catfile($Config{sitebin}, 'cpanm'), File::Spec->catfile($Config{installscript}, 'cpanm'), ); my $cpanm; foreach my $path (@try) { if (-e $path) { # don't use -x because it fails on Windows $cpanm = $path; last; } } if (!$cpanm) { if ($^O =~ /^(?:darwin|linux)$/ && system(qw(which cpanm)) == 0) { $cpanm = 'cpanm'; } } die <<'EOF' cpanm was not found. Please install it before running this script. There are several ways to install cpanm, try one of these: apt-get install cpanminus curl -L http://cpanmin.us | perl - --sudo App::cpanminus cpan App::cpanminus If it is installed in a non-standard location you can do: CPANM=/path/to/cpanm perl Build.PL 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, @cpanm_args, 'App::cpanminus'; # install the Windows-compatible Math::Libm if ($^O eq 'MSWin32' && !eval "use Math::Libm; 1") { 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, @cpanm_args); push @cmd, '--notest', if $module eq 'OpenGL'; # temporary workaround for upstream bug in test push @cmd, "$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; if (!-d $mingw) { print "Could not find the MinGW directory at $mingw; skipping XML::SAX::ExpatXS (only needed for faster parsing of AMF files)\n"; } else { push @cmd, sprintf('--configure-args="EXPATLIBPATH=%s\lib EXPATINCPATH=%s\include"', $mingw, $mingw); } } my $res = system @cmd; if ($res != 0) { if (exists $prereqs{$module}) { push @missing_prereqs, $module; } else { printf "Don't worry, this module is optional.\n"; } } } if (!$gui) { # clean xs directory before reinstalling, to make sure Build is called # with current perl binary if (-e './xs/Build') { if ($^O eq 'MSWin32') { system '.\xs\Build', 'distclean'; } else { system './xs/Build', 'distclean'; } } my $res = system $cpanm, @cpanm_args, '--reinstall', '--verbose', './xs'; if ($res != 0) { die "The XS/C++ code failed to compile, aborting\n"; } } } if (@missing_prereqs) { printf "The following prerequisites failed to install: %s\n", join(', ', @missing_prereqs); exit 1; } elsif (!$gui) { eval "use App::Prove; 1" or die "Failed to load App::Prove"; my $res = App::Prove->new->run ? 0 : 1; if ($res == 0) { print "If you also want to use the GUI you can now run `perl Build.PL --gui` to install the required modules.\n"; } else { print "Some tests failed. Please report the failure to the author!\n"; } exit $res; } __END__ Slic3r-1.2.9/CONTRIBUTING.md000066400000000000000000000032031254023100400151000ustar00rootroot00000000000000Did you encounter an issue with using Slic3r? Fear not! This guide will help you to write a good bug report in just a few, simple steps. There is a good chance that the issue, you have encountered, is already reported. Please check the [list of reported issues](https://github.com/alexrj/Slic3r/issues) before creating a new issue report. If you find an existing issue report, feel free to add further information to that report. If possible, please include the following information when [reporting an issue](https://github.com/alexrj/Slic3r/issues/new): * Slic3r version (See the about dialog for the version number. If running from git, please include the git commit ID from `git rev-parse HEAD` also.) * 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 - please don't export a bundle) * Expected result * Actual result * Any error messages * If the issue is related to G-code generation, please include the following: * STL, OBJ or AMF input file (please make sure the input file is not broken, e.g. non-manifold, before reporting a bug) * a screenshot of the G-code layer with the issue (e.g. using [Pronterface](https://github.com/kliment/Printrun)) Please make sure only to include one issue per report. If you encounter multiple, unrelated issues, please report them as such. Simon Tatham has written an excellent on article on [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) which is well worth reading, although it is not specific to Slic3r. Slic3r-1.2.9/README.md000066400000000000000000000534601254023100400141400ustar00rootroot00000000000000_Q: Oh cool, a new RepRap slicer?_ A: Yes. Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.png?branch=master)](https://travis-ci.org/alexrj/Slic3r) ====== Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for 3D printers. It's compatible with any modern printer based on the RepRap toolchain, including all those based on the Marlin, Sprinter and Repetier firmware. It also works with Mach3, LinuxCNC and Machinekit controllers. See the [project homepage](http://slic3r.org/) at slic3r.org and the [manual](http://manual.slic3r.org/) for more information. ### What language is it written in? The core geometric algorithms and data structures are written in C++, and Perl is used for high-level flow abstraction, GUI and testing. If you're wondering why Perl, see http://xkcd.com/224/ The C++ API is public and its use in other projects is encouraged. The goal is to make Slic3r fully modular so that any part of its logic can be used separately. ### What are Slic3r's main features? Key features are: * **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required * complete **command-line interface** to use it with no GUI * multi-material **(multiple extruders)** object printing * multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.) * ability to plate **multiple objects having distinct print settings** * **multithread** processing * **STL auto-repair** (tolerance for broken models) * wide automated unit testing Other major features are: * combine infill every 'n' perimeters layer to speed up printing * **3D preview** (including multi-material files) * **multiple layer heights** in a single print * **spiral vase** mode for bumpless vases * fine-grained configuration of speed, acceleration, extrusion width * several infill patterns including honeycomb, spirals, Hilbert curves * support material, raft, brim, skirt * **standby temperature** and automatic wiping for multi-extruder printing * customizable **G-code macros** and output filename with variable placeholders * support for **post-processing scripts** * **cooling logic** controlling fan speed and dynamic print speed ### How to install? You can just download a precompiled package from [slic3r.org](http://slic3r.org/); it will run without the need for any dependency. If you want to compile the source yourself just do the following (checkout [slic3r.org](http://slic3r.org/download) for more details): ``` $ git clone https://github.com/alexrj/Slic3r.git $ cd Slic3r $ perl Build.PL --sudo $ perl Build.PL --sudo --gui $ ./slic3r.pl ``` ### Can I help? Sure! Drop me a line at aar@cpan.org. You can also find me in #reprap and in #slic3r on FreeNode with the nickname _Sound_. Before sending patches and pull requests contact me to discuss your proposed changes: this way we'll ensure nobody wastes their time and no conflicts arise in development. ### What's Slic3r license? Slic3r is licensed under the _GNU Affero General Public License, version 3_. The author is Alessandro Ranellucci. The [Silk icon set](http://www.famfamfam.com/lab/icons/silk/) used in Slic3r is licensed under the _Creative Commons Attribution 3.0 License_. The author of the Silk icon set is Mark James. ### How can I invoke slic3r.pl using the command line? Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... --help Output this usage screen and exit --version Output the version of Slic3r and exit --save Save configuration to the specified file --load Load configuration from the specified file. It can be used more than once to load options from multiple files. -o, --output 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.) 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 _fixed.obj --cut Cut given input files at given Z (relative) and export them as _upper.stl and _lower.stl --split Split the shells contained in given STL file into several STL files --info Output information about the supplied file(s) and exit -j, --threads Number of threads to use (1+, default: 2) GUI options: --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file Output options: --output-filename-format Output file name format; all config options enclosed in brackets will be replaced by their values, as well as [input_filename_base] and [input_filename] (default: [input_filename_base].gcode) --post-process Generated G-code will be processed with the supplied script; call this more than once to process through multiple scripts. --export-svg Export a SVG file containing slices instead of G-code. -m, --merge If multiple files are supplied, they will be composed into a single print rather than processed individually. Printer options: --nozzle-diameter Diameter of nozzle in mm (default: 0.5) --print-center Coordinates in mm of the point to center the print around (default: 100,100) --z-offset Additional height in mm to add to vertical coordinates (+/-, default: 0) --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/machinekit/no-extrusion, default: reprap) --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no) --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported by all firmwares) --gcode-comments Make G-code verbose by adding comments (default: no) --vibration-limit Limit the frequency of moves on X and Y axes (Hz, set zero to disable; default: 0) --pressure-advance Adjust pressure using the experimental advance algorithm (K constant, set zero to disable; default: 0) Filament options: --filament-diameter Diameter in mm of your raw filament (default: 3) --extrusion-multiplier Change this to alter the amount of plastic extruded. There should be very little need to change this value, which is only useful to compensate for filament packing (default: 1) --temperature Extrusion temperature in degree Celsius, set 0 to disable (default: 200) --first-layer-temperature Extrusion temperature for the first layer, in degree Celsius, set 0 to disable (default: same as --temperature) --bed-temperature Heated bed temperature in degree Celsius, set 0 to disable (default: 0) --first-layer-bed-temperature Heated bed temperature for the first layer, in degree Celsius, set 0 to disable (default: same as --bed-temperature) Speed options: --travel-speed Speed of non-print moves in mm/s (default: 130) --perimeter-speed Speed of print moves for perimeters in mm/s (default: 30) --small-perimeter-speed Speed of print moves for small perimeters in mm/s or % over perimeter speed (default: 30) --external-perimeter-speed Speed of print moves for the external perimeter in mm/s or % over perimeter speed (default: 70%) --infill-speed Speed of print moves in mm/s (default: 60) --solid-infill-speed Speed of print moves for solid surfaces in mm/s or % over infill speed (default: 60) --top-solid-infill-speed Speed of print moves for top surfaces in mm/s or % over solid infill speed (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 value or as a percentage over normal speeds (default: 30%) Acceleration options: --perimeter-acceleration Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero to disable; default: 0) --infill-acceleration Overrides firmware's default acceleration for infill. (mm/s^2, set zero to disable; default: 0) --bridge-acceleration Overrides firmware's default acceleration for bridges. (mm/s^2, set zero to disable; default: 0) --first-layer-acceleration Overrides firmware's default acceleration for first layer. (mm/s^2, set zero to disable; default: 0) --default-acceleration Acceleration will be reset to this value after the specific settings above have been applied. (mm/s^2, set zero to disable; default: 0) Accuracy options: --layer-height Layer height in mm (default: 0.3) --first-layer-height Layer height for first layer (mm or %, default: 0.35) --infill-every-layers Infill every N layers (default: 1) --solid-infill-every-layers Force a solid layer every N layers (default: 0) Print options: --perimeters Number of perimeters/horizontal skins (range: 0+, default: 3) --top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: 3) --bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: 3) --solid-layers Shortcut for setting the two options above at once --fill-density Infill density (range: 0%-100%, default: 40%) --fill-angle Infill angle in degrees (range: 0-90, default: 45) --fill-pattern Pattern to use to fill non-solid layers (default: honeycomb) --solid-fill-pattern Pattern to use to fill solid layers (default: rectilinear) --start-gcode Load initial G-code from the supplied file. This will overwrite the default command (home all axes [G28]). --end-gcode Load final G-code from the supplied file. This will overwrite the default commands (turn off temperature [M104 S0], home X axis [G28 X], disable motors [M84]). --before-layer-gcode Load before-layer-change G-code from the supplied file (default: nothing). --layer-gcode Load after-layer-change G-code from the supplied file (default: nothing). --toolchange-gcode Load tool-change G-code from the supplied file (default: nothing). --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) --only-retract-when-crossing-perimeters Disable retraction when travelling between infill paths inside the same island. (default: no) --solid-infill-below-area Force solid infill when a region has a smaller area than this threshold (mm^2, default: 70) --infill-only-where-needed Only infill under ceilings (default: no) --infill-first Make infill before perimeters (default: no) 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) --thin-walls Detect single-width walls (default: yes) --overhangs Experimental option to use bridge flow, speed and fan for overhangs (default: yes) Support material options: --support-material Generate support material for overhangs --support-material-threshold Overhang threshold angle (range: 0-90, set 0 for automatic detection, default: 0) --support-material-pattern Pattern to use for support material (default: honeycomb) --support-material-spacing Spacing between pattern lines (mm, default: 2.5) --support-material-angle Support material angle in degrees (range: 0-90, default: 0) --support-material-contact-distance Vertical distance between object and support material (0+, default: 0.2) --support-material-interface-layers Number of perpendicular layers between support material and object (0+, default: 3) --support-material-interface-spacing Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: 0) --raft-layers Number of layers to raise the printed objects by (range: 0+, default: 0) --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) --retract-speed Speed for retraction in mm/s (default: 30) --retract-restart-extra Additional amount of filament in mm to push after compensating retraction (default: 0) --retract-before-travel Only retract before travel moves of this length in mm (default: 2) --retract-lift Lift Z by the given distance in mm when retracting (default: 0) --retract-layer-change Enforce a retraction before each Z move (default: no) --wipe Wipe the nozzle while doing a retraction (default: no) Retraction options for multi-extruder setups: --retract-length-toolchange Length of retraction in mm when disabling tool (default: 10) --retract-restart-extra-toolchange Additional amount of filament in mm to push after switching tool (default: 0) Cooling options: --cooling Enable fan and cooling control --min-fan-speed Minimum fan speed (default: 35%) --max-fan-speed Maximum fan speed (default: 100%) --bridge-fan-speed Fan speed to use when bridging (default: 100%) --fan-below-layer-time Enable fan if layer print time is below this approximate number of seconds (default: 60) --slowdown-below-layer-time Slow down if layer print time is below this approximate number of seconds (default: 30) --min-print-speed Minimum print speed (mm/s, default: 10) --disable-fan-first-layers Disable fan for the first N layers (default: 1) --fan-always-on Keep fan always on at min fan speed, even for layers that don't need cooling Skirt options: --skirts Number of skirts to draw (0+, default: 1) --skirt-distance Distance in mm between innermost skirt and object (default: 6) --skirt-height Height of skirts to draw (expressed in layers, 0+, default: 1) --min-skirt-length Generate no less than the number of loops required to consume this length of filament on the first layer, for each extruder (mm, 0+, default: 0) --brim-width Width of the brim that will get added to each object to help adhesion (mm, default: 0) Transform options: --scale Factor for scaling input object (default: 1) --rotate Rotation angle in degrees (0-360, default: 0) --duplicate Number of items with auto-arrange (1+, default: 1) --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 starting the next one; watch out for extruder collisions (default: no) --extruder-clearance-radius Radius in mm above which extruder won't collide with anything (default: 20) --extruder-clearance-height Maximum vertical extruder depth; i.e. vertical distance from extruder tip and carriage bottom (default: 20) Miscellaneous options: --notes Notes to be added as comments to the output file --resolution Minimum detail resolution (mm, set zero for full resolution, default: 0) Flow options (advanced): --extrusion-width Set extrusion width manually; it accepts either an absolute value in mm (like 0.65) or a percentage over layer height (like 200%) --first-layer-extrusion-width 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 Set a different extrusion width for solid infill --top-infill-extrusion-width Set a different extrusion width for top infill --support-material-extrusion-width Set a different extrusion width for support material --infill-overlap Overlap between infill and perimeters (default: 15%) --bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: 1) Multiple extruder options: --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement (can be specified multiple times, default: 0x0) --perimeter-extruder Extruder to use for perimeters and brim (1+, default: 1) --infill-extruder Extruder to use for infill (1+, default: 1) --solid-infill-extruder Extruder to use for solid infill (1+, default: 1) --support-material-extruder Extruder to use for support material, raft and skirt (1+, default: 1) --support-material-interface-extruder Extruder to use for support material interface (1+, default: 1) --ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping (default: no) --ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping (default: no) --standby-temperature-delta Temperature difference to be applied when an extruder is not active and --ooze-prevention is enabled (default: -5) If you want to change a preset file, just do slic3r.pl --load config.ini --layer-height 0.25 --save config.ini If you want to slice a file overriding an option contained in your preset file: slic3r.pl --load config.ini --layer-height 0.25 file.stl Slic3r-1.2.9/lib/000077500000000000000000000000001254023100400134175ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r.pm000066400000000000000000000232051254023100400151160ustar00rootroot00000000000000package Slic3r; # Copyright holder: Alessandro Ranellucci # This application is licensed under the GNU Affero General Public License, version 3 use strict; use warnings; require v5.10; our $VERSION = VERSION(); our $debug = 0; sub debugf { printf @_ if $debug; } # load threads before Moo as required by it our $have_threads; BEGIN { use Config; $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96; ### temporarily disable threads if using the broken Moo version use Moo; $have_threads = 0 if $Moo::VERSION == 1.003000; } warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n" if $^V == v5.16; use FindBin; our $var = decode_path($FindBin::Bin) . "/var"; use Moo 1.003001; use Slic3r::XS; # import all symbols (constants etc.) before they get parsed use Slic3r::Config; use Slic3r::ExPolygon; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; use Slic3r::Fill; use Slic3r::Flow; use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::PlaceholderParser; use Slic3r::GCode::PressureRegulator; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; use Slic3r::GCode::VibrationLimit; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; use Slic3r::Layer; use Slic3r::Layer::PerimeterGenerator; use Slic3r::Layer::Region; use Slic3r::Line; use Slic3r::Model; use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; use Slic3r::Print; use Slic3r::Print::GCode; use Slic3r::Print::Object; use Slic3r::Print::Simple; use Slic3r::Print::SupportMaterial; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; use Thread::Semaphore; use Encode::Locale 1.05; use Encode; use Unicode::Normalize; use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; 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.3; use constant EXTERNAL_INFILL_MARGIN => 3; use constant INSET_OVERLAP_TOLERANCE => 0.4; # keep track of threads we created my @my_threads = (); my @threads : shared = (); my $pause_sema = Thread::Semaphore->new; my $parallel_sema; my $paused = 0; sub spawn_thread { my ($cb) = @_; my $parent_tid = threads->tid; lock @threads; @_ = (); my $thread = threads->create(sub { @my_threads = (); Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid; local $SIG{'KILL'} = sub { Slic3r::debugf "Exiting thread %d...\n", threads->tid; $parallel_sema->up if $parallel_sema; kill_all_threads(); Slic3r::thread_cleanup(); threads->exit(); }; local $SIG{'STOP'} = sub { $pause_sema->down; $pause_sema->up; }; $cb->(); }); push @my_threads, $thread->tid; push @threads, $thread->tid; return $thread; } sub parallelize { my %params = @_; lock @threads; if (!$params{disable} && $Slic3r::have_threads && $params{threads} > 1) { my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}}; my $q = Thread::Queue->new; $q->enqueue(@items, (map undef, 1..$params{threads})); $parallel_sema = Thread::Semaphore->new(-$params{threads}); $parallel_sema->up; my $thread_cb = sub { # execute thread callback $params{thread_cb}->($q); # signal the parent thread that we're done $parallel_sema->up; # cleanup before terminating thread Slic3r::thread_cleanup(); # This explicit exit avoids an untrappable # "Attempt to free unreferenced scalar" error # triggered on Ubuntu 12.04 32-bit when we're running # from the Wx plater and # we're reusing the same plater object more than once. # The downside to using this exit is that we can't return # any value to the main thread but we're not doing that # anymore anyway. threads->exit; }; @_ = (); my @my_threads = map spawn_thread($thread_cb), 1..$params{threads}; # We use a semaphore instead of $th->join because joined threads are # not listed by threads->list or threads->object anymore, thus can't # be signalled. $parallel_sema->down; $_->detach for @my_threads; } else { $params{no_threads_cb}->(); } } # call this at the very end of each thread (except the main one) # so that it does not try to free existing objects. # at that stage, existing objects are only those that we # inherited at the thread creation (thus shared) and those # that we are returning: destruction will be handled by the # main thread in both cases. # reminder: do not destroy inherited objects in other threads, # as the main thread will still try to destroy them when they # go out of scope; in other words, if you're undef()'ing an # object in a thread, make sure the main thread still holds a # reference so that it won't be destroyed in thread. sub thread_cleanup { return if !$Slic3r::have_threads; # prevent destruction of shared objects no warnings 'redefine'; *Slic3r::BridgeDetector::DESTROY = sub {}; *Slic3r::Config::DESTROY = sub {}; *Slic3r::Config::Full::DESTROY = sub {}; *Slic3r::Config::GCode::DESTROY = sub {}; *Slic3r::Config::Print::DESTROY = sub {}; *Slic3r::Config::PrintObject::DESTROY = sub {}; *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::GCode::Writer::DESTROY = sub {}; *Slic3r::Geometry::BoundingBox::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {}; *Slic3r::Line::DESTROY = sub {}; *Slic3r::Linef3::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::DESTROY = sub {}; *Slic3r::Print::Object::DESTROY = sub {}; *Slic3r::Print::Region::DESTROY = sub {}; *Slic3r::Surface::DESTROY = sub {}; *Slic3r::Surface::Collection::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } sub get_running_threads { return grep defined($_), map threads->object($_), @_; } sub kill_all_threads { # if we're the main thread, we send SIGKILL to all the running threads if (threads->tid == 0) { lock @threads; foreach my $thread (get_running_threads(@threads)) { Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid; $thread->kill('KILL'); } # unlock semaphore before we block on wait # otherwise we'd get a deadlock if threads were paused resume_all_threads(); } # in any thread we wait for our children foreach my $thread (get_running_threads(@my_threads)) { Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid; $thread->join; # block until threads are killed Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid; } @my_threads = (); } sub pause_all_threads { return if $paused; lock @threads; $paused = 1; $pause_sema->down; $_->kill('STOP') for get_running_threads(@threads); } sub resume_all_threads { return unless $paused; lock @threads; $paused = 0; $pause_sema->up; } sub encode_path { my ($path) = @_; $path = Unicode::Normalize::NFC($path); $path = Encode::encode(locale_fs => $path); return $path; } sub decode_path { my ($path) = @_; $path = Encode::decode(locale_fs => $path) unless utf8::is_utf8($path); # The filesystem might force a normalization form (like HFS+ does) so # if we rely on the filename being comparable after the open() + readdir() # roundtrip (like when creating and then selecting a preset), we need to # restore our normalization form. $path = Unicode::Normalize::NFC($path); return $path; } sub open { my ($fh, $mode, $filename) = @_; return CORE::open $$fh, $mode, encode_path($filename); } # this package declaration prevents an ugly fatal warning to be emitted when # spawning a new thread package GLUquadricObjPtr; 1; Slic3r-1.2.9/lib/Slic3r/000077500000000000000000000000001254023100400145565ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Config.pm000066400000000000000000000330251254023100400163240ustar00rootroot00000000000000package Slic3r::Config; use strict; use warnings; use utf8; 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 start_perimeters_at_concave_points start_perimeters_at_non_overhang randomize_start seal_position bed_size print_center g0); 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'; for my $opt_key (keys %$Options) { *{$opt_key} = sub { $_[0]->get($opt_key) }; } } sub new_from_defaults { my $class = shift; my (@opt_keys) = @_; my $self = $class->new; my $defaults = Slic3r::Config::Full->new; if (@opt_keys) { $self->set($_, $defaults->get($_)) for @opt_keys; } else { $self->apply_static($defaults); } return $self; } sub new_from_cli { my $class = shift; my %args = @_; delete $args{$_} for grep !defined $args{$_}, keys %args; for (qw(start end layer toolchange)) { my $opt_key = "${_}_gcode"; if ($args{$opt_key}) { if (-e $args{$opt_key}) { Slic3r::open(\my $fh, "<", $args{$opt_key}) or die "Failed to open $args{$opt_key}\n"; binmode $fh, ':utf8'; $args{$opt_key} = do { local $/; <$fh> }; close $fh; } } } my $self = $class->new; foreach my $opt_key (keys %args) { 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 =~ /^(?:bed_shape|duplicate_grid|extruder_offset)$/ || $opt_def->{type} eq 'bool') { $self->set_deserialize($opt_key, $args{$opt_key}); } elsif (my $shortcut = $opt_def->{shortcut}) { $self->set($_, $args{$opt_key}) for @$shortcut; } else { $self->set($opt_key, $args{$opt_key}); } } return $self; } sub merge { my $class = shift; my $config = $class->new; $config->apply($_) for @_; return $config; } sub load { my $class = shift; my ($file) = @_; my $ini = __PACKAGE__->read_ini($file); return $class->load_ini_hash($ini->{_}); } sub load_ini_hash { my $class = shift; my ($ini_hash) = @_; my $config = $class->new; foreach my $opt_key (keys %$ini_hash) { ($opt_key, my $value) = _handle_legacy($opt_key, $ini_hash->{$opt_key}); next if !defined $opt_key; $config->set_deserialize($opt_key, $value); } return $config; } sub clone { my $self = shift; my $new = (ref $self)->new; $new->apply($self); return $new; } sub get_value { my $self = shift; my ($opt_key) = @_; return $Options->{$opt_key}{ratio_over} ? $self->get_abs_value($opt_key) : $self->get($opt_key); } sub _handle_legacy { my ($opt_key, $value) = @_; # handle legacy options if ($opt_key =~ /^(extrusion_width|bottom_layer_speed|first_layer_height)_ratio$/) { $opt_key = $1; $opt_key =~ s/^bottom_layer_speed$/first_layer_speed/; $value = $value =~ /^\d+(?:\.\d+)?$/ && $value != 0 ? ($value*100) . "%" : 0; } if ($opt_key eq 'threads' && !$Slic3r::have_threads) { $value = 1; } if ($opt_key eq 'gcode_flavor' && $value eq 'makerbot') { $value = 'makerware'; } if ($opt_key eq 'fill_density' && defined($value) && $value !~ /%/ && $value <= 1) { # fill_density was turned into a percent value $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'; } if ($opt_key eq 'bed_size' && $value) { $opt_key = 'bed_shape'; my ($x, $y) = split /,/, $value; $value = "0x0,${x}x0,${x}x${y},0x${y}"; } return () if first { $_ eq $opt_key } @Ignore; # 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 # values is a dirty hack and will need to be removed sometime in the future, but it # will avoid lots of complaints for now. if ($opt_key eq 'perimeter_acceleration' && $value == '25') { $value = 0; } if ($opt_key eq 'infill_acceleration' && $value == '50') { $value = 0; } if (!exists $Options->{$opt_key}) { my @keys = grep { $Options->{$_}{aliases} && grep $_ eq $opt_key, @{$Options->{$_}{aliases}} } keys %$Options; if (!@keys) { warn "Unknown option $opt_key\n"; return (); } $opt_key = $keys[0]; } return ($opt_key, $value); } sub as_ini { my ($self) = @_; my $ini = { _ => {} }; foreach my $opt_key (sort @{$self->get_keys}) { next if $Options->{$opt_key}{shortcut}; $ini->{_}{$opt_key} = $self->serialize($opt_key); } return $ini; } sub save { my $self = shift; my ($file) = @_; __PACKAGE__->write_ini($file, $self->as_ini); } sub setenv { my $self = shift; foreach my $opt_key (@{$self->get_keys}) { $ENV{"SLIC3R_" . uc $opt_key} = $self->serialize($opt_key); } } # this method is idempotent by design and only applies to ::DynamicConfig or ::Full # objects because it performs cross checks sub validate { my $self = shift; # -j, --threads die "Invalid value for --threads\n" if $self->threads < 1; # --layer-height die "Invalid value for --layer-height\n" if $self->layer_height <= 0; die "--layer-height must be a multiple of print resolution\n" if $self->layer_height / &Slic3r::SCALING_FACTOR % 1 != 0; # --first-layer-height die "Invalid value for --first-layer-height\n" if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/; die "Invalid value for --first-layer-height\n" if $self->get_value('first_layer_height') <= 0; # --filament-diameter die "Invalid value for --filament-diameter\n" if grep $_ < 1, @{$self->filament_diameter}; # --nozzle-diameter die "Invalid value for --nozzle-diameter\n" if grep $_ < 0, @{$self->nozzle_diameter}; # --perimeters die "Invalid value for --perimeters\n" if $self->perimeters < 0; # --solid-layers die "Invalid value for --solid-layers\n" if defined $self->solid_layers && $self->solid_layers < 0; die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0; die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0; # --gcode-flavor die "Invalid value for --gcode-flavor\n" if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; die "--use-firmware-retraction is only supported by Marlin firmware\n" if $self->use_firmware_retraction && $self->gcode_flavor ne 'reprap' && $self->gcode_flavor ne 'machinekit'; die "--use-firmware-retraction is not compatible with --wipe\n" if $self->use_firmware_retraction && first {$_} @{$self->wipe}; # --fill-pattern die "Invalid value for --fill-pattern\n" if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}}; # --external-fill-pattern die "Invalid value for --external-fill-pattern\n" if !first { $_ eq $self->external_fill_pattern } @{$Options->{external_fill_pattern}{values}}; # --fill-density die "The selected fill pattern is not supposed to work at 100% density\n" if $self->fill_density == 100 && !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}}; # --infill-every-layers die "Invalid value for --infill-every-layers\n" if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1; # --skirt-height die "Invalid value for --skirt-height\n" if $self->skirt_height < -1; # -1 means as tall as the object # --bridge-flow-ratio die "Invalid value for --bridge-flow-ratio\n" if $self->bridge_flow_ratio <= 0; # extruder clearance die "Invalid value for --extruder-clearance-radius\n" if $self->extruder_clearance_radius <= 0; die "Invalid value for --extruder-clearance-height\n" if $self->extruder_clearance_height <= 0; # --extrusion-multiplier die "Invalid value for --extrusion-multiplier\n" if defined first { $_ <= 0 } @{$self->extrusion_multiplier}; # --default-acceleration die "Invalid zero value for --default-acceleration when using other acceleration settings\n" if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration) && !$self->default_acceleration; # --spiral-vase if ($self->spiral_vase) { # Note that we might want to have more than one perimeter on the bottom # solid layers. die "Can't make more than one perimeter when spiral vase mode is enabled\n" if $self->perimeters > 1; die "Can't make less than one perimeter when spiral vase mode is enabled\n" if $self->perimeters < 1; 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" if $self->top_solid_layers > 0; die "Spiral vase mode is not compatible with support material\n" if $self->support_material || $self->support_material_enforce_layers > 0; } # extrusion widths { my $max_nozzle_diameter = max(@{ $self->nozzle_diameter }); die "Invalid extrusion width (too large)\n" if defined first { $_ > 10 * $max_nozzle_diameter } map $self->get_abs_value_over("${_}_extrusion_width", $self->layer_height), qw(perimeter infill solid_infill top_infill support_material first_layer); } # general validation, quick and dirty foreach my $opt_key (@{$self->get_keys}) { my $opt = $Options->{$opt_key}; next unless defined $self->$opt_key; next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/; my $type = $1; my @values = (); if ($type =~ s/\@$//) { die "Invalid value for $opt_key\n" if ref($self->$opt_key) ne 'ARRAY'; @values = @{ $self->$opt_key }; } else { @values = ($self->$opt_key); } foreach my $value (@values) { if ($type eq 'i' || $type eq 'f' || $opt->{type} eq 'percent') { $value =~ s/%$// if $opt->{type} eq 'percent'; die "Invalid value for $opt_key\n" if ($type eq 'i' && $value !~ /^-?\d+$/) || (($type eq 'f' || $opt->{type} eq 'percent') && $value !~ /^-?(?:\d+|\d*\.\d+)$/) || (defined $opt->{min} && $value < $opt->{min}) || (defined $opt->{max} && $value > $opt->{max}); } elsif ($type eq 's' && $opt->{type} eq 'select') { die "Invalid value for $opt_key\n" unless first { $_ eq $value } @{ $opt->{values} }; } } } return 1; } # min object distance is max(duplicate_distance, clearance_radius) sub min_object_distance { my $self = shift; return ($self->complete_objects && $self->extruder_clearance_radius > $self->duplicate_distance) ? $self->extruder_clearance_radius : $self->duplicate_distance; } # CLASS METHODS: sub write_ini { my $class = shift; my ($file, $ini) = @_; Slic3r::open(\my $fh, '>', $file); binmode $fh, ':utf8'; my $localtime = localtime; printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime"; # make sure the _ category is the first one written foreach my $category (sort { ($a eq '_') ? -1 : ($a cmp $b) } keys %$ini) { printf $fh "\n[%s]\n", $category if $category ne '_'; foreach my $key (sort keys %{$ini->{$category}}) { printf $fh "%s = %s\n", $key, $ini->{$category}{$key}; } } close $fh; } sub read_ini { my $class = shift; my ($file) = @_; local $/ = "\n"; Slic3r::open(\my $fh, '<', $file) or die "Unable to open $file: $!\n"; binmode $fh, ':utf8'; my $ini = { _ => {} }; my $category = '_'; while (<$fh>) { s/\R+$//; next if /^\s+/; next if /^$/; next if /^\s*#/; if (/^\[(.+?)\]$/) { $category = $1; next; } /^(\w+) *= *(.*)/ or die "Unreadable configuration file (invalid data at line $.)\n"; $ini->{$category}{$1} = $2; } close $fh; return $ini; } package Slic3r::Config::GCode; use parent 'Slic3r::Config'; package Slic3r::Config::Print; use parent 'Slic3r::Config'; package Slic3r::Config::PrintObject; use parent 'Slic3r::Config'; package Slic3r::Config::PrintRegion; use parent 'Slic3r::Config'; package Slic3r::Config::Full; use parent 'Slic3r::Config'; 1; Slic3r-1.2.9/lib/Slic3r/ExPolygon.pm000066400000000000000000000022211254023100400170350ustar00rootroot00000000000000package Slic3r::ExPolygon; use strict; use warnings; # an ExPolygon is a polygon with holes use List::Util qw(first); use Slic3r::Geometry qw(X Y A B point_in_polygon epsilon scaled_epsilon); use Slic3r::Geometry::Clipper qw(union_ex diff_pl); sub wkt { my $self = shift; return sprintf "POLYGON(%s)", join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self; } sub dump_perl { my $self = shift; return sprintf "[%s]", join ',', map "[$_]", map { join ',', map "[$_->[0],$_->[1]]", @$_ } @$self; } sub offset { my $self = shift; return Slic3r::Geometry::Clipper::offset(\@$self, @_); } sub offset_ex { my $self = shift; return Slic3r::Geometry::Clipper::offset_ex(\@$self, @_); } sub noncollapsing_offset_ex { my $self = shift; my ($distance, @params) = @_; return $self->offset_ex($distance + 1, @params); } sub bounding_box { my $self = shift; return $self->contour->bounding_box; } package Slic3r::ExPolygon::Collection; use Slic3r::Geometry qw(X1 Y1); sub size { my $self = shift; return [ Slic3r::Geometry::size_2D([ map @$_, map @$_, @$self ]) ]; } 1; Slic3r-1.2.9/lib/Slic3r/ExtrusionLoop.pm000066400000000000000000000003271254023100400177500ustar00rootroot00000000000000package Slic3r::ExtrusionLoop; use strict; use warnings; use parent qw(Exporter); our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER); our %EXPORT_TAGS = (roles => \@EXPORT_OK); 1; Slic3r-1.2.9/lib/Slic3r/ExtrusionPath.pm000066400000000000000000000006331254023100400177330ustar00rootroot00000000000000package Slic3r::ExtrusionPath; use strict; use warnings; use parent qw(Exporter); 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; Slic3r-1.2.9/lib/Slic3r/Fill.pm000066400000000000000000000257501254023100400160130ustar00rootroot00000000000000package Slic3r::Fill; use Moo; use List::Util qw(max); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Fill::3DHoneycomb; use Slic3r::Fill::Base; use Slic3r::Fill::Concentric; use Slic3r::Fill::Honeycomb; use Slic3r::Fill::PlanePath; use Slic3r::Fill::Rectilinear; use Slic3r::Flow ':roles'; 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'; has 'bounding_box' => (is => 'ro', required => 0); has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( archimedeanchords => 'Slic3r::Fill::ArchimedeanChords', rectilinear => 'Slic3r::Fill::Rectilinear', flowsnake => 'Slic3r::Fill::Flowsnake', octagramspiral => 'Slic3r::Fill::OctagramSpiral', hilbertcurve => 'Slic3r::Fill::HilbertCurve', line => 'Slic3r::Fill::Line', concentric => 'Slic3r::Fill::Concentric', honeycomb => 'Slic3r::Fill::Honeycomb', '3dhoneycomb' => 'Slic3r::Fill::3DHoneycomb', ); sub filler { my $self = shift; my ($filler) = @_; if (!ref $self) { return $FillTypes{$filler}->new; } $self->fillers->{$filler} ||= $FillTypes{$filler}->new( bounding_box => $self->bounding_box, ); return $self->fillers->{$filler}; } sub make_fill { my $self = shift; my ($layerm) = @_; Slic3r::debugf "Filling layer %d:\n", $layerm->id; my $fill_density = $layerm->config->fill_density; my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL); my @surfaces = (); # merge adjacent surfaces # 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 { $_->bridge_angle >= 0 } @{$layerm->fill_surfaces}; # group surfaces by distinct properties my @groups = @{$layerm->fill_surfaces->group}; # merge compatible groups (we can generate continuous infill for them) { # cache flow widths and patterns used for all solid groups # (we'll use them for comparing compatible groups) my @is_solid = my @fw = my @pattern = (); for (my $i = 0; $i <= $#groups; $i++) { # we can only merge solid non-bridge surfaces, so discard # non-solid surfaces if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) { $is_solid[$i] = 1; $fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP) ? $top_solid_infill_flow->width : $solid_infill_flow->width; $pattern[$i] = $groups[$i][0]->is_external ? $layerm->config->external_fill_pattern : 'rectilinear'; } else { $is_solid[$i] = 0; $fw[$i] = 0; $pattern[$i] = 'none'; } } # loop through solid groups for (my $i = 0; $i <= $#groups; $i++) { next if !$is_solid[$i]; # find compatible groups and append them to this one for (my $j = $i+1; $j <= $#groups; $j++) { next if !$is_solid[$j]; if ($fw[$i] == $fw[$j] && $pattern[$i] eq $pattern[$j]) { # groups are compatible, merge them push @{$groups[$i]}, @{$groups[$j]}; splice @groups, $j, 1; splice @is_solid, $j, 1; splice @fw, $j, 1; splice @pattern, $j, 1; } } } } # give priority to bridges @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 && $group->[0]->bridge_angle < 0) { $union_p = diff( $union_p, [ map $_->p, @surfaces_with_bridge_angle ], 1, ); } # subtract any other surface already processed my $union = diff_ex( $union_p, [ map $_->p, @surfaces ], 1, ); push @surfaces, map $group->[0]->clone(expolygon => $_), @$union; } } # we need to detect any narrow surfaces that might collapse # when adding spacing below # such narrow surfaces are often generated in sloping walls # by bridge_over_infill() and combine_infill() as a result of the # subtraction of the combinable area from the layer infill area, # which leaves small areas near the perimeters # we are going to grow such regions by overlapping them with the void (if any) # TODO: detect and investigate whether there could be narrow regions without # any void neighbors { my $distance_between_surfaces = max( $infill_flow->scaled_spacing, $solid_infill_flow->scaled_spacing, $top_solid_infill_flow->scaled_spacing, ); my $collapsed = diff( [ map @{$_->expolygon}, @surfaces ], offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2), 1, ); push @surfaces, map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNALSOLID, ), @{intersection_ex( offset($collapsed, $distance_between_surfaces), [ (map @{$_->expolygon}, grep $_->surface_type == S_TYPE_INTERNALVOID, @surfaces), (@$collapsed), ], 1, )}; } if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], ); } my @fills = (); SURFACE: foreach my $surface (@surfaces) { next if $surface->surface_type == S_TYPE_INTERNALVOID; my $filler = $layerm->config->fill_pattern; my $density = $fill_density; 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; if ($surface->is_solid) { $density = 100; $filler = 'rectilinear'; if ($surface->is_external && !$is_bridge) { $filler = $layerm->config->external_fill_pattern; } } else { next SURFACE unless $density > 0; } # get filler object my $f = $self->filler($filler); # calculate the actual flow we'll be using for this infill my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness; my $flow = $layerm->region->flow( $role, $h, $is_bridge || $f->use_bridge_flow, $layerm->id == 0, -1, $layerm->object, ); # calculate flow spacing for infill pattern generation my $using_internal_flow = 0; if (!$is_solid && !$is_bridge) { # it's internal infill, so we can calculate a generic flow spacing # for all layers, for avoiding the ugly effect of # misaligned infill on first layer because of different extrusion width and # layer height my $internal_flow = $layerm->region->flow( FLOW_ROLE_INFILL, $layerm->object->config->layer_height, # TODO: handle infill_every_layers? 0, # no bridge 0, # no first layer -1, # auto width $layerm->object, ); $f->spacing($internal_flow->spacing); $using_internal_flow = 1; } else { $f->spacing($flow->spacing); } $f->layer_id($layerm->id); $f->z($layerm->print_z); $f->angle(deg2rad($layerm->config->fill_angle)); $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); # apply half spacing using this flow's own spacing and generate infill my @polylines = map $f->fill_surface( $_, density => $density/100, layer_height => $h, ), @{ $surface->offset(-scale($f->spacing)/2) }; next unless @polylines; # calculate actual flow from spacing (which might have been adjusted by the infill # pattern generator) if ($using_internal_flow) { # if we used the internal flow we're not doing a solid infill # so we can safely ignore the slight variation that might have # been applied to $f->flow_spacing } else { $flow = Slic3r::Flow->new_from_spacing( spacing => $f->spacing, nozzle_diameter => $flow->nozzle_diameter, layer_height => $h, bridge => $is_bridge || $f->use_bridge_flow, ); } my $mm3_per_mm = $flow->mm3_per_mm; # save into layer { my $role = $is_bridge ? EXTR_ROLE_BRIDGE : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) : EXTR_ROLE_FILL; push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; $collection->no_sort($f->no_sort); $collection->append( map Slic3r::ExtrusionPath->new( polyline => $_, role => $role, mm3_per_mm => $mm3_per_mm, width => $flow->width, height => $flow->height, ), @polylines, ); } } # add thin fill regions foreach my $thin_fill (@{$layerm->thin_fills}) { push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill); } return @fills; } 1; Slic3r-1.2.9/lib/Slic3r/Fill/000077500000000000000000000000001254023100400154445ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Fill/3DHoneycomb.pm000066400000000000000000000163601254023100400201220ustar00rootroot00000000000000package Slic3r::Fill::3DHoneycomb; use Moo; extends 'Slic3r::Fill::Base'; use POSIX qw(ceil fmod); use Slic3r::Geometry qw(scale scaled_epsilon); use Slic3r::Geometry::Clipper qw(intersection_pl); # require bridge flow since most of this pattern hangs in air sub use_bridge_flow { 1 } sub fill_surface { my ($self, $surface, %params) = @_; my $expolygon = $surface->expolygon; my $bb = $expolygon->bounding_box; my $size = $bb->size; my $distance = scale($self->spacing) / $params{density}; # align bounding box to a multiple of our honeycomb grid module # (a module is 2*$distance since one $distance half-module is # growing while the other $distance half-module is shrinking) { my $min = $bb->min_point; $min->translate( -($bb->x_min % (2*$distance)), -($bb->y_min % (2*$distance)), ); $bb->merge_point($min); } # generate pattern my @polylines = map Slic3r::Polyline->new(@$_), makeGrid( scale($self->z), $distance, ceil($size->x / $distance) + 1, ceil($size->y / $distance) + 1, #// (($self->layer_id / $surface->thickness_layers) % 2) + 1, ); # move pattern in place $_->translate($bb->x_min, $bb->y_min) for @polylines; # clip pattern to boundaries @polylines = @{intersection_pl(\@polylines, \@$expolygon)}; # connect lines unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections my ($expolygon_off) = @{$expolygon->offset_ex(scaled_epsilon)}; my $collection = Slic3r::Polyline::Collection->new(@polylines); @polylines = (); foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { # try to append this polyline to previous one if any if (@polylines) { my $line = Slic3r::Line->new($polylines[-1]->last_point, $polyline->first_point); if ($line->length <= 1.5*$distance && $expolygon_off->contains_line($line)) { $polylines[-1]->append_polyline($polyline); next; } } # make a clone before $collection goes out of scope push @polylines, $polyline->clone; } } # TODO: return ExtrusionLoop objects to get better chained paths return @polylines; } =head1 DESCRIPTION Creates a contiguous sequence of points at a specified height that make up a horizontal slice of the edges of a space filling truncated octahedron tesselation. The octahedrons are oriented so that the square faces are in the horizontal plane with edges parallel to the X and Y axes. Credits: David Eccles (gringer). =head2 makeGrid(z, gridSize, gridWidth, gridHeight, curveType) Generate a set of curves (array of array of 2d points) that describe a horizontal slice of a truncated regular octahedron with a specified grid square size. =cut sub makeGrid { my ($z, $gridSize, $gridWidth, $gridHeight, $curveType) = @_; my $scaleFactor = $gridSize; my $normalisedZ = $z / $scaleFactor; my @points = makeNormalisedGrid($normalisedZ, $gridWidth, $gridHeight, $curveType); foreach my $lineRef (@points) { foreach my $pointRef (@$lineRef) { $pointRef->[0] *= $scaleFactor; $pointRef->[1] *= $scaleFactor; } } return @points; } =head1 FUNCTIONS =cut =head2 colinearPoints(offset, gridLength) Generate an array of points that are in the same direction as the basic printing line (i.e. Y points for columns, X points for rows) Note: a negative offset only causes a change in the perpendicular direction =cut sub colinearPoints { my ($offset, $baseLocation, $gridLength) = @_; my @points = (); push @points, $baseLocation - abs($offset/2); for (my $i = 0; $i < $gridLength; $i++) { push @points, $baseLocation + $i + abs($offset/2); push @points, $baseLocation + ($i+1) - abs($offset/2); } push @points, $baseLocation + $gridLength + abs($offset/2); return @points; } =head2 colinearPoints(offset, baseLocation, gridLength) Generate an array of points for the dimension that is perpendicular to the basic printing line (i.e. X points for columns, Y points for rows) =cut sub perpendPoints { my ($offset, $baseLocation, $gridLength) = @_; my @points = (); my $side = 2*(($baseLocation) % 2) - 1; push @points, $baseLocation - $offset/2 * $side; for (my $i = 0; $i < $gridLength; $i++) { $side = 2*(($i+$baseLocation) % 2) - 1; push @points, $baseLocation + $offset/2 * $side; push @points, $baseLocation + $offset/2 * $side; } push @points, $baseLocation - $offset/2 * $side; return @points; } =head2 trim(pointArrayRef, minX, minY, maxX, maxY) Trims an array of points to specified rectangular limits. Point components that are outside these limits are set to the limits. =cut sub trim { my ($pointArrayRef, $minX, $minY, $maxX, $maxY) = @_; foreach (@$pointArrayRef) { $_->[0] = ($_->[0] < $minX) ? $minX : (($_->[0] > $maxX) ? $maxX : $_->[0]); $_->[1] = ($_->[1] < $minY) ? $minY : (($_->[1] > $maxY) ? $maxY : $_->[1]); } } =head2 makeNormalisedGrid(z, gridWidth, gridHeight, curveType) Generate a set of curves (array of array of 2d points) that describe a horizontal slice of a truncated regular octahedron with edge length 1. curveType specifies which lines to print, 1 for vertical lines (columns), 2 for horizontal lines (rows), and 3 for both. =cut sub makeNormalisedGrid { my ($z, $gridWidth, $gridHeight, $curveType) = @_; ## offset required to create a regular octagram my $octagramGap = 0.5; # sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] my $a = sqrt(2); # period my $wave = abs(fmod($z, $a) - $a/2)/$a*4 - 1; my $offset = $wave * $octagramGap; my @points = (); if (($curveType & 1) != 0) { for (my $x = 0; $x <= $gridWidth; $x++) { my @xPoints = perpendPoints($offset, $x, $gridHeight); my @yPoints = colinearPoints($offset, 0, $gridHeight); # This is essentially @newPoints = zip(@xPoints, @yPoints) my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints; # trim points to grid edges #trim(\@newPoints, 0, 0, $gridWidth, $gridHeight); if ($x % 2 == 0){ push @points, [ @newPoints ]; } else { push @points, [ reverse @newPoints ]; } } } if (($curveType & 2) != 0) { for (my $y = 0; $y <= $gridHeight; $y++) { my @xPoints = colinearPoints($offset, 0, $gridWidth); my @yPoints = perpendPoints($offset, $y, $gridWidth); my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints; # trim points to grid edges #trim(\@newPoints, 0, 0, $gridWidth, $gridHeight); if ($y % 2 == 0) { push @points, [ @newPoints ]; } else { push @points, [ reverse @newPoints ]; } } } return @points; } 1; Slic3r-1.2.9/lib/Slic3r/Fill/Base.pm000066400000000000000000000050331254023100400166550ustar00rootroot00000000000000package Slic3r::Fill::Base; use Moo; has 'layer_id' => (is => 'rw'); has 'z' => (is => 'rw'); # in unscaled coordinates has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East has 'spacing' => (is => 'rw'); # in unscaled coordinates has 'loop_clipping' => (is => 'rw', default => sub { 0 }); # in scaled coordinates has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object sub adjust_solid_spacing { my $self = shift; my %params = @_; my $number_of_lines = int($params{width} / $params{distance}) + 1; return $params{distance} if $number_of_lines <= 1; my $extra_space = $params{width} % $params{distance}; return $params{distance} + $extra_space / ($number_of_lines - 1); } sub no_sort { 0 } sub use_bridge_flow { 0 } package Slic3r::Fill::WithDirection; use Moo::Role; use Slic3r::Geometry qw(PI rad2deg); sub angles () { [0, PI/2] } sub infill_direction { my $self = shift; my ($surface) = @_; if (!defined $self->angle) { warn "Using undefined infill angle"; $self->angle(0); } # set infill angle my (@rotate); $rotate[0] = $self->angle; $rotate[1] = $self->bounding_box ? $self->bounding_box->center : $surface->expolygon->bounding_box->center; 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] = $self->angle + $angle if $angle; } # use bridge angle if ($surface->bridge_angle >= 0) { Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); $rotate[0] = $surface->bridge_angle; } $rotate[0] += PI/2; $shift->rotate(@rotate); return [\@rotate, $shift]; } # this method accepts any object that implements rotate() and translate() sub rotate_points { my $self = shift; my ($expolygon, $rotate_vector) = @_; # rotate points 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, $shift) = @$rotate_vector; $shift = [ map -$_, @$shift ]; $_->translate(@$shift) for @$paths; $_->rotate(@$rotate) for @$paths; } 1; Slic3r-1.2.9/lib/Slic3r/Fill/Concentric.pm000066400000000000000000000034601254023100400200740ustar00rootroot00000000000000package Slic3r::Fill::Concentric; use Moo; extends 'Slic3r::Fill::Base'; use Slic3r::Geometry qw(scale unscale X); use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained); sub no_sort { 1 } sub fill_surface { my $self = shift; my ($surface, %params) = @_; # no rotation is supported for this infill pattern my $expolygon = $surface->expolygon; my $bounding_box = $expolygon->bounding_box; my $min_spacing = scale($self->spacing); my $distance = $min_spacing / $params{density}; if ($params{density} == 1 && !$params{dont_adjust}) { $distance = $self->adjust_solid_spacing( width => $bounding_box->size->[X], distance => $distance, ); $self->spacing(unscale $distance); } my @loops = my @last = map $_->clone, @$expolygon; while (@last) { push @loops, @last = @{offset2(\@last, -($distance + 0.5*$min_spacing), +0.5*$min_spacing)}; } # generate paths from the outermost to the innermost, to avoid # adhesion problems of the first central tiny loops @loops = map Slic3r::Polygon->new(@$_), reverse @{union_pt_chained(\@loops)}; # split paths using a nearest neighbor search my @paths = (); my $last_pos = Slic3r::Point->new(0,0); foreach my $loop (@loops) { push @paths, $loop->split_at_index($last_pos->nearest_point_index(\@$loop)); $last_pos = $paths[-1]->last_point; } # clip the paths to prevent the extruder from getting exactly on the first point of the loop $_->clip_end($self->loop_clipping) for @paths; @paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping) # TODO: return ExtrusionLoop objects to get better chained paths return @paths; } 1; Slic3r-1.2.9/lib/Slic3r/Fill/Honeycomb.pm000066400000000000000000000123141254023100400177260ustar00rootroot00000000000000package Slic3r::Fill::Honeycomb; use Moo; extends 'Slic3r::Fill::Base'; with qw(Slic3r::Fill::WithDirection); has 'cache' => (is => 'rw', default => sub {{}}); use Slic3r::Geometry qw(PI X Y MIN MAX scale scaled_epsilon); use Slic3r::Geometry::Clipper qw(intersection intersection_pl); sub angles () { [0, PI/3, PI/3*2] } sub fill_surface { my $self = shift; my ($surface, %params) = @_; my $rotate_vector = $self->infill_direction($surface); # cache hexagons math my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing; my $m; if (!($m = $self->cache->{$cache_id})) { $m = $self->cache->{$cache_id} = {}; my $min_spacing = scale($self->spacing); $m->{distance} = $min_spacing / $params{density}; $m->{hex_side} = $m->{distance} / (sqrt(3)/2); $m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3); my $hex_height = $m->{hex_side} * 2; $m->{pattern_height} = $hex_height + $m->{hex_side}; $m->{y_short} = $m->{distance} * sqrt(3)/3; $m->{x_offset} = $min_spacing / 2; $m->{y_offset} = $m->{x_offset} * sqrt(3)/3; $m->{hex_center} = Slic3r::Point->new($m->{hex_width}/2, $m->{hex_side}); } my @polygons = (); { # adjust actual bounding box to the nearest multiple of our hex pattern # and align it so that it matches across layers my $bounding_box = $surface->expolygon->bounding_box; { # rotate bounding box according to infill direction my $bb_polygon = $bounding_box->polygon; $bb_polygon->rotate($rotate_vector->[0][0], $m->{hex_center}); $bounding_box = $bb_polygon->bounding_box; # extend bounding box so that our pattern will be aligned with other layers # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one $bounding_box->merge_point(Slic3r::Point->new( $bounding_box->x_min - ($bounding_box->x_min % $m->{hex_width}), $bounding_box->y_min - ($bounding_box->y_min % $m->{pattern_height}), )); } my $x = $bounding_box->x_min; while ($x <= $bounding_box->x_max) { my $p = []; my @x = ($x + $m->{x_offset}, $x + $m->{distance} - $m->{x_offset}); for (1..2) { @$p = reverse @$p; # turn first half upside down my @p = (); for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y += $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side}) { push @$p, [ $x[1], $y + $m->{y_offset} ], [ $x[0], $y + $m->{y_short} - $m->{y_offset} ], [ $x[0], $y + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ], [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} - $m->{y_offset} ], [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ]; } @x = map $_ + $m->{distance}, reverse @x; # draw symmetrical pattern $x += $m->{distance}; } push @polygons, Slic3r::Polygon->new(@$p); } $_->rotate(-$rotate_vector->[0][0], $m->{hex_center}) for @polygons; } my @paths; if ($params{complete} || 1) { # we were requested to complete each loop; # in this case we don't try to make more continuous paths @paths = map $_->split_at_first_point, @{intersection([ $surface->p ], \@polygons)}; } else { # consider polygons as polylines without re-appending the initial point: # this cuts the last segment on purpose, so that the jump to the next # path is more straight @paths = @{intersection_pl( [ map Slic3r::Polyline->new(@$_), @polygons ], [ @{$surface->expolygon} ], )}; # connect paths if (@paths) { # prevent calling leftmost_point() on empty collections my $collection = Slic3r::Polyline::Collection->new(@paths); @paths = (); foreach my $path (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { if (@paths) { # distance between first point of this path and last point of last path my $distance = $paths[-1]->last_point->distance_to($path->first_point); if ($distance <= $m->{hex_width}) { $paths[-1]->append_polyline($path); next; } } # make a clone before $collection goes out of scope push @paths, $path->clone; } } # clip paths again to prevent connection segments from crossing the expolygon boundaries @paths = @{intersection_pl( \@paths, [ map @$_, @{$surface->expolygon->offset_ex(scaled_epsilon)} ], )}; } return @paths; } 1; Slic3r-1.2.9/lib/Slic3r/Fill/PlanePath.pm000066400000000000000000000063151254023100400176630ustar00rootroot00000000000000package Slic3r::Fill::PlanePath; use Moo; extends 'Slic3r::Fill::Base'; with qw(Slic3r::Fill::WithDirection); use Slic3r::Geometry qw(scale X1 Y1 X2 Y2); use Slic3r::Geometry::Clipper qw(intersection_pl); sub angles () { [0] } sub multiplier () { 1 } sub process_polyline {} sub fill_surface { my $self = shift; my ($surface, %params) = @_; # rotate polygons my $expolygon = $surface->expolygon->clone; my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); my $distance_between_lines = scale($self->spacing) / $params{density} * $self->multiplier; # align infill across layers using the object's bounding box my $bb_polygon = $self->bounding_box->polygon; $self->rotate_points($bb_polygon, $rotate_vector); my $bounding_box = $bb_polygon->bounding_box; (ref $self) =~ /::([^:]+)$/; my $path = "Math::PlanePath::$1"->new; my $translate = Slic3r::Point->new(0,0); # vector if ($path->x_negative || $path->y_negative) { # if the curve extends on both positive and negative coordinate space, # center our expolygon around origin $translate = $bounding_box->center->negative; } else { # if the curve does not extend in negative coordinate space, # move expolygon entirely in positive coordinate space $translate = $bounding_box->min_point->negative; } $expolygon->translate(@$translate); $bounding_box->translate(@$translate); my ($n_lo, $n_hi) = $path->rect_to_n_range( map { $_ / $distance_between_lines } @{$bounding_box->min_point}, @{$bounding_box->max_point}, ); my $polyline = Slic3r::Polyline->new( map [ map { $_ * $distance_between_lines } $path->n_to_xy($_) ], ($n_lo..$n_hi) ); return {} if @$polyline <= 1; $self->process_polyline($polyline, $bounding_box); my @paths = @{intersection_pl([$polyline], \@$expolygon)}; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("fill.svg", no_arrows => 1, polygons => \@$expolygon, green_polygons => [ $bounding_box->polygon ], polylines => [ $polyline ], red_polylines => \@paths, ); } # paths must be repositioned and rotated back $_->translate(@{$translate->negative}) for @paths; $self->rotate_points_back(\@paths, $rotate_vector); return @paths; } package Slic3r::Fill::ArchimedeanChords; use Moo; extends 'Slic3r::Fill::PlanePath'; use Math::PlanePath::ArchimedeanChords; package Slic3r::Fill::Flowsnake; use Moo; extends 'Slic3r::Fill::PlanePath'; use Math::PlanePath::Flowsnake; use Slic3r::Geometry qw(X); # Sorry, this fill is currently broken. sub process_polyline { my $self = shift; my ($polyline, $bounding_box) = @_; $_->[X] += $bounding_box->center->[X] for @$polyline; } package Slic3r::Fill::HilbertCurve; use Moo; extends 'Slic3r::Fill::PlanePath'; use Math::PlanePath::HilbertCurve; package Slic3r::Fill::OctagramSpiral; use Moo; extends 'Slic3r::Fill::PlanePath'; use Math::PlanePath::OctagramSpiral; sub multiplier () { sqrt(2) } 1; Slic3r-1.2.9/lib/Slic3r/Fill/Rectilinear.pm000066400000000000000000000125241254023100400202470ustar00rootroot00000000000000package Slic3r::Fill::Rectilinear; use Moo; extends 'Slic3r::Fill::Base'; with qw(Slic3r::Fill::WithDirection); has '_min_spacing' => (is => 'rw'); has '_line_spacing' => (is => 'rw'); has '_diagonal_distance' => (is => 'rw'); has '_line_oscillation' => (is => 'rw'); use Slic3r::Geometry qw(scale unscale scaled_epsilon); use Slic3r::Geometry::Clipper qw(intersection_pl); sub fill_surface { my $self = shift; my ($surface, %params) = @_; # rotate polygons so that we can work with vertical lines here my $expolygon = $surface->expolygon->clone; my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); $self->_min_spacing(scale $self->spacing); $self->_line_spacing($self->_min_spacing / $params{density}); $self->_diagonal_distance($self->_line_spacing * 2); $self->_line_oscillation($self->_line_spacing - $self->_min_spacing); # only for Line infill my $bounding_box = $expolygon->bounding_box; # define flow spacing according to requested density if ($params{density} == 1 && !$params{dont_adjust}) { $self->_line_spacing($self->adjust_solid_spacing( width => $bounding_box->size->x, distance => $self->_line_spacing, )); $self->spacing(unscale $self->_line_spacing); } else { # extend bounding box so that our pattern will be aligned with other layers $bounding_box->merge_point(Slic3r::Point->new( $bounding_box->x_min - ($bounding_box->x_min % $self->_line_spacing), $bounding_box->y_min - ($bounding_box->y_min % $self->_line_spacing), )); } # generate the basic pattern my $x_max = $bounding_box->x_max + scaled_epsilon; my @vertical_lines = (); for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) { push @vertical_lines, $self->_line($#vertical_lines, $x, $bounding_box->y_min, $bounding_box->y_max); } # clip paths against a slightly larger expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides # the minimum offset for preventing edge lines from being clipped is scaled_epsilon; # however we use a larger offset to support expolygons with slightly skewed sides and # not perfectly straight my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset(+scale 0.02))}; my $extra = $self->_min_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING; foreach my $polyline (@polylines) { my ($first_point, $last_point) = @$polyline[0,-1]; if ($first_point->y > $last_point->y) { #> ($first_point, $last_point) = ($last_point, $first_point); } $first_point->set_y($first_point->y - $extra); #-- $last_point->set_y($last_point->y + $extra); #++ } # connect lines unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections # offset the expolygon by max(min_spacing/2, extra) my ($expolygon_off) = @{$expolygon->offset_ex($self->_min_spacing/2)}; my $collection = Slic3r::Polyline::Collection->new(@polylines); @polylines = (); foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { if (@polylines) { my $first_point = $polyline->first_point; my $last_point = $polylines[-1]->last_point; my @distance = map abs($first_point->$_ - $last_point->$_), qw(x y); # TODO: we should also check that both points are on a fill_boundary to avoid # connecting paths on the boundaries of internal regions if ($self->_can_connect(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) { $polylines[-1]->append_polyline($polyline); next; } } # make a clone before $collection goes out of scope push @polylines, $polyline->clone; } } # paths must be rotated back $self->rotate_points_back(\@polylines, $rotate_vector); return @polylines; } sub _line { my ($self, $i, $x, $y_min, $y_max) = @_; return Slic3r::Polyline->new( [$x, $y_min], [$x, $y_max], ); } sub _can_connect { my ($self, $dist_X, $dist_Y) = @_; return $dist_X <= $self->_diagonal_distance && $dist_Y <= $self->_diagonal_distance; } package Slic3r::Fill::Line; use Moo; extends 'Slic3r::Fill::Rectilinear'; use Slic3r::Geometry qw(scaled_epsilon); sub _line { my ($self, $i, $x, $y_min, $y_max) = @_; if ($i % 2) { return Slic3r::Polyline->new( [$x - $self->_line_oscillation, $y_min], [$x + $self->_line_oscillation, $y_max], ); } else { return Slic3r::Polyline->new( [$x, $y_min], [$x, $y_max], ); } } sub _can_connect { my ($self, $dist_X, $dist_Y) = @_; my $TOLERANCE = 10 * scaled_epsilon; return ($dist_X >= ($self->_line_spacing - $self->_line_oscillation) - $TOLERANCE) && ($dist_X <= ($self->_line_spacing + $self->_line_oscillation) + $TOLERANCE) && $dist_Y <= $self->_diagonal_distance; } 1; Slic3r-1.2.9/lib/Slic3r/Flow.pm000066400000000000000000000005211254023100400160210ustar00rootroot00000000000000package Slic3r::Flow; use strict; use warnings; use parent qw(Exporter); 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); 1; Slic3r-1.2.9/lib/Slic3r/Format/000077500000000000000000000000001254023100400160065ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Format/AMF.pm000066400000000000000000000120771254023100400167560ustar00rootroot00000000000000package Slic3r::Format::AMF; use Moo; use Slic3r::Geometry qw(X Y Z); sub read_file { my $self = shift; my ($file) = @_; eval qq{ require Slic3r::Format::AMF::Parser; use XML::SAX::ParserFactory; 1; } or die "AMF parsing requires XML::SAX\n"; Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n"; my $model = Slic3r::Model->new; XML::SAX::ParserFactory ->parser(Handler => Slic3r::Format::AMF::Parser->new(_model => $model)) ->parse_file($fh); close $fh; return $model; } sub write_file { my $self = shift; my ($file, $model, %params) = @_; my %vertices_offset = (); Slic3r::open(\my $fh, '>', $file); binmode $fh, ':utf8'; printf $fh qq{\n}; printf $fh qq{\n}; printf $fh qq{ Slic3r %s\n}, $Slic3r::VERSION; for my $material_id (sort @{ $model->material_names }) { next if $material_id eq ''; my $material = $model->get_material($material_id); # note that material-id must never be 0 since it's reserved by the AMF spec printf $fh qq{ \n}, $material_id; for (keys %{$material->attributes}) { printf $fh qq{ %s\n}, $_, $material->attributes->{$_}; } my $config = $material->config; foreach my $opt_key (@{$config->get_keys}) { printf $fh qq{ %s\n}, $opt_key, $config->serialize($opt_key); } printf $fh qq{ \n}; } my $instances = ''; for my $object_id (0 .. $#{ $model->objects }) { my $object = $model->objects->[$object_id]; printf $fh qq{ \n}, $object_id; my $config = $object->config; foreach my $opt_key (@{$config->get_keys}) { printf $fh qq{ %s\n}, $opt_key, $config->serialize($opt_key); } if ($object->name) { printf $fh qq{ %s\n}, $object->name; } printf $fh qq{ \n}; printf $fh qq{ \n}; my @vertices_offset = (); { my $vertices_offset = 0; foreach my $volume (@{ $object->volumes }) { push @vertices_offset, $vertices_offset; my $vertices = $volume->mesh->vertices; foreach my $vertex (@$vertices) { printf $fh qq{ \n}; printf $fh qq{ \n}; printf $fh qq{ %s\n}, $vertex->[X]; printf $fh qq{ %s\n}, $vertex->[Y]; printf $fh qq{ %s\n}, $vertex->[Z]; printf $fh qq{ \n}; printf $fh qq{ \n}; } $vertices_offset += scalar(@$vertices); } } printf $fh qq{ \n}; foreach my $volume (@{ $object->volumes }) { my $vertices_offset = shift @vertices_offset; printf $fh qq{ \n}, ($volume->material_id eq '') ? '' : (sprintf ' materialid="%s"', $volume->material_id); my $config = $volume->config; foreach my $opt_key (@{$config->get_keys}) { printf $fh qq{ %s\n}, $opt_key, $config->serialize($opt_key); } if ($volume->name) { printf $fh qq{ %s\n}, $volume->name; } if ($volume->modifier) { printf $fh qq{ 1\n}; } foreach my $facet (@{$volume->mesh->facets}) { printf $fh qq{ \n}; printf $fh qq{ %d\n}, $_, $facet->[$_-1] + $vertices_offset, $_ for 1..3; printf $fh qq{ \n}; } printf $fh qq{ \n}; } printf $fh qq{ \n}; printf $fh qq{ \n}; if ($object->instances) { foreach my $instance (@{$object->instances}) { $instances .= sprintf qq{ \n}, $object_id; $instances .= sprintf qq{ %s\n}, $instance->offset->[X]; $instances .= sprintf qq{ %s\n}, $instance->offset->[Y]; $instances .= sprintf qq{ %s\n}, $instance->rotation; $instances .= sprintf qq{ \n}; } } } if ($instances) { printf $fh qq{ \n}; printf $fh $instances; printf $fh qq{ \n}; } printf $fh qq{\n}; close $fh; } 1; Slic3r-1.2.9/lib/Slic3r/Format/AMF/000077500000000000000000000000001254023100400164115ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Format/AMF/Parser.pm000066400000000000000000000146101254023100400202050ustar00rootroot00000000000000package Slic3r::Format::AMF::Parser; use strict; use warnings; use base 'XML::SAX::Base'; my %xyz_index = (x => 0, y => 1, z => 2); #= sub new { my $self = shift->SUPER::new(@_); $self->{_tree} = []; $self->{_objects_map} = {}; # this hash maps AMF object IDs to object indexes in $model->objects $self->{_instances} = {}; # apply these lazily to make sure all objects have been parsed $self; } sub start_element { my $self = shift; my $data = shift; if ($data->{LocalName} eq 'object') { $self->{_object} = $self->{_model}->add_object; $self->{_object_vertices} = []; $self->{_objects_map}{ $self->_get_attribute($data, 'id') } = $#{ $self->{_model}->objects }; } elsif ($data->{LocalName} eq 'vertex') { $self->{_vertex} = ["", "", ""]; } elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') { $self->{_coordinate} = $data->{LocalName}; } elsif ($data->{LocalName} eq 'volume') { $self->{_volume} = $self->{_object}->add_volume( material_id => $self->_get_attribute($data, 'materialid') // undef, mesh => Slic3r::TriangleMesh->new, ); $self->{_volume_facets} = []; } elsif ($data->{LocalName} eq 'triangle') { $self->{_triangle} = ["", "", ""]; } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { $self->{_vertex_idx} = $1-1; } elsif ($data->{LocalName} eq 'material') { my $material_id = $self->_get_attribute($data, 'id') // '_'; $self->{_material} = $self->{_model}->set_material($material_id); } elsif ($data->{LocalName} eq 'metadata') { $self->{_metadata_type} = $self->_get_attribute($data, 'type'); $self->{_metadata_value} = ''; } elsif ($data->{LocalName} eq 'constellation') { $self->{_constellation} = 1; # we merge all constellations as we don't support more than one } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) { my $object_id = $self->_get_attribute($data, 'objectid'); $self->{_instances}{$object_id} ||= []; push @{ $self->{_instances}{$object_id} }, $self->{_instance} = {}; } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) { $self->{_instance_property} = $data->{LocalName}; } push @{$self->{_tree}}, $data->{LocalName}; } sub characters { my $self = shift; my $data = shift; if ($self->{_vertex} && $self->{_coordinate}) { $self->{_vertex}[ $xyz_index{$self->{_coordinate}} ] .= $data->{Data}; } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) { $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; } elsif ($self->{_metadata_type}) { $self->{_metadata_value} .= $data->{Data}; } elsif ($self->{_instance_property}) { $self->{_instance}{ $self->{_instance_property} } .= $data->{Data}; } } sub end_element { my $self = shift; my $data = shift; pop @{$self->{_tree}}; if ($data->{LocalName} eq 'object') { $self->{_object} = undef; $self->{_object_vertices} = undef; } elsif ($data->{LocalName} eq 'vertex') { push @{$self->{_object_vertices}}, $self->{_vertex}; $self->{_vertex} = undef; } elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) { $self->{_coordinate} = undef; } elsif ($data->{LocalName} eq 'volume') { $self->{_volume}->mesh->ReadFromPerl($self->{_object_vertices}, $self->{_volume_facets}); $self->{_volume}->mesh->repair; $self->{_volume} = undef; $self->{_volume_facets} = undef; } elsif ($data->{LocalName} eq 'triangle') { push @{$self->{_volume_facets}}, $self->{_triangle}; $self->{_triangle} = undef; } elsif (defined $self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { $self->{_vertex_idx} = undef; } elsif ($data->{LocalName} eq 'material') { $self->{_material} = undef; } elsif ($data->{LocalName} eq 'metadata') { my $value = $self->{_metadata_value}; if ($self->{_metadata_type} =~ /^slic3r\.(.+)/) { my $opt_key = $1; if (exists $Slic3r::Config::Options->{$opt_key}) { my $config; if ($self->{_material}) { $config = $self->{_material}->config; } elsif ($self->{_volume}) { $config = $self->{_volume}->config; } elsif ($self->{_object}) { $config = $self->{_object}->config; } $config->set_deserialize($opt_key, $value) if defined $config; } elsif ($opt_key eq 'modifier' && $self->{_volume}) { $self->{_volume}->set_modifier($value); } } elsif ($self->{_metadata_type} eq 'name' && $self->{_volume}) { $self->{_volume}->set_name($value); } elsif ($self->{_metadata_type} eq 'name' && $self->{_object}) { $self->{_object}->set_name($value); } elsif ($self->{_material}) { $self->{_material}->set_attribute($self->{_metadata_type}, $value); } $self->{_metadata_type} = undef; $self->{_metadata_value} = undef; } elsif ($data->{LocalName} eq 'constellation') { $self->{_constellation} = undef; } elsif ($data->{LocalName} eq 'instance') { $self->{_instance} = undef; } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) { $self->{_instance_property} = undef; } } sub end_document { my $self = shift; foreach my $object_id (keys %{ $self->{_instances} }) { my $new_object_id = $self->{_objects_map}{$object_id}; if (!defined $new_object_id) { warn "Undefined object $object_id referenced in constellation\n"; next; } foreach my $instance (@{ $self->{_instances}{$object_id} }) { $self->{_model}->objects->[$new_object_id]->add_instance( rotation => $instance->{rz} || 0, offset => Slic3r::Pointf->new($instance->{deltax} || 0, $instance->{deltay} || 0), ); } } } sub _get_attribute { my $self = shift; my ($data, $name) = @_; return +(map $_->{Value}, grep $_->{Name} eq $name, values %{$data->{Attributes}})[0]; } 1; Slic3r-1.2.9/lib/Slic3r/Format/OBJ.pm000066400000000000000000000015361254023100400167630ustar00rootroot00000000000000package Slic3r::Format::OBJ; use Moo; use File::Basename qw(basename); sub read_file { my $self = shift; my ($file) = @_; Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n"; my $vertices = []; my $facets = []; while (<$fh>) { if (/^v ([^ ]+)\s+([^ ]+)\s+([^ ]+)/) { push @$vertices, [$1, $2, $3]; } elsif (/^f (\d+).*? (\d+).*? (\d+).*?/) { push @$facets, [ $1-1, $2-1, $3-1 ]; } } close $fh; my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadFromPerl($vertices, $facets); $mesh->repair; my $model = Slic3r::Model->new; my $basename = basename($file); my $object = $model->add_object(input_file => $file, name => $basename); my $volume = $object->add_volume(mesh => $mesh, name => $basename); return $model; } 1; Slic3r-1.2.9/lib/Slic3r/Format/STL.pm000066400000000000000000000017141254023100400170110ustar00rootroot00000000000000package Slic3r::Format::STL; use Moo; use File::Basename qw(basename); sub read_file { my $self = shift; my ($file) = @_; my $path = Slic3r::encode_path($file); die "Failed to open $file\n" if !-e $path; my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadSTLFile($path); $mesh->repair; die "This STL file couldn't be read because it's empty.\n" if $mesh->facets_count == 0; my $model = Slic3r::Model->new; my $basename = basename($file); my $object = $model->add_object(input_file => $file, name => $basename); my $volume = $object->add_volume(mesh => $mesh, name => $basename); return $model; } sub write_file { my $self = shift; my ($file, $mesh, %params) = @_; $mesh = $mesh->mesh if $mesh->isa('Slic3r::Model'); my $path = Slic3r::encode_path($file); $params{binary} ? $mesh->write_binary($path) : $mesh->write_ascii($path); } 1; Slic3r-1.2.9/lib/Slic3r/GCode.pm000066400000000000000000000650101254023100400160770ustar00rootroot00000000000000package Slic3r::GCode; use Moo; use List::Util qw(min max first); use Slic3r::ExtrusionLoop ':roles'; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(epsilon scale unscale PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); # Origin of print coordinates expressed in unscaled G-code coordinates. # This affects the input arguments supplied to the extrude*() and travel_to() # methods. has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf->new }); has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new }); has 'writer' => (is => 'ro', default => sub { Slic3r::GCode::Writer->new }); has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new }); has 'ooze_prevention' => (is => 'rw', default => sub { Slic3r::GCode::OozePrevention->new }); has 'wipe' => (is => 'rw', default => sub { Slic3r::GCode::Wipe->new }); has 'avoid_crossing_perimeters' => (is => 'rw', default => sub { Slic3r::GCode::AvoidCrossingPerimeters->new }); has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); has 'enable_cooling_markers' => (is =>'rw', default => sub {0}); has 'layer_count' => (is => 'ro'); has 'layer_index' => (is => 'rw', default => sub {-1}); # just a counter has 'layer' => (is => 'rw'); has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); has 'volumetric_speed' => (is => 'rw', default => sub {0}); sub apply_print_config { my ($self, $print_config) = @_; $self->writer->apply_print_config($print_config); $self->config->apply_print_config($print_config); } sub set_extruders { my ($self, $extruder_ids) = @_; $self->writer->set_extruders($extruder_ids); # enable wipe path generation if any extruder has wipe enabled $self->wipe->enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); } sub set_origin { my ($self, $pointf) = @_; # if origin increases (goes towards right), last_pos decreases because it goes towards left my @translate = ( scale ($self->origin->x - $pointf->x), scale ($self->origin->y - $pointf->y), #- ); $self->last_pos->translate(@translate); $self->wipe->path->translate(@translate) if $self->wipe->path; $self->origin($pointf); } sub preamble { my ($self) = @_; my $gcode = $self->writer->preamble; # Perform a *silent* move to z_offset: we need this to initialize the Z # position of our writer object so that any initial lift taking place # before the first layer change will raise the extruder from the correct # initial Z instead of 0. $self->writer->travel_to_z($self->config->z_offset, ''); return $gcode; } sub change_layer { my ($self, $layer) = @_; $self->layer($layer); $self->layer_index($self->layer_index + 1); $self->first_layer($layer->id == 0); # avoid computing islands and overhangs if they're not needed if ($self->config->avoid_crossing_perimeters) { $self->avoid_crossing_perimeters->init_layer_mp( union_ex([ map @$_, @{$layer->slices} ], 1), ); } my $gcode = ""; if (defined $self->layer_count) { $gcode .= $self->writer->update_progress($self->layer_index, $self->layer_count); } my $z = $layer->print_z + $self->config->z_offset; # in unscaled coordinates if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) { $gcode .= $self->retract; } $gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer_index . ')'); # forget last wiping path as wiping after raising Z is pointless $self->wipe->path(undef); return $gcode; } sub extrude { my $self = shift; $_[0]->isa('Slic3r::ExtrusionLoop') ? $self->extrude_loop(@_) : $self->extrude_path(@_); } sub extrude_loop { 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 $loop = $loop->clone; # extrude all loops ccw my $was_clockwise = $loop->make_counter_clockwise; # 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->config->spiral_vase) { $loop->split_at($last_pos); } elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') { # simplify polygon in order to skip false positives in concave/convex detection # ($loop is always ccw as $polygon->simplify only works on ccw polygons) my $polygon = $loop->polygon; my @simplified = @{$polygon->simplify(scale $self->config->get_at('nozzle_diameter', $self->writer->extruder->id)/2)}; # restore original winding order so that concave and convex detection always happens # on the right/outer side of the polygon if ($was_clockwise) { $_->reverse for @simplified; } # concave vertices have priority my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified; # if no concave points were found, look for convex vertices @candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates; # retrieve the last start position for this object my $obj_ptr = 0; if (defined $self->layer) { $obj_ptr = $self->layer->object->ptr; if (defined $self->_seam_position->{$obj_ptr}) { $last_pos = $self->_seam_position->{$obj_ptr}; } } my $point; if ($self->config->seam_position eq 'nearest') { @candidates = @$polygon if !@candidates; $point = $last_pos->nearest_point(\@candidates); if (!$loop->split_at_vertex($point)) { # On 32-bit Linux, Clipper will change some point coordinates by 1 unit # while performing simplify_polygons(), thus split_at_vertex() won't # find them anymore. $loop->split_at($point); } } elsif (@candidates) { my @non_overhang = grep !$loop->has_overhang_point($_), @candidates; @candidates = @non_overhang if @non_overhang; $point = $last_pos->nearest_point(\@candidates); if (!$loop->split_at_vertex($point)) { $loop->split_at($point); } } else { $point = $last_pos->projection_onto_polygon($polygon); $loop->split_at($point); } $self->_seam_position->{$obj_ptr} = $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); } # 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 my $clip_length = $self->enable_loop_clipping ? scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : 0; # get paths my @paths = @{$loop->clip_end($clip_length)}; return '' if !@paths; # apply the small perimeter speed if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) { $speed //= $self->config->get_abs_value('small_perimeter_speed'); } # extrude along the path my $gcode = join '', map $self->_extrude_path($_, $description, $speed), @paths; # reset acceleration $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); $self->wipe->path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path # make a little move inwards before leaving loop 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 = ($paths[0][1], $paths[-1][-2]); @points = reverse @points if $was_clockwise; my $angle = $paths[0]->first_point->ccw_angle(@points) / 3; # turn left if contour, turn right if hole $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(@{$paths[0]->polyline}[0,1]); my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length); my $point = $first_segment->point_at($distance); $point->rotate($angle, $first_segment->a); # generate the travel move $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), "move inwards before travel"); } return $gcode; } sub extrude_path { my ($self, $path, $description, $speed) = @_; my $gcode = $self->_extrude_path($path, $description, $speed); # reset acceleration $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); return $gcode; } sub _extrude_path { my ($self, $path, $description, $speed) = @_; $path->simplify(&Slic3r::SCALED_RESOLUTION); # go to first point of extrusion path my $gcode = ""; { my $first_point = $path->first_point; $gcode .= $self->travel_to($first_point, $path->role, "move to first $description point") if !defined $self->last_pos || !$self->last_pos->coincides_with($first_point); } # compensate retraction $gcode .= $self->unretract; # adjust acceleration { my $acceleration; if ($self->config->first_layer_acceleration && $self->first_layer) { $acceleration = $self->config->first_layer_acceleration; } elsif ($self->config->perimeter_acceleration && $path->is_perimeter) { $acceleration = $self->config->perimeter_acceleration; } elsif ($self->config->bridge_acceleration && $path->is_bridge) { $acceleration = $self->config->bridge_acceleration; } elsif ($self->config->infill_acceleration && $path->is_infill) { $acceleration = $self->config->infill_acceleration; } else { $acceleration = $self->config->default_acceleration; } $gcode .= $self->writer->set_acceleration($acceleration); } # calculate extrusion length per distance unit my $e_per_mm = $self->writer->extruder->e_per_mm3 * $path->mm3_per_mm; $e_per_mm = 0 if !$self->writer->extrusion_axis; # set speed $speed //= -1; if ($speed == -1) { if ($path->role == EXTR_ROLE_PERIMETER) { $speed = $self->config->get_abs_value('perimeter_speed'); } elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) { $speed = $self->config->get_abs_value('external_perimeter_speed'); } elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) { $speed = $self->config->get_abs_value('bridge_speed'); } elsif ($path->role == EXTR_ROLE_FILL) { $speed = $self->config->get_abs_value('infill_speed'); } elsif ($path->role == EXTR_ROLE_SOLIDFILL) { $speed = $self->config->get_abs_value('solid_infill_speed'); } elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) { $speed = $self->config->get_abs_value('top_solid_infill_speed'); } elsif ($path->role == EXTR_ROLE_GAPFILL) { $speed = $self->config->get_abs_value('gap_fill_speed'); } else { die "Invalid speed"; } } if ($self->first_layer) { $speed = $self->config->get_abs_value_over('first_layer_speed', $speed); } if ($self->volumetric_speed != 0) { $speed ||= $self->volumetric_speed / $path->mm3_per_mm; } if ($self->config->max_volumetric_speed > 0) { # Cap speed with max_volumetric_speed anyway (even if user is not using autospeed) $speed = min( $speed, $self->config->max_volumetric_speed / $path->mm3_per_mm, ); } my $F = $speed * 60; # convert mm/sec to mm/min # extrude arc or line $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers; my $path_length = unscale $path->length; { my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id); $gcode .= $path->gcode($self->writer->extruder, $e_per_mm, $F, $self->origin->x - $extruder_offset->x, $self->origin->y - $extruder_offset->y, #- $self->writer->extrusion_axis, $self->config->gcode_comments ? " ; $description" : ""); if ($self->wipe->enable) { $self->wipe->path($path->polyline->clone); $self->wipe->path->reverse; } } $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge && $self->enable_cooling_markers; $self->last_pos($path->last_point); if ($self->config->cooling) { my $path_time = $path_length / $F * 60; $self->elapsed_time($self->elapsed_time + $path_time); } return $gcode; } # This method accepts $point in print coordinates. sub travel_to { my ($self, $point, $role, $comment) = @_; # Define the travel move as a line between current position and the taget point. # This is expressed in print coordinates, so it will need to be translated by # $self->origin in order to get G-code coordinates. my $travel = Slic3r::Polyline->new($self->last_pos, $point); # check whether a straight travel move would need retraction my $needs_retraction = $self->needs_retraction($travel, $role); # if a retraction would be needed, try to use avoid_crossing_perimeters to plan a # multi-hop travel path inside the configuration space if ($needs_retraction && $self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) { $travel = $self->avoid_crossing_perimeters->travel_to($self, $point); # check again whether the new travel path still needs a retraction $needs_retraction = $self->needs_retraction($travel, $role); } # Re-allow avoid_crossing_perimeters for the next travel moves $self->avoid_crossing_perimeters->disable_once(0); $self->avoid_crossing_perimeters->use_external_mp_once(0); # generate G-code for the travel move my $gcode = ""; $gcode .= $self->retract if $needs_retraction; # use G1 because we rely on paths being straight (G0 may make round paths) $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment) for @{$travel->lines}; return $gcode; } sub needs_retraction { my ($self, $travel, $role) = @_; if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)) { # skip retraction if the move is shorter than the configured threshold return 0; } if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) { # skip retraction if this is a travel move inside a support material island return 0; } if ($self->config->only_retract_when_crossing_perimeters && defined $self->layer) { if ($self->config->fill_density > 0 && $self->layer->any_internal_region_slice_contains_polyline($travel)) { # skip retraction if travel is contained in an internal slice *and* # internal infill is enabled (so that stringing is entirely not visible) return 0; } elsif ($self->layer->any_bottom_region_slice_contains_polyline($travel) && defined $self->layer->upper_layer && $self->layer->upper_layer->slices->contains_polyline($travel) && ($self->config->bottom_solid_layers >= 2 || $self->config->fill_density > 0)) { # skip retraction if travel is contained in an *infilled* bottom slice # but only if it's also covered by an *infilled* upper layer's slice # so that it's not visible from above (a bottom surface might not have an # upper slice in case of a thin membrane) return 0; } } # retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return 1; } sub retract { my ($self, $toolchange) = @_; return "" if !defined $self->writer->extruder; my $gcode = ""; # wipe (if it's enabled for this extruder and we have a stored wipe path) if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->path) { $gcode .= $self->wipe->wipe($self, $toolchange); } # The parent class will decide whether we need to perform an actual retraction # (the extruder might be already retracted fully or partially). We call these # methods even if we performed wipe, since this will ensure the entire retraction # length is honored in case wipe path was too short.p $gcode .= $toolchange ? $self->writer->retract_for_toolchange : $self->writer->retract; $gcode .= $self->writer->reset_e; $gcode .= $self->writer->lift if $self->writer->extruder->retract_length > 0 || $self->config->use_firmware_retraction; return $gcode; } sub unretract { my ($self) = @_; my $gcode = ""; $gcode .= $self->writer->unlift; $gcode .= $self->writer->unretract; return $gcode; } # convert a model-space scaled point into G-code coordinates sub point_to_gcode { my ($self, $point) = @_; my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id); return Slic3r::Pointf->new( ($point->x * &Slic3r::SCALING_FACTOR) + $self->origin->x - $extruder_offset->x, ($point->y * &Slic3r::SCALING_FACTOR) + $self->origin->y - $extruder_offset->y, #** ); } sub set_extruder { my ($self, $extruder_id) = @_; $self->placeholder_parser->set('current_extruder', $extruder_id); return "" if !$self->writer->need_toolchange($extruder_id); # if we are running a single-extruder setup, just set the extruder and return nothing if (!$self->writer->multiple_extruders) { return $self->writer->toolchange($extruder_id); } # prepend retraction on the current extruder my $gcode = $self->retract(1); # append custom toolchange G-code if (defined $self->writer->extruder && $self->config->toolchange_gcode) { $gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, { previous_extruder => $self->writer->extruder->id, next_extruder => $extruder_id, }); } # if ooze prevention is enabled, park current extruder in the nearest # standby point and set it to the standby temperature $gcode .= $self->ooze_prevention->pre_toolchange($self) if $self->ooze_prevention->enable && defined $self->writer->extruder; # append the toolchange command $gcode .= $self->writer->toolchange($extruder_id); # set the new extruder to the operating temperature $gcode .= $self->ooze_prevention->post_toolchange($self) if $self->ooze_prevention->enable; return $gcode; } package Slic3r::GCode::OozePrevention; use Moo; use Slic3r::Geometry qw(scale); has 'enable' => (is => 'rw', default => sub { 0 }); has 'standby_points' => (is => 'rw'); sub pre_toolchange { my ($self, $gcodegen) = @_; my $gcode = ""; # move to the nearest standby point if (@{$self->standby_points}) { # get current position in print coordinates my $pos = Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1]); my $standby_point = Slic3r::Pointf->new_unscale(@{$pos->nearest_point($self->standby_points)}); # We don't call $gcodegen->travel_to() because we don't need retraction (it was already # triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates # of the destination point must not be transformed by origin nor current extruder offset. $gcode .= $gcodegen->writer->travel_to_xy($standby_point, 'move to standby position'); } if ($gcodegen->config->standby_temperature_delta != 0) { my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); # we assume that heating is always slower than cooling, so no need to block $gcode .= $gcodegen->writer->set_temperature($temp + $gcodegen->config->standby_temperature_delta, 0); } return $gcode; } sub post_toolchange { my ($self, $gcodegen) = @_; my $gcode = ""; if ($gcodegen->config->standby_temperature_delta != 0) { my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); $gcode .= $gcodegen->writer->set_temperature($temp, 1); } return $gcode; } package Slic3r::GCode::Wipe; use Moo; use Slic3r::Geometry qw(scale); has 'enable' => (is => 'rw', default => sub { 0 }); has 'path' => (is => 'rw'); sub wipe { my ($self, $gcodegen, $toolchange) = @_; my $gcode = ""; # Reduce feedrate a bit; travel speed is often too high to move on existing material. # Too fast = ripping of existing material; too slow = short wipe path, thus more blob. my $wipe_speed = $gcodegen->writer->config->get('travel_speed') * 0.8; # get the retraction length my $length = $toolchange ? $gcodegen->writer->extruder->retract_length_toolchange : $gcodegen->writer->extruder->retract_length; if ($length) { # Calculate how long we need to travel in order to consume the required # amount of retraction. In other words, how far do we move in XY at $wipe_speed # for the time needed to consume retract_length at retract_speed? my $wipe_dist = scale($length / $gcodegen->writer->extruder->retract_speed * $wipe_speed); # Take the stored wipe path and replace first point with the current actual position # (they might be different, for example, in case of loop clipping). my $wipe_path = Slic3r::Polyline->new( $gcodegen->last_pos, @{$self->path}[1..$#{$self->path}], ); # $wipe_path->clip_end($wipe_path->length - $wipe_dist); # subdivide the retraction in segments 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 (TODO: test and/or better math for this) my $dE = $length * ($segment_length / $wipe_dist) * 0.95; $gcode .= $gcodegen->writer->set_speed($wipe_speed*60); $gcode .= $gcodegen->writer->extrude_to_xy( $gcodegen->point_to_gcode($line->b), -$dE, 'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''), ); $retracted += $dE; } $gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted); # prevent wiping again on same path $self->path(undef); } return $gcode; } package Slic3r::GCode::AvoidCrossingPerimeters; use Moo; has '_external_mp' => (is => 'rw'); has '_layer_mp' => (is => 'rw'); has 'use_external_mp' => (is => 'rw', default => sub {0}); has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move # this flag disables avoid_crossing_perimeters just for the next travel move # we enable it by default for the first travel move in print has 'disable_once' => (is => 'rw', default => sub {1}); sub init_external_mp { my ($self, $islands) = @_; $self->_external_mp(Slic3r::MotionPlanner->new($islands)); } sub init_layer_mp { my ($self, $islands) = @_; $self->_layer_mp(Slic3r::MotionPlanner->new($islands)); } sub travel_to { my ($self, $gcodegen, $point) = @_; if ($self->use_external_mp || $self->use_external_mp_once) { # get current origin set in $gcodegen # (the one that will be used to translate the G-code coordinates by) my $scaled_origin = Slic3r::Point->new_scale(@{$gcodegen->origin}); # represent last_pos in absolute G-code coordinates my $last_pos = $gcodegen->last_pos->clone; $last_pos->translate(@$scaled_origin); # represent $point in absolute G-code coordinates $point = $point->clone; $point->translate(@$scaled_origin); # calculate path my $travel = $self->_external_mp->shortest_path($last_pos, $point); # translate the path back into the shifted coordinate system that $gcodegen # is currently using for writing coordinates $travel->translate(@{$scaled_origin->negative}); return $travel; } else { return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point); } } 1; Slic3r-1.2.9/lib/Slic3r/GCode/000077500000000000000000000000001254023100400155375ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/GCode/ArcFitting.pm000066400000000000000000000205631254023100400201350ustar00rootroot00000000000000package Slic3r::GCode::ArcFitting; use Moo; use Slic3r::Geometry qw(X Y PI scale unscale epsilon scaled_epsilon deg2rad angle3points); extends 'Slic3r::GCode::Reader'; 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) = @_; 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) { # this is an extrusion segment # 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 (!$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 { # 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->path_to_gcode if $self->_path; return $new_gcode; } sub path_to_gcode { my ($self) = @_; my @chunks = $self->detect_arcs($self->_path); my $gcode = ""; 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; } } return $gcode; } sub detect_arcs { my ($self, $path) = @_; 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; Slic3r-1.2.9/lib/Slic3r/GCode/CoolingBuffer.pm000066400000000000000000000062351254023100400206270ustar00rootroot00000000000000package Slic3r::GCode::CoolingBuffer; use Moo; has 'config' => (is => 'ro', required => 1); # Slic3r::Config::Print has 'gcodegen' => (is => 'ro', required => 1); has 'gcode' => (is => 'rw', default => sub {""}); has 'elapsed_time' => (is => 'rw', default => sub {0}); has 'layer_id' => (is => 'rw'); has 'last_z' => (is => 'rw', default => sub { {} }); # obj_id => z (basically a 'last seen' table) has 'min_print_speed' => (is => 'lazy'); sub _build_min_print_speed { my $self = shift; return 60 * $self->config->min_print_speed; } sub append { my $self = shift; my ($gcode, $obj_id, $layer_id, $print_z) = @_; my $return = ""; if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) { $return = $self->flush; } $self->layer_id($layer_id); $self->last_z->{$obj_id} = $print_z; $self->gcode($self->gcode . $gcode); $self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time); $self->gcodegen->elapsed_time(0); return $return; } sub flush { my $self = shift; my $gcode = $self->gcode; my $elapsed = $self->elapsed_time; $self->gcode(""); $self->elapsed_time(0); $self->last_z({}); # reset the whole table otherwise we would compute overlapping times my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0; my $speed_factor = 1; if ($self->config->cooling) { Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $elapsed; if ($elapsed < $self->config->slowdown_below_layer_time) { $fan_speed = $self->config->max_fan_speed; $speed_factor = $elapsed / $self->config->slowdown_below_layer_time; } elsif ($elapsed < $self->config->fan_below_layer_time) { $fan_speed = $self->config->max_fan_speed - ($self->config->max_fan_speed - $self->config->min_fan_speed) * ($elapsed - $self->config->slowdown_below_layer_time) / ($self->config->fan_below_layer_time - $self->config->slowdown_below_layer_time); #/ } Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; if ($speed_factor < 1) { $gcode =~ s/^(?=.*? [XY])(?=.*? E)(?!;_WIPE)(?min_print_speed ? $self->min_print_speed : $new_speed) /gexm; } } $fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers; $gcode = $self->gcodegen->writer->set_fan($fan_speed) . $gcode; # bridge fan speed if (!$self->config->cooling || $self->config->bridge_fan_speed == 0 || $self->layer_id < $self->config->disable_fan_first_layers) { $gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm; } else { $gcode =~ s/^;_BRIDGE_FAN_START\n/ $self->gcodegen->writer->set_fan($self->config->bridge_fan_speed, 1) /gmex; $gcode =~ s/^;_BRIDGE_FAN_END\n/ $self->gcodegen->writer->set_fan($fan_speed, 1) /gmex; } $gcode =~ s/;_WIPE//g; return $gcode; } 1; Slic3r-1.2.9/lib/Slic3r/GCode/MotionPlanner.pm000066400000000000000000000253331254023100400206700ustar00rootroot00000000000000package Slic3r::GCode::MotionPlanner; use Moo; has 'islands' => (is => 'ro', required => 1); # arrayref of ExPolygons has 'internal' => (is => 'ro', default => sub { 1 }); has '_space' => (is => 'ro', default => sub { Slic3r::GCode::MotionPlanner::ConfigurationSpace->new }); has '_inner' => (is => 'ro', default => sub { [] }); # arrayref of ExPolygons use List::Util qw(first max); use Slic3r::Geometry qw(A B scale epsilon); use Slic3r::Geometry::Clipper qw(offset offset_ex diff_ex intersection_pl); # clearance (in mm) from the perimeters has '_inner_margin' => (is => 'ro', default => sub { scale 1 }); has '_outer_margin' => (is => 'ro', default => sub { scale 2 }); # this factor weigths the crossing of a perimeter # vs. the alternative path. a value of 5 means that # a perimeter will be crossed if the alternative path # is >= 5x the length of the straight line we could # follow if we decided to cross the perimeter. # a nearly-infinite value for this will only permit # perimeter crossing when there's no alternative path. use constant CROSSING_PENALTY => 20; use constant POINT_DISTANCE => 10; # unscaled # setup our configuration space sub BUILD { my $self = shift; my $point_distance = scale POINT_DISTANCE; my $nodes = $self->_space->nodes; my $edges = $self->_space->edges; # process individual islands for my $i (0 .. $#{$self->islands}) { my $expolygon = $self->islands->[$i]; # find external margin my $outer = offset([ @$expolygon ], +$self->_outer_margin); my @outer_points = map @{$_->equally_spaced_points($point_distance)}, @$outer; # add outer points to graph my $o_outer = $self->_space->add_nodes(@outer_points); # find pairs of visible outer points and add them to the graph for my $i (0 .. $#outer_points) { for my $j (($i+1) .. $#outer_points) { my ($a, $b) = ($outer_points[$i], $outer_points[$j]); my $line = Slic3r::Polyline->new($a, $b); # outer points are visible when their line has empty intersection with islands my $intersection = intersection_pl( [ $line ], [ map @$_, @{$self->islands} ], ); if (!@$intersection) { $self->_space->add_edge($i+$o_outer, $j+$o_outer, $line->length); } } } if ($self->internal) { # find internal margin my $inner = offset_ex([ @$expolygon ], -$self->_inner_margin); push @{ $self->_inner }, @$inner; my @inner_points = map @{$_->equally_spaced_points($point_distance)}, map @$_, @$inner; # add points to graph and get their offset my $o_inner = $self->_space->add_nodes(@inner_points); # find pairs of visible inner points and add them to the graph for my $i (0 .. $#inner_points) { for my $j (($i+1) .. $#inner_points) { my ($a, $b) = ($inner_points[$i], $inner_points[$j]); my $line = Slic3r::Line->new($a, $b); # turn $inner into an ExPolygonCollection and use $inner->contains_line() if (first { $_->contains_line($line) } @$inner) { $self->_space->add_edge($i+$o_inner, $j+$o_inner, $line->length); } } } # generate the stripe around slice contours my $contour = diff_ex( $outer, [ map @$_, @$inner ], ); # find pairs of visible points in this area and add them to the graph for my $i (0 .. $#inner_points) { for my $j (0 .. $#outer_points) { my ($a, $b) = ($inner_points[$i], $outer_points[$j]); my $line = Slic3r::Line->new($a, $b); # turn $contour into an ExPolygonCollection and use $contour->contains_line() if (first { $_->contains_line($line) } @$contour) { $self->_space->add_edge($i+$o_inner, $j+$o_outer, $line->length * CROSSING_PENALTY); } } } } } # since Perl has no infinity symbol and we don't want to overcomplicate # the Dijkstra algorithm with string constants or -1 values $self->_space->_infinity(10 * (max(map values %$_, values %{$self->_space->edges}) // 0)); if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("space.svg", no_arrows => 1, expolygons => $self->islands, lines => $self->_space->get_lines, points => $self->_space->nodes, ); printf "%d islands\n", scalar @{$self->islands}; eval "use Devel::Size"; print "MEMORY USAGE:\n"; printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->$_)/1024/1024 for qw(_space islands); printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->_space->$_)/1024/1024 for qw(nodes edges); printf " %-19s = %.1fMb\n", 'self', Devel::Size::total_size($self)/1024/1024; exit if $self->internal; } } sub shortest_path { my $self = shift; my ($from, $to) = @_; return Slic3r::Polyline->new($from, $to) if !@{$self->_space->nodes}; # create a temporary configuration space my $space = $self->_space->clone; # add from/to points to the temporary configuration space my $node_from = $self->_add_point_to_space($from, $space); my $node_to = $self->_add_point_to_space($to, $space); # compute shortest path my $path = $space->shortest_path($node_from, $node_to); if (!$path->is_valid) { Slic3r::debugf "Failed to compute shortest path.\n"; return Slic3r::Polyline->new($from, $to); } if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("path.svg", no_arrows => 1, expolygons => $self->islands, lines => $space->get_lines, red_points => [$from, $to], red_polylines => [$path], ); exit; } return $path; } # returns the index of the new node sub _add_point_to_space { my ($self, $point, $space) = @_; my $n = $space->add_nodes($point); # check whether we are inside an island or outside my $inside = defined first { $self->islands->[$_]->contains_point($point) } 0..$#{$self->islands}; # find candidates by checking visibility from $from to them foreach my $idx (0..$#{$space->nodes}) { 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->as_polyline ], [ map @$_, @{$self->islands} ], )}) ) { # $n ($point) and $idx are visible $space->add_edge($n, $idx, $line->length); } } # if we found no visibility, retry with larger margins if (!exists $space->edges->{$n} && $inside) { foreach my $idx (0..$#{$space->nodes}) { my $line = Slic3r::Line->new($point, $space->nodes->[$idx]); if (defined first { $_->contains_line($line) } @{$self->islands}) { # $n ($point) and $idx are visible $space->add_edge($n, $idx, $line->length); } } } warn "Temporary node is not visible from any other node" if !exists $space->edges->{$n}; return $n; } package Slic3r::GCode::MotionPlanner::ConfigurationSpace; use Moo; has 'nodes' => (is => 'rw', default => sub { [] }); # [ Point, ... ] has 'edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... } has '_infinity' => (is => 'rw'); sub clone { my $self = shift; return (ref $self)->new( nodes => [ map $_->clone, @{$self->nodes} ], edges => { map { $_ => { %{$self->edges->{$_}} } } keys %{$self->edges} }, _infinity => $self->_infinity, ); } sub nodes_count { my $self = shift; return scalar(@{ $self->nodes }); } sub add_nodes { my ($self, @nodes) = @_; my $offset = $self->nodes_count; push @{ $self->nodes }, @nodes; return $offset; } sub add_edge { my ($self, $a, $b, $dist) = @_; $self->edges->{$a}{$b} = $self->edges->{$b}{$a} = $dist; } sub shortest_path { my ($self, $node_from, $node_to) = @_; my $edges = $self->edges; my (%dist, %visited, %prev); $dist{$_} = $self->_infinity for keys %$edges; $dist{$node_from} = 0; my @queue = ($node_from); while (@queue) { my $u = -1; { # find node in @queue with smallest distance in %dist and has not been visited my $d = -1; foreach my $n (@queue) { next if $visited{$n}; if ($u == -1 || $dist{$n} < $d) { $u = $n; $d = $dist{$n}; } } } last if $u == $node_to; # remove $u from @queue @queue = grep $_ != $u, @queue; $visited{$u} = 1; # loop through neighbors of $u foreach my $v (keys %{ $edges->{$u} }) { my $alt = $dist{$u} + $edges->{$u}{$v}; if ($alt < $dist{$v}) { $dist{$v} = $alt; $prev{$v} = $u; if (!$visited{$v}) { push @queue, $v; } } } } my @points = (); { my $u = $node_to; while (exists $prev{$u}) { unshift @points, $self->nodes->[$u]; $u = $prev{$u}; } unshift @points, $self->nodes->[$node_from]; } return Slic3r::Polyline->new(@points); } # for debugging purposes sub get_lines { my $self = shift; my @lines = (); my %lines = (); for my $i (keys %{$self->edges}) { for my $j (keys %{$self->edges->{$i}}) { my $line_id = join '_', sort $i, $j; next if $lines{$line_id}; $lines{$line_id} = 1; push @lines, Slic3r::Line->new(map $self->nodes->[$_], $i, $j); } } return [@lines]; } 1; Slic3r-1.2.9/lib/Slic3r/GCode/PlaceholderParser.pm000066400000000000000000000020231254023100400214710ustar00rootroot00000000000000package Slic3r::GCode::PlaceholderParser; use strict; use warnings; sub new { # TODO: move this code to C++ constructor, remove this method my ($class) = @_; my $self = $class->_new; $self->apply_env_variables; return $self; } sub apply_env_variables { my ($self) = @_; $self->_single_set($_, $ENV{$_}) for grep /^SLIC3R_/, keys %ENV; } sub process { my ($self, $string, $extra) = @_; # extra variables have priority over the stored ones if ($extra) { my $regex = join '|', keys %$extra; $string =~ s/\[($regex)\]/$extra->{$1}/eg; } { my $regex = join '|', @{$self->_single_keys}; $string =~ s/\[($regex)\]/$self->_single_get("$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; } 1; Slic3r-1.2.9/lib/Slic3r/GCode/PressureRegulator.pm000066400000000000000000000070231254023100400215740ustar00rootroot00000000000000package Slic3r::GCode::PressureRegulator; use Moo; has 'config' => (is => 'ro', required => 1); has 'enable' => (is => 'rw', default => sub { 0 }); has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new }); has '_extrusion_axis' => (is => 'rw', default => sub { "E" }); has '_tool' => (is => 'rw', default => sub { 0 }); has '_last_print_F' => (is => 'rw', default => sub { 0 }); has '_advance' => (is => 'rw', default => sub { 0 }); # extra E injected use Slic3r::Geometry qw(epsilon); # Acknowledgements: # The advance algorithm was proposed by Matthew Roberts. # The initial work on this Slic3r feature was done by Luís Andrade (lluis) sub BUILD { my ($self) = @_; $self->reader->apply_print_config($self->config); $self->_extrusion_axis($self->config->get_extrusion_axis); } sub process { my $self = shift; my ($gcode, $flush) = @_; my $new_gcode = ""; $self->reader->parse($gcode, sub { my ($reader, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $self->_tool($1); } elsif ($info->{extruding} && $info->{dist_XY} > 0) { # This is a print move. my $F = $args->{F} // $reader->F; if ($F != $self->_last_print_F) { # We are setting a (potentially) new speed, so we calculate the new advance amount. # First calculate relative flow rate (mm of filament over mm of travel) my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY}; # Then calculate absolute flow rate (mm/sec of feedstock) my $flow_rate = $rel_flow_rate * $args->{F} / 60; # And finally calculate advance by using the user-configured K factor. my $new_advance = $self->config->pressure_advance * ($flow_rate**2); if (abs($new_advance - $self->_advance) > 1E-5) { my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance); $new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n", $self->_extrusion_axis, $new_E, $self->_unretract_speed; $new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E if !$self->config->use_relative_e_distances; $self->_advance($new_advance); } $self->_last_print_F($F); } } elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) { # We need to bring pressure to zero when retracting. $new_gcode .= $self->_discharge($args->{F}); } $new_gcode .= "$info->{raw}\n"; }); if ($flush) { $new_gcode .= $self->_discharge; } return $new_gcode; } sub _discharge { my ($self, $F) = @_; my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance; my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n", $self->_extrusion_axis, $new_E, $F // $self->_unretract_speed; $gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E if !$self->config->use_relative_e_distances; $self->_advance(0); return $gcode; } sub _unretract_speed { my ($self) = @_; return $self->config->get_at('retract_speed', $self->_tool) * 60; } 1; Slic3r-1.2.9/lib/Slic3r/GCode/Reader.pm000066400000000000000000000051741254023100400173060ustar00rootroot00000000000000package Slic3r::GCode::Reader; use Moo; has 'config' => (is => 'ro', default => sub { Slic3r::Config::GCode->new }); has 'X' => (is => 'rw', default => sub {0}); has 'Y' => (is => 'rw', default => sub {0}); has 'Z' => (is => 'rw', default => sub {0}); has 'E' => (is => 'rw', default => sub {0}); has 'F' => (is => 'rw', default => sub {0}); has '_extrusion_axis' => (is => 'rw', default => sub {"E"}); our $Verbose = 0; my @AXES = qw(X Y Z E); sub apply_print_config { my ($self, $print_config) = @_; $self->config->apply_print_config($print_config); $self->_extrusion_axis($self->config->get_extrusion_axis); } sub clone { my $self = shift; return (ref $self)->new( map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis'), ); } sub parse { my $self = shift; my ($gcode, $cb) = @_; foreach my $raw_line (split /\R+/, $gcode) { print "$raw_line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE}; my $line = $raw_line; $line =~ s/\s*;(.*)//; # strip comment my %info = (comment => $1, raw => $raw_line); # parse command my ($command, @args) = split /\s+/, $line; $command //= ''; my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args; # convert extrusion axis if (exists $args{ $self->_extrusion_axis }) { $args{E} = $args{ $self->_extrusion_axis }; } # check motion if ($command =~ /^G[01]$/) { foreach my $axis (@AXES) { if (exists $args{$axis}) { $self->$axis(0) if $axis eq 'E' && $self->config->use_relative_e_distances; $info{"dist_$axis"} = $args{$axis} - $self->$axis; $info{"new_$axis"} = $args{$axis}; } else { $info{"dist_$axis"} = 0; $info{"new_$axis"} = $self->$axis; } } $info{dist_XY} = sqrt(($info{dist_X}**2) + ($info{dist_Y}**2)); if (exists $args{E}) { if ($info{dist_E} > 0) { $info{extruding} = 1; } elsif ($info{dist_E} < 0) { $info{retracting} = 1 } } else { $info{travel} = 1; } } # run callback $cb->($self, $command, \%args, \%info); # update coordinates if ($command =~ /^(?:G[01]|G92)$/) { for my $axis (@AXES, 'F') { $self->$axis($args{$axis}) if exists $args{$axis}; } } # TODO: update temperatures } } 1; Slic3r-1.2.9/lib/Slic3r/GCode/SpiralVase.pm000066400000000000000000000057471254023100400201630ustar00rootroot00000000000000package Slic3r::GCode::SpiralVase; use Moo; has 'config' => (is => 'ro', required => 1); has 'enable' => (is => 'rw', default => sub { 0 }); has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new }); use Slic3r::Geometry qw(unscale); sub BUILD { my ($self) = @_; $self->reader->apply_print_config($self->config); } sub process_layer { my $self = shift; my ($gcode) = @_; # This post-processor relies on several assumptions: # - all layers are processed through it, including those that are not supposed # to be transformed, in order to update the reader with the XY positions # - each call to this method includes a full layer, with a single Z move # at the beginning # - each layer is composed by suitable geometry (i.e. a single complete loop) # - loops were not clipped before calling this method # if we're not going to modify G-code, just feed it to the reader # in order to update positions if (!$self->enable) { $self->reader->parse($gcode, sub {}); return $gcode; } # get total XY length for this layer by summing all extrusion moves my $total_layer_length = 0; my $layer_height = 0; my $z = undef; $self->reader->clone->parse($gcode, sub { my ($reader, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { if ($info->{extruding}) { $total_layer_length += $info->{dist_XY}; } elsif (exists $args->{Z}) { $layer_height += $info->{dist_Z}; $z //= $args->{Z}; } } }); #use XXX; XXX [ $gcode, $layer_height, $z, $total_layer_length ]; # remove layer height from initial Z $z -= $layer_height; my $new_gcode = ""; $self->reader->parse($gcode, sub { my ($reader, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && exists $args->{Z}) { # if this is the initial Z move of the layer, replace it with a # (redundant) move to the last Z of previous 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}) { # 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"; } # 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"; } }); return $new_gcode; } 1; Slic3r-1.2.9/lib/Slic3r/GCode/VibrationLimit.pm000066400000000000000000000040161254023100400210320ustar00rootroot00000000000000package Slic3r::GCode::VibrationLimit; use Moo; extends 'Slic3r::GCode::Reader'; has '_min_time' => (is => 'lazy'); has '_last_dir' => (is => 'ro', default => sub { [0,0] }); has '_dir_time' => (is => 'ro', default => sub { [0,0] }); # inspired by http://hydraraptor.blogspot.it/2010/12/frequency-limit.html use List::Util qw(max); sub _build__min_time { my ($self) = @_; return 1 / ($self->config->vibration_limit * 60); # in minutes } sub process { my $self = shift; my ($gcode) = @_; my $new_gcode = ""; $self->parse($gcode, sub { my ($reader, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{dist_XY} > 0) { my $point = Slic3r::Pointf->new($args->{X} // $reader->X, $args->{Y} // $reader->Y); my @dir = ( ($point->x <=> $reader->X), ($point->y <=> $reader->Y), #$ ); my $time = $info->{dist_XY} / ($args->{F} // $reader->F); # in minutes if ($time > 0) { my @pause = (); foreach my $axis (0..$#dir) { if ($dir[$axis] != 0 && $self->_last_dir->[$axis] != $dir[$axis]) { if ($self->_last_dir->[$axis] != 0) { # this axis is changing direction: check whether we need to pause if ($self->_dir_time->[$axis] < $self->_min_time) { push @pause, ($self->_min_time - $self->_dir_time->[$axis]); } } $self->_last_dir->[$axis] = $dir[$axis]; $self->_dir_time->[$axis] = 0; } $self->_dir_time->[$axis] += $time; } if (@pause) { $new_gcode .= sprintf "G4 P%d\n", max(@pause) * 60 * 1000; } } } $new_gcode .= $info->{raw} . "\n"; }); return $new_gcode; } 1; Slic3r-1.2.9/lib/Slic3r/GUI.pm000066400000000000000000000242601254023100400155440ustar00rootroot00000000000000package Slic3r::GUI; use strict; use warnings; use utf8; use File::Basename qw(basename); use FindBin; use Slic3r::GUI::AboutDialog; use Slic3r::GUI::BedShapeDialog; use Slic3r::GUI::BonjourBrowser; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::MainFrame; use Slic3r::GUI::Notifier; use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::2D; use Slic3r::GUI::Plater::2DToolpaths; use Slic3r::GUI::Plater::3D; use Slic3r::GUI::Plater::3DPreview; use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectCutDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::OverrideSettingsPanel; use Slic3r::GUI::Preferences; use Slic3r::GUI::ProgressStatusBar; use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::OptionsGroup::Field; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; our $have_LWP = eval "use LWP::UserAgent; 1"; use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog); use Wx::Event qw(EVT_IDLE EVT_COMMAND); use base 'Wx::App'; use constant FILE_WILDCARDS => { known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML', stl => 'STL files (*.stl)|*.stl;*.STL', obj => 'OBJ files (*.obj)|*.obj;*.OBJ', amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', ini => 'INI files *.ini|*.ini;*.INI', gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC', svg => 'SVG files *.svg|*.svg;*.SVG', }; use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)}; our $datadir; our $no_plater; our $mode; our $autosave; our @cb; our $Settings = { _ => { mode => 'simple', version_check => 1, autocenter => 1, background_processing => 1, }, }; our $have_button_icons = &Wx::wxVERSION_STRING =~ / (?:2\.9\.[1-9]|3\.)/; our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $small_font->SetPointSize(11) if !&Wx::wxMSW; our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $medium_font->SetPointSize(12); our $VERSION_CHECK_EVENT : shared = Wx::NewEventType; sub OnInit { my ($self) = @_; $self->SetAppName('Slic3r'); Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; $self->{notifier} = Slic3r::GUI::Notifier->new; # locate or create data directory $datadir ||= Slic3r::decode_path(Wx::StandardPaths::Get->GetUserDataDir); my $enc_datadir = Slic3r::encode_path($datadir); Slic3r::debugf "Data directory: %s\n", $datadir; # just checking for existence of $datadir is not enough: it may be an empty directory # supplied as argument to --datadir; in that case we should still run the wizard my $run_wizard = (-d $enc_datadir && -e "$enc_datadir/slic3r.ini") ? 0 : 1; foreach my $dir ($enc_datadir, "$enc_datadir/print", "$enc_datadir/filament", "$enc_datadir/printer") { next if -d $dir; if (!mkdir $dir) { my $error = "Slic3r was unable to create its data directory at $dir ($!)."; warn "$error\n"; fatal_error(undef, $error); } } # load settings my $last_version; if (-f "$enc_datadir/slic3r.ini") { my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") }; $Settings = $ini if $ini; $last_version = $Settings->{_}{version}; $Settings->{_}{mode} ||= 'expert'; $Settings->{_}{autocenter} //= 1; $Settings->{_}{background_processing} //= 1; } $Settings->{_}{version} = $Slic3r::VERSION; $self->save_settings; # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( mode => $mode // $Settings->{_}{mode}, no_plater => $no_plater, ); $self->SetTopWindow($frame); if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) { # user was running another Slic3r version on this computer if (!defined $last_version || $last_version =~ /^0\./) { show_info($self->{mainframe}, "Hello! Support material was improved since the " . "last version of Slic3r you used. It is strongly recommended to revert " . "your support material settings to the factory defaults and start from " . "those. Enjoy and provide feedback!", "Support Material"); } if (!defined $last_version || $last_version =~ /^(?:0|1\.[01])\./) { show_info($self->{mainframe}, "Hello! In this version a new Bed Shape option was " . "added. If the bed coordinates in the plater preview screen look wrong, go " . "to Print Settings and click the \"Set\" button next to \"Bed Shape\".", "Bed Shape"); } } $self->{mainframe}->config_wizard if $run_wizard; $self->check_version if $self->have_version_check && ($Settings->{_}{version_check} // 1) && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); EVT_IDLE($frame, sub { while (my $cb = shift @cb) { $cb->(); } }); EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub { my ($self, $event) = @_; my ($success, $response, $manual_check) = @{$event->GetData}; if ($success) { if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) { my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?", 'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal; Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES; } else { Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check; } $Settings->{_}{last_version_check} = time(); $self->save_settings; } else { Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check; } }); return 1; } sub about { my ($self) = @_; my $about = Slic3r::GUI::AboutDialog->new(undef); $about->ShowModal; $about->Destroy; } # static method accepting a wxWindow object as first parameter sub catch_error { my ($self, $cb, $message_dialog) = @_; if (my $err = $@) { $cb->() if $cb; $message_dialog ? $message_dialog->($err, 'Error', wxOK | wxICON_ERROR) : Slic3r::GUI::show_error($self, $err); return 1; } return 0; } # static method accepting a wxWindow object as first parameter sub show_error { my ($parent, $message) = @_; Wx::MessageDialog->new($parent, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal; } # static method accepting a wxWindow object as first parameter sub show_info { my ($parent, $message, $title) = @_; Wx::MessageDialog->new($parent, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal; } # static method accepting a wxWindow object as first parameter sub fatal_error { show_error(@_); exit 1; } # static method accepting a wxWindow object as first parameter sub warning_catcher { my ($self, $message_dialog) = @_; return sub { my $message = shift; return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/; my @params = ($message, 'Warning', wxOK | wxICON_WARNING); $message_dialog ? $message_dialog->(@params) : Wx::MessageDialog->new($self, @params)->ShowModal; }; } sub notify { my ($self, $message) = @_; my $frame = $self->GetTopWindow; # try harder to attract user attention on OS X $frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO) unless ($frame->IsActive); $self->{notifier}->notify($message); } sub save_settings { my ($self) = @_; Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings); } sub presets { my ($self, $section) = @_; my %presets = (); opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section") or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; foreach my $file (grep /\.ini$/i, readdir $dh) { $file = Slic3r::decode_path($file); my $name = basename($file); $name =~ s/\.ini$//; $presets{$name} = "$Slic3r::GUI::datadir/$section/$file"; } closedir $dh; return %presets; } sub have_version_check { my ($self) = @_; # return an explicit 0 return ($Slic3r::have_threads && $Slic3r::build && $have_LWP) || 0; } sub check_version { my ($self, $manual_check) = @_; Slic3r::debugf "Checking for updates...\n"; @_ = (); threads->create(sub { my $ua = LWP::UserAgent->new; $ua->timeout(10); my $response = $ua->get('http://slic3r.org/updatecheck'); Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT, threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ]))); Slic3r::thread_cleanup(); })->detach; } sub output_path { my ($self, $dir) = @_; return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path}) ? $Settings->{_}{last_output_path} : $dir; } sub open_model { my ($self, $window) = @_; my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } my @input_files = map Slic3r::decode_path($_), $dialog->GetPaths; $dialog->Destroy; return @input_files; } sub CallAfter { my ($self, $cb) = @_; push @cb, $cb; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/000077500000000000000000000000001254023100400152025ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/GUI/3DScene.pm000066400000000000000000001306111254023100400167660ustar00rootroot00000000000000package Slic3r::GUI::3DScene::Base; use strict; use warnings; use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); # must load OpenGL *before* Wx::GLCanvas use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); use Math::Trig qw(asin); use List::Util qw(reduce min max first); use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scale unscale scaled_epsilon); use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl); use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(_quat _dirty init enable_cutting enable_picking enable_moving on_viewport_changed on_hover on_select on_double_click on_right_click on_move volumes _sphi _stheta cutting_plane_z cut_lines_vertices bed_shape bed_triangles bed_grid_lines background origin _mouse_pos _hover_volume_idx _drag_volume_idx _drag_start_pos _drag_start_xy _dragged _camera_target _zoom ) ); use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; use constant GROUND_Z => -0.02; use constant DEFAULT_COLOR => [1,1,0]; use constant SELECTED_COLOR => [0,1,0,1]; use constant HOVER_COLOR => [0.4,0.9,0,1]; # make OpenGL::Array thread-safe { no warnings 'redefine'; *OpenGL::Array::CLONE_SKIP = sub { 1 }; } sub new { my ($class, $parent) = @_; # we request a depth buffer explicitely because it looks like it's not created by # default on Linux, causing transparency issues my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0]); $self->background(1); $self->_quat((0, 0, 0, 1)); $self->_stheta(45); $self->_sphi(45); $self->_zoom(1); # 3D point in model space $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); $self->reset_objects; EVT_PAINT($self, sub { my $dc = Wx::PaintDC->new($self); $self->Render($dc); }); EVT_SIZE($self, sub { $self->_dirty(1) }); EVT_IDLE($self, sub { return unless $self->_dirty; return if !$self->IsShownOnScreen; $self->Resize( $self->GetSizeWH ); $self->Refresh; }); EVT_MOUSEWHEEL($self, sub { my ($self, $e) = @_; # Calculate the zoom delta and apply it to the current zoom factor my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); $zoom = max(min($zoom, 4), -4); $zoom /= 10; $self->_zoom($self->_zoom / (1-$zoom)); # In order to zoom around the mouse point we need to translate # the camera target my $size = Slic3r::Pointf->new($self->GetSizeWH); my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- $self->_camera_target->translate( # ($pos - $size/2) represents the vector from the viewport center # to the mouse point. By multiplying it by $zoom we get the new, # transformed, length of such vector. # Since we want that point to stay fixed, we move our camera target # in the opposite direction by the delta of the length of such vector # ($zoom - 1). We then scale everything by 1/$self->_zoom since # $self->_camera_target is expressed in terms of model units. -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, 0, ) if 0; $self->on_viewport_changed->() if $self->on_viewport_changed; $self->_dirty(1); $self->Refresh; }); EVT_MOUSE_EVENTS($self, \&mouse_event); return $self; } sub mouse_event { my ($self, $e) = @_; my $pos = Slic3r::Pointf->new($e->GetPositionXY); if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; } elsif ($e->LeftDClick) { $self->on_double_click->() if $self->on_double_click; } elsif ($e->LeftDown || $e->RightDown) { # If user pressed left or right button we first check whether this happened # on a volume or not. my $volume_idx = $self->_hover_volume_idx // -1; # select volume in this 3D canvas if ($self->enable_picking) { $self->deselect_volumes; $self->select_volume($volume_idx); if ($volume_idx != -1) { my $group_id = $self->volumes->[$volume_idx]->select_group_id; my @volumes; if ($group_id != -1) { $self->select_volume($_) for grep $self->volumes->[$_]->select_group_id == $group_id, 0..$#{$self->volumes}; } } $self->Refresh; } # propagate event through callback $self->on_select->($volume_idx) if $self->on_select; if ($volume_idx != -1) { if ($e->LeftDown && $self->enable_moving) { $self->_drag_volume_idx($volume_idx); $self->_drag_start_pos($self->mouse_to_3d(@$pos)); } elsif ($e->RightDown) { # if right clicking on volume, propagate event through callback $self->on_right_click->($e->GetPosition) if $self->on_right_click; } } } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) { # get new position at the same Z of the initial click point my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY); my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z); # calculate the translation vector my $vector = $self->_drag_start_pos->vector_to($cur_pos); # get volume being dragged my $volume = $self->volumes->[$self->_drag_volume_idx]; # get all volumes belonging to the same group, if any my @volumes; if ($volume->drag_group_id == -1) { @volumes = ($volume); } else { @volumes = grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes}; } # apply new temporary volume origin and ignore Z $_->origin->translate($vector->x, $vector->y, 0) for @volumes; #,, $self->_drag_start_pos($cur_pos); $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging) { if ($e->LeftIsDown) { # if dragging over blank area with left button, rotate if (defined $self->_drag_start_pos) { my $orig = $self->_drag_start_pos; if (TURNTABLE_MODE) { $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- $self->_stheta(150) if $self->_stheta > 150; $self->_stheta(0) if $self->_stheta < 0; } else { my $size = $self->GetClientSize; my @quat = trackball( $orig->x / ($size->width / 2) - 1, 1 - $orig->y / ($size->height / 2), #/ $pos->x / ($size->width / 2) - 1, 1 - $pos->y / ($size->height / 2), #/ ); $self->_quat(mulquats($self->_quat, \@quat)); } $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Refresh; } $self->_drag_start_pos($pos); } elsif ($e->MiddleIsDown || $e->RightIsDown) { # if dragging over blank area with right button, translate if (defined $self->_drag_start_xy) { # get point in model space at Z = 0 my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane(0); my $orig = $self->mouse_ray(@{$self->_drag_start_xy})->intersect_plane(0); $self->_camera_target->translate( @{$orig->vector_to($cur_pos)->negative}, ); $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Refresh; } $self->_drag_start_xy($pos); } } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) { if ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) { # get all volumes belonging to the same group, if any my @volume_idxs; my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id; if ($group_id == -1) { @volume_idxs = ($self->_drag_volume_idx); } else { @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id, 0..$#{$self->volumes}; } $self->on_move->(@volume_idxs); } $self->_drag_volume_idx(undef); $self->_drag_start_pos(undef); $self->_drag_start_xy(undef); $self->_dragged(undef); } elsif ($e->Moving) { $self->_mouse_pos($pos); $self->Refresh; } else { $e->Skip(); } } sub reset_objects { my ($self) = @_; $self->volumes([]); $self->_dirty(1); } sub set_viewport_from_scene { my ($self, $scene) = @_; $self->_sphi($scene->_sphi); $self->_stheta($scene->_stheta); $self->_camera_target($scene->_camera_target); $self->_zoom($scene->_zoom); $self->_quat($scene->_quat); $self->_dirty(1); } sub zoom_to_bounding_box { my ($self, $bb) = @_; # calculate the zoom factor needed to adjust viewport to # bounding box my $max_size = max(@{$bb->size}) * 2; my $min_viewport_size = min($self->GetSizeWH); $self->_zoom($min_viewport_size / $max_size); # center view around bounding box center $self->_camera_target($bb->center); $self->on_viewport_changed->() if $self->on_viewport_changed; } sub zoom_to_bed { my ($self) = @_; if ($self->bed_shape) { $self->zoom_to_bounding_box($self->bed_bounding_box); } } sub zoom_to_volume { my ($self, $volume_idx) = @_; my $volume = $self->volumes->[$volume_idx]; my $bb = $volume->transformed_bounding_box; $self->zoom_to_bounding_box($bb); } sub zoom_to_volumes { my ($self) = @_; $self->zoom_to_bounding_box($self->volumes_bounding_box); } sub volumes_bounding_box { my ($self) = @_; my $bb = Slic3r::Geometry::BoundingBoxf3->new; $bb->merge($_->transformed_bounding_box) for @{$self->volumes}; return $bb; } sub bed_bounding_box { my ($self) = @_; my $bb = Slic3r::Geometry::BoundingBoxf3->new; $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape}; return $bb; } sub max_bounding_box { my ($self) = @_; my $bb = $self->bed_bounding_box; $bb->merge($self->volumes_bounding_box); return $bb; } sub set_auto_bed_shape { my ($self, $bed_shape) = @_; # draw a default square bed around object center my $max_size = max(@{ $self->volumes_bounding_box->size }); my $center = $self->volumes_bounding_box->center; $self->set_bed_shape([ [ $center->x - $max_size, $center->y - $max_size ], #-- [ $center->x + $max_size, $center->y - $max_size ], #-- [ $center->x + $max_size, $center->y + $max_size ], #++ [ $center->x - $max_size, $center->y + $max_size ], #++ ]); $self->origin(Slic3r::Pointf->new(@$center[X,Y])); } sub set_bed_shape { my ($self, $bed_shape) = @_; $self->bed_shape($bed_shape); # triangulate bed my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]); my $bed_bb = $expolygon->bounding_box; { my @points = (); foreach my $triangle (@{ $expolygon->triangulate }) { push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; #)) } $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points)); } { my @polylines = (); for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) { push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]); } for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) { push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]); } # clip with a slightly grown expolygon because our lines lay on the contours and # may get erroneously clipped my @lines = map Slic3r::Line->new(@$_[0,-1]), @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])}; # append bed contours push @lines, map @{$_->lines}, @$expolygon; my @points = (); foreach my $line (@lines) { push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #)) } $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points)); } $self->origin(Slic3r::Pointf->new(0,0)); } sub deselect_volumes { my ($self) = @_; $_->selected(0) for @{$self->volumes}; } sub select_volume { my ($self, $volume_idx) = @_; $self->volumes->[$volume_idx]->selected(1) if $volume_idx != -1; } sub SetCuttingPlane { my ($self, $z) = @_; $self->cutting_plane_z($z); # perform cut and cache section lines my @verts = (); foreach my $volume (@{$self->volumes}) { foreach my $volume (@{$self->volumes}) { next if !$volume->mesh; my $expolygons = $volume->mesh->slice([ $z - $volume->origin->z ])->[0]; $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { push @verts, ( unscale($line->a->x), unscale($line->a->y), $z, #)) unscale($line->b->x), unscale($line->b->y), $z, #)) ); } } } $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts)); } # Given an axis and angle, compute quaternion. sub axis_to_quat { my ($ax, $phi) = @_; my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax)); my @q = map { $_ * (1 / $lena) } @$ax; @q = map { $_ * sin($phi / 2.0) } @q; $q[$#q + 1] = cos($phi / 2.0); return @q; } # Project a point on the virtual trackball. # If it is inside the sphere, map it to the sphere, if it outside map it # to a hyperbola. sub project_to_sphere { my ($r, $x, $y) = @_; my $d = sqrt($x * $x + $y * $y); if ($d < $r * 0.70710678118654752440) { # Inside sphere return sqrt($r * $r - $d * $d); } else { # On hyperbola my $t = $r / 1.41421356237309504880; return $t * $t / $d; } } sub cross { my ($v1, $v2) = @_; return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1], @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2], @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]); } # Simulate a track-ball. Project the points onto the virtual trackball, # then figure out the axis of rotation, which is the cross product of # P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a # deformed trackball-- is a trackball in the center, but is deformed # into a hyperbolic sheet of rotation away from the center. # It is assumed that the arguments to this routine are in the range # (-1.0 ... 1.0). sub trackball { my ($p1x, $p1y, $p2x, $p2y) = @_; if ($p1x == $p2x && $p1y == $p2y) { # zero rotation return (0.0, 0.0, 0.0, 1.0); } # First, figure out z-coordinates for projection of P1 and P2 to # deformed sphere my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y)); my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y)); # axis of rotation (cross product of P1 and P2) my @a = cross(\@p2, \@p1); # Figure out how much to rotate around that axis. my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1); my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE); # Avoid problems with out-of-control values... $t = 1.0 if ($t > 1.0); $t = -1.0 if ($t < -1.0); my $phi = 2.0 * asin($t); return axis_to_quat(\@a, $phi); } # Build a rotation matrix, given a quaternion rotation. sub quat_to_rotmatrix { my ($q) = @_; my @m = (); $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]); $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]); $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]); $m[3] = 0.0; $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]); $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]); $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]); $m[7] = 0.0; $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]); $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]); $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]); $m[11] = 0.0; $m[12] = 0.0; $m[13] = 0.0; $m[14] = 0.0; $m[15] = 1.0; return @m; } sub mulquats { my ($q1, $rq) = @_; return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1], @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2], @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0], @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2]) } sub mouse_to_3d { my ($self, $x, $y, $z) = @_; my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items $y = $viewport[3] - $y; $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport); return Slic3r::Pointf3->new(@projected); } sub mouse_ray { my ($self, $x, $y) = @_; return Slic3r::Linef3->new( $self->mouse_to_3d($x, $y, 0), $self->mouse_to_3d($x, $y, 1), ); } sub GetContext { my ($self) = @_; if (Wx::wxVERSION >= 2.009) { return $self->{context} ||= Wx::GLContext->new($self); } else { return $self->SUPER::GetContext; } } sub SetCurrent { my ($self, $context) = @_; if (Wx::wxVERSION >= 2.009) { return $self->SUPER::SetCurrent($context); } else { return $self->SUPER::SetCurrent; } } sub Resize { my ($self, $x, $y) = @_; return unless $self->GetContext; $self->_dirty(0); $self->SetCurrent($self->GetContext); glViewport(0, 0, $x, $y); $x /= $self->_zoom; $y /= $self->_zoom; glMatrixMode(GL_PROJECTION); glLoadIdentity(); my $depth = 10 * max(@{ $self->max_bounding_box->size }); glOrtho( -$x/2, $x/2, -$y/2, $y/2, -$depth, 2*$depth, ); glMatrixMode(GL_MODELVIEW); } sub InitGL { my $self = shift; return if $self->init; return unless $self->GetContext; $self->init(1); glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); glEnable(GL_DEPTH_TEST); glClearDepth(1.0); glDepthFunc(GL_LEQUAL); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); # Set antialiasing/multisampling glDisable(GL_LINE_SMOOTH); glDisable(GL_POLYGON_SMOOTH); glEnable(GL_MULTISAMPLE); # ambient lighting glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); # light from camera glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1); glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1); # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. glShadeModel(GL_SMOOTH); glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1); glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1); glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50); glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9); # A handy trick -- have surface material mirror the color. glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); glEnable(GL_MULTISAMPLE); } sub Render { my ($self, $dc) = @_; # prevent calling SetCurrent() when window is not shown yet return unless $self->IsShownOnScreen; return unless my $context = $self->GetContext; $self->SetCurrent($context); $self->InitGL; glClearColor(1, 1, 1, 1); glClearDepth(1); glDepthFunc(GL_LESS); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (TURNTABLE_MODE) { glRotatef(-$self->_stheta, 1, 0, 0); # pitch glRotatef($self->_sphi, 0, 0, 1); # yaw } else { my @rotmat = quat_to_rotmatrix($self->quat); glMultMatrixd_p(@rotmat[0..15]); } glTranslatef(@{ $self->_camera_target->negative }); # light from above glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0); glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1); glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1); if ($self->enable_picking) { glDisable(GL_LIGHTING); $self->draw_volumes(1); glFlush(); glFinish(); if (my $pos = $self->_mouse_pos) { my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ]; my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; $self->_hover_volume_idx(undef); $_->hover(0) for @{$self->volumes}; if ($volume_idx <= $#{$self->volumes}) { $self->_hover_volume_idx($volume_idx); $self->volumes->[$volume_idx]->hover(1); my $group_id = $self->volumes->[$volume_idx]->select_group_id; if ($group_id != -1) { $_->hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes}; } $self->on_hover->($volume_idx) if $self->on_hover; } } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glFlush(); glFinish(); glEnable(GL_LIGHTING); } # draw fixed background if ($self->background) { glDisable(GL_LIGHTING); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glBegin(GL_QUADS); glColor3f(0.0,0.0,0.0); glVertex2f(-1.0,-1.0); glVertex2f(1,-1.0); glColor3f(10/255,98/255,144/255); glVertex2f(1, 1); glVertex2f(-1.0, 1); glEnd(); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glEnable(GL_LIGHTING); } # draw ground and axes glDisable(GL_LIGHTING); # draw ground my $ground_z = GROUND_Z; if ($self->bed_triangles) { glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); glColor4f(0.8, 0.6, 0.5, 0.4); glNormal3d(0,0,1); glVertexPointer_p(3, $self->bed_triangles); glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); glDisableClientState(GL_VERTEX_ARRAY); # we need depth test for grid, otherwise it would disappear when looking # the object from below glEnable(GL_DEPTH_TEST); # draw grid glLineWidth(3); glColor4f(0.2, 0.2, 0.2, 0.4); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer_p(3, $self->bed_grid_lines); glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_BLEND); } my $volumes_bb = $self->volumes_bounding_box; { # draw axes # disable depth testing so that axes are not covered by ground glDisable(GL_DEPTH_TEST); my $origin = $self->origin; my $axis_len = max( 0.3 * max(@{ $self->bed_bounding_box->size }), 2 * max(@{ $volumes_bb->size }), ); glLineWidth(2); glBegin(GL_LINES); # draw line for x axis glColor3f(1, 0, 0); glVertex3f(@$origin, $ground_z); glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,, # draw line for y axis glColor3f(0, 1, 0); glVertex3f(@$origin, $ground_z); glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ glEnd(); # draw line for Z axis # (re-enable depth test so that axis is correctly shown when objects are behind it) glEnable(GL_DEPTH_TEST); glBegin(GL_LINES); glColor3f(0, 0, 1); glVertex3f(@$origin, $ground_z); glVertex3f(@$origin, $ground_z+$axis_len); glEnd(); } glEnable(GL_LIGHTING); # draw objects $self->draw_volumes; # draw cutting plane if (defined $self->cutting_plane_z) { my $plane_z = $self->cutting_plane_z; my $bb = $volumes_bb; glDisable(GL_CULL_FACE); glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_QUADS); glColor4f(0.8, 0.8, 0.8, 0.5); glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); glEnd(); glEnable(GL_CULL_FACE); glDisable(GL_BLEND); } glFlush(); $self->SwapBuffers(); } sub draw_volumes { my ($self, $fakecolor) = @_; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; glPushMatrix(); glTranslatef(@{$volume->origin}); if ($fakecolor) { my $r = ($volume_idx & 0x000000FF) >> 0; my $g = ($volume_idx & 0x0000FF00) >> 8; my $b = ($volume_idx & 0x00FF0000) >> 16; glColor4f($r/255.0, $g/255.0, $b/255.0, 1); } elsif ($volume->selected) { glColor4f(@{ &SELECTED_COLOR }); } elsif ($volume->hover) { glColor4f(@{ &HOVER_COLOR }); } else { glColor4f(@{ $volume->color }); } my @sorted_z = (); my ($min_z, $max_z); if ($volume->range && $volume->offsets) { @sorted_z = sort { $a <=> $b } keys %{$volume->offsets}; ($min_z, $max_z) = @{$volume->range}; $min_z = first { $_ >= $min_z } @sorted_z; $max_z = first { $_ > $max_z } @sorted_z; } glCullFace(GL_BACK); if ($volume->qverts) { my ($min_offset, $max_offset); if (defined $min_z) { $min_offset = $volume->offsets->{$min_z}->[0]; } if (defined $max_z) { $max_offset = $volume->offsets->{$max_z}->[0]; } $min_offset //= 0; $max_offset //= $volume->qverts->size; glVertexPointer_c(3, GL_FLOAT, 0, $volume->qverts->verts_ptr); glNormalPointer_c(GL_FLOAT, 0, $volume->qverts->norms_ptr); glDrawArrays(GL_QUADS, $min_offset / 3, ($max_offset-$min_offset) / 3); } if ($volume->tverts) { my ($min_offset, $max_offset); if (defined $min_z) { $min_offset = $volume->offsets->{$min_z}->[1]; } if (defined $max_z) { $max_offset = $volume->offsets->{$max_z}->[1]; } $min_offset //= 0; $max_offset //= $volume->tverts->size; glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr); glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr); glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3); } glPopMatrix(); } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); if (defined $self->cutting_plane_z) { glLineWidth(2); glColor3f(0, 0, 0); glVertexPointer_p(3, $self->cut_lines_vertices); glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); } glDisableClientState(GL_VERTEX_ARRAY); } package Slic3r::GUI::3DScene::Volume; use Moo; has 'bounding_box' => (is => 'ro', required => 1); has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) }); has 'color' => (is => 'ro', required => 1); has 'select_group_id' => (is => 'rw', default => sub { -1 }); has 'drag_group_id' => (is => 'rw', default => sub { -1 }); has 'selected' => (is => 'rw', default => sub { 0 }); has 'hover' => (is => 'rw', default => sub { 0 }); has 'range' => (is => 'rw'); # geometric data has 'qverts' => (is => 'rw'); # GLVertexArray object has 'tverts' => (is => 'rw'); # GLVertexArray object has 'mesh' => (is => 'rw'); # only required for cut contours has 'offsets' => (is => 'rw'); # [ z => [ qverts_idx, tverts_idx ] ] sub transformed_bounding_box { my ($self) = @_; my $bb = $self->bounding_box; $bb->translate(@{$self->origin}); return $bb; } package Slic3r::GUI::3DScene; use base qw(Slic3r::GUI::3DScene::Base); use OpenGL qw(:glconstants :gluconstants :glufunctions); use List::Util qw(first min max); use Slic3r::Geometry qw(scale unscale epsilon); use Slic3r::Print::State ':steps'; use constant COLORS => [ [1,1,0,1], [1,0.5,0.5,1], [0.5,1,0.5,1], [0.5,0.5,1,1] ]; __PACKAGE__->mk_accessors(qw( color_by select_by drag_by volumes_by_object _objects_by_volumes )); sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->color_by('volume'); # object | volume $self->select_by('object'); # object | volume | instance $self->drag_by('instance'); # object | instance $self->volumes_by_object({}); # obj_idx => [ volume_idx, volume_idx ... ] $self->_objects_by_volumes({}); # volume_idx => [ obj_idx, instance_idx ] return $self; } sub load_object { my ($self, $model, $obj_idx, $instance_idxs) = @_; my $model_object; if ($model->isa('Slic3r::Model::Object')) { $model_object = $model; $model = $model_object->model; $obj_idx = 0; } else { $model_object = $model->get_object($obj_idx); } $instance_idxs ||= [0..$#{$model_object->instances}]; my @volumes_idx = (); foreach my $volume_idx (0..$#{$model_object->volumes}) { my $volume = $model_object->volumes->[$volume_idx]; foreach my $instance_idx (@$instance_idxs) { my $instance = $model_object->instances->[$instance_idx]; my $mesh = $volume->mesh->clone; $instance->transform_mesh($mesh); my $color_idx; if ($self->color_by eq 'volume') { $color_idx = $volume_idx; } elsif ($self->color_by eq 'object') { $color_idx = $obj_idx; } my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ]; $color->[3] = $volume->modifier ? 0.5 : 1; push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new( bounding_box => $mesh->bounding_box, color => $color, ); $v->mesh($mesh) if $self->enable_cutting; if ($self->select_by eq 'object') { $v->select_group_id($obj_idx*1000000); } elsif ($self->select_by eq 'volume') { $v->select_group_id($obj_idx*1000000 + $volume_idx*1000); } elsif ($self->select_by eq 'instance') { $v->select_group_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx); } if ($self->drag_by eq 'object') { $v->drag_group_id($obj_idx*1000); } elsif ($self->drag_by eq 'instance') { $v->drag_group_id($obj_idx*1000 + $instance_idx); } push @volumes_idx, my $scene_volume_idx = $#{$self->volumes}; $self->_objects_by_volumes->{$scene_volume_idx} = [ $obj_idx, $volume_idx, $instance_idx ]; my $verts = Slic3r::GUI::_3DScene::GLVertexArray->new; $verts->load_mesh($mesh); $v->tverts($verts); } } $self->volumes_by_object->{$obj_idx} = [@volumes_idx]; return @volumes_idx; } sub load_print_object_slices { my ($self, $object) = @_; my @verts = (); my @norms = (); my @quad_verts = (); my @quad_norms = (); foreach my $layer (@{$object->layers}) { my $gap = 0; my $top_z = $layer->print_z; my $bottom_z = $layer->print_z - $layer->height + $gap; foreach my $copy (@{ $object->_shifted_copies }) { { my @expolygons = map $_->clone, @{$layer->slices}; $_->translate(@$copy) for @expolygons; $self->_expolygons_to_verts(\@expolygons, $layer->print_z, \@verts, \@norms); } foreach my $slice (@{$layer->slices}) { foreach my $polygon (@$slice) { foreach my $line (@{$polygon->lines}) { $line->translate(@$copy); push @quad_norms, (0,0,-1), (0,0,-1); push @quad_verts, (map unscale($_), @{$line->a}), $bottom_z; push @quad_verts, (map unscale($_), @{$line->b}), $bottom_z; push @quad_norms, (0,0,1), (0,0,1); push @quad_verts, (map unscale($_), @{$line->b}), $top_z; push @quad_verts, (map unscale($_), @{$line->a}), $top_z; # We'll use this for the middle normal when using 4 quads: #my $xy_normal = $line->normal; #$_xynormal->scale(1/$line->length); } } } } } my $obb = $object->bounding_box; my $bb = Slic3r::Geometry::BoundingBoxf3->new; $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->min_point}, 0)); $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->max_point}, $object->size->z)); push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new( bounding_box => $bb, color => COLORS->[0], verts => OpenGL::Array->new_list(GL_FLOAT, @verts), norms => OpenGL::Array->new_list(GL_FLOAT, @norms), quad_verts => OpenGL::Array->new_list(GL_FLOAT, @quad_verts), quad_norms => OpenGL::Array->new_list(GL_FLOAT, @quad_norms), ); } sub load_print_toolpaths { my ($self, $print) = @_; return if !$print->step_done(STEP_SKIRT); return if !$print->step_done(STEP_BRIM); return if !$print->has_skirt && $print->config->brim_width == 0; my $qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my %offsets = (); # print_z => [ qverts, tverts ] my $skirt_height = 0; # number of layers if ($print->has_infinite_skirt) { $skirt_height = $print->total_layer_count; } else { $skirt_height = min($print->config->skirt_height, $print->total_layer_count); } $skirt_height ||= 1 if $print->config->brim_width > 0; # get first $skirt_height layers (maybe this should be moved to a PrintObject method?) my $object0 = $print->get_object(0); my @layers = (); push @layers, map $object0->get_layer($_-1), 1..min($skirt_height, $object0->layer_count); push @layers, map $object0->get_support_layer($_-1), 1..min($skirt_height, $object0->support_layer_count); @layers = sort { $a->print_z <=> $b->print_z } @layers; @layers = @layers[0..($skirt_height-1)]; foreach my $i (0..($skirt_height-1)) { my $top_z = $layers[$i]->print_z; $offsets{$top_z} = [$qverts->size, $tverts->size]; if ($i == 0) { $self->_extrusionentity_to_verts($print->brim, $top_z, Slic3r::Point->new(0,0), $qverts, $tverts); } $self->_extrusionentity_to_verts($print->skirt, $top_z, Slic3r::Point->new(0,0), $qverts, $tverts); } my $bb = Slic3r::Geometry::BoundingBoxf3->new; { my $pbb = $print->bounding_box; $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$pbb->min_point})); $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$pbb->max_point})); } push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( bounding_box => $bb, color => COLORS->[2], qverts => $qverts, tverts => $tverts, offsets => { %offsets }, ); } sub load_print_object_toolpaths { my ($self, $object) = @_; my $perim_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $perim_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $infill_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $infill_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $support_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $support_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my %perim_offsets = (); # print_z => [ qverts, tverts ] my %infill_offsets = (); my %support_offsets = (); # order layers by print_z my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; foreach my $layer (@layers) { my $top_z = $layer->print_z; if (!exists $perim_offsets{$top_z}) { $perim_offsets{$top_z} = [ $perim_qverts->size, $perim_tverts->size, ]; $infill_offsets{$top_z} = [ $infill_qverts->size, $infill_tverts->size, ]; $support_offsets{$top_z} = [ $support_qverts->size, $support_tverts->size, ]; } foreach my $copy (@{ $object->_shifted_copies }) { foreach my $layerm (@{$layer->regions}) { if ($object->step_done(STEP_PERIMETERS)) { $self->_extrusionentity_to_verts($layerm->perimeters, $top_z, $copy, $perim_qverts, $perim_tverts); } if ($object->step_done(STEP_INFILL)) { $self->_extrusionentity_to_verts($layerm->fills, $top_z, $copy, $infill_qverts, $infill_tverts); } } if ($layer->isa('Slic3r::Layer::Support') && $object->step_done(STEP_SUPPORTMATERIAL)) { $self->_extrusionentity_to_verts($layer->support_fills, $top_z, $copy, $support_qverts, $support_tverts); $self->_extrusionentity_to_verts($layer->support_interface_fills, $top_z, $copy, $support_qverts, $support_tverts); } } } my $obb = $object->bounding_box; my $bb = Slic3r::Geometry::BoundingBoxf3->new; foreach my $copy (@{ $object->_shifted_copies }) { my $cbb = $obb->clone; $cbb->translate(@$copy); $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0)); $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z)); } push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( bounding_box => $bb, color => COLORS->[0], qverts => $perim_qverts, tverts => $perim_tverts, offsets => { %perim_offsets }, ); push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( bounding_box => $bb, color => COLORS->[1], qverts => $infill_qverts, tverts => $infill_tverts, offsets => { %infill_offsets }, ); push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( bounding_box => $bb, color => COLORS->[2], qverts => $support_qverts, tverts => $support_tverts, offsets => { %support_offsets }, ); } sub set_toolpaths_range { my ($self, $min_z, $max_z) = @_; foreach my $volume (@{$self->volumes}) { $volume->range([ $min_z, $max_z ]); } } sub _expolygons_to_verts { my ($self, $expolygons, $z, $verts, $norms) = @_; my $tess = gluNewTess(); gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_VERTEX, sub { my ($x, $y, $z) = @_; push @$verts, $x, $y, $z; push @$norms, (0,0,1), (0,0,1), (0,0,1); }); gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); foreach my $expolygon (@$expolygons) { gluTessBeginPolygon($tess); foreach my $polygon (@$expolygon) { gluTessBeginContour($tess); gluTessVertex_p($tess, (map unscale($_), @$_), $z) for @$polygon; gluTessEndContour($tess); } gluTessEndPolygon($tess); } gluDeleteTess($tess); } sub _extrusionentity_to_verts { my ($self, $entity, $top_z, $copy, $qverts, $tverts) = @_; my ($lines, $widths, $heights, $closed); if ($entity->isa('Slic3r::ExtrusionPath::Collection')) { $self->_extrusionentity_to_verts($_, $top_z, $copy, $qverts, $tverts) for @$entity; return; } elsif ($entity->isa('Slic3r::ExtrusionPath')) { my $polyline = $entity->polyline->clone; $polyline->remove_duplicate_points; $polyline->translate(@$copy); $lines = $polyline->lines; $widths = [ map $entity->width, 0..$#$lines ]; $heights = [ map $entity->height, 0..$#$lines ]; $closed = 0; } else { $lines = []; $widths = []; $heights = []; $closed = 1; foreach my $path (@$entity) { my $polyline = $path->polyline->clone; $polyline->remove_duplicate_points; $polyline->translate(@$copy); my $path_lines = $polyline->lines; push @$lines, @$path_lines; push @$widths, map $path->width, 0..$#$path_lines; push @$heights, map $path->height, 0..$#$path_lines; } } Slic3r::GUI::_3DScene::_extrusionentity_to_verts_do($lines, $widths, $heights, $closed, $top_z, $copy, $qverts, $tverts); } sub object_idx { my ($self, $volume_idx) = @_; return $self->_objects_by_volumes->{$volume_idx}[0]; } sub volume_idx { my ($self, $volume_idx) = @_; return $self->_objects_by_volumes->{$volume_idx}[1]; } sub instance_idx { my ($self, $volume_idx) = @_; return $self->_objects_by_volumes->{$volume_idx}[2]; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/AboutDialog.pm000066400000000000000000000103111254023100400177260ustar00rootroot00000000000000package Slic3r::GUI::AboutDialog; use strict; use warnings; use utf8; use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id); use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON); use Wx::Print; use Wx::Html; use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION); $self->SetBackgroundColour(Wx::wxWHITE); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->SetSizer($hsizer); # logo my $logo = Slic3r::GUI::AboutDialog::Logo->new($self, -1, wxDefaultPosition, wxDefaultSize); $logo->SetBackgroundColour(Wx::wxWHITE); $hsizer->Add($logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30); my $vsizer = Wx::BoxSizer->new(wxVERTICAL); $hsizer->Add($vsizer, 1, wxEXPAND, 0); # title my $title = Wx::StaticText->new($self, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize); my $title_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $title_font->SetWeight(wxFONTWEIGHT_BOLD); $title_font->SetFamily(wxFONTFAMILY_ROMAN); $title_font->SetPointSize(24); $title->SetFont($title_font); $vsizer->Add($title, 0, wxALIGN_LEFT | wxTOP, 30); # version my $version = Wx::StaticText->new($self, -1, "Version $Slic3r::VERSION", wxDefaultPosition, wxDefaultSize); my $version_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $version_font->SetPointSize(&Wx::wxMSW ? 9 : 11); $version->SetFont($version_font); $vsizer->Add($version, 0, wxALIGN_LEFT | wxBOTTOM, 10); # text my $text = '' . '' . '' . 'Copyright © 2011-2015 Alessandro Ranellucci.
' . 'Slic3r is licensed under the ' . 'GNU Affero General Public License, version 3.' . '


' . '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.
' . 'Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. ' . '
' . '' . ''; my $html = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); my $size = &Wx::wxMSW ? 8 : 10; $html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size, $size, $size, $size, $size, $size, $size]); $html->SetBorders(2); $html->SetPage($text); $vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20); EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked); my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE); $self->SetEscapeId(wxID_CLOSE); EVT_BUTTON($self, wxID_CLOSE, sub { $self->EndModal(wxID_CLOSE); $self->Close; }); $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); EVT_LEFT_DOWN($self, sub { $self->Close }); EVT_LEFT_DOWN($logo, sub { $self->Close }); return $self; } sub link_clicked { my ($self, $event) = @_; Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref); $event->Skip(0); } package Slic3r::GUI::AboutDialog::Logo; use Wx qw(:bitmap :dc); use Wx::Event qw(EVT_PAINT); use base 'Wx::Panel'; sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{logo} = Wx::Bitmap->new("$Slic3r::var/Slic3r_192px.png", wxBITMAP_TYPE_PNG); $self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight)); EVT_PAINT($self, \&repaint); return $self; } sub repaint { my ($self, $event) = @_; my $dc = Wx::PaintDC->new($self); $dc->SetBackgroundMode(wxTRANSPARENT); my $size = $self->GetSize; my $logo_w = $self->{logo}->GetWidth; my $logo_h = $self->{logo}->GetHeight; $dc->DrawBitmap($self->{logo}, ($size->GetWidth - $logo_w) / 2, ($size->GetHeight - $logo_h) / 2, 1); $event->Skip; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/BedShapeDialog.pm000066400000000000000000000334721254023100400203440ustar00rootroot00000000000000package Slic3r::GUI::BedShapeDialog; use strict; use warnings; use utf8; use List::Util qw(min max); use Slic3r::Geometry qw(PI X Y unscale); use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL); use Wx::Event qw(EVT_CLOSE); use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, $default) = @_; my $self = $class->SUPER::new($parent, -1, "Bed Shape", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $self->{panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $default); my $main_sizer = Wx::BoxSizer->new(wxVERTICAL); $main_sizer->Add($panel, 1, wxEXPAND); $main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND); $self->SetSizer($main_sizer); $self->SetMinSize($self->GetSize); $main_sizer->SetSizeHints($self); # needed to actually free memory EVT_CLOSE($self, sub { $self->EndModal(wxID_OK); $self->Destroy; }); return $self; } sub GetValue { my ($self) = @_; return $self->{panel}->GetValue; } package Slic3r::GUI::BedShapePanel; use List::Util qw(min max sum first); use Slic3r::Geometry qw(PI X Y scale unscale scaled_epsilon deg2rad); use Slic3r::Geometry::Clipper qw(intersection_pl); use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL); use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON EVT_PAINT); use base 'Wx::Panel'; use constant SHAPE_RECTANGULAR => 0; use constant SHAPE_CIRCULAR => 1; use constant SHAPE_CUSTOM => 2; sub new { my $class = shift; my ($parent, $default) = @_; my $self = $class->SUPER::new($parent, -1); $self->on_change(undef); my $box = Wx::StaticBox->new($self, -1, "Shape"); my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); # shape options $self->{shape_options_book} = Wx::Choicebook->new($self, -1, wxDefaultPosition, [300,-1], wxCHB_TOP); $sbsizer->Add($self->{shape_options_book}); $self->{optgroups} = []; { my $optgroup = $self->_init_shape_options_page('Rectangular'); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'rect_size', type => 'point', label => 'Size', tooltip => 'Size in X and Y of the rectangular plate.', default => [200,200], )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'rect_origin', type => 'point', label => 'Origin', tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.', default => [0,0], )); } { my $optgroup = $self->_init_shape_options_page('Circular'); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'diameter', type => 'f', label => 'Diameter', tooltip => 'Diameter of the print bed. It is assumed that origin (0,0) is located in the center.', sidetext => 'mm', default => 200, )); } { my $optgroup = $self->_init_shape_options_page('Custom'); $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new( full_width => 1, widget => sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Load shape from STL...", wxDefaultPosition, wxDefaultSize); EVT_BUTTON($self, $btn, sub { $self->_load_stl }); return $btn; } )); } EVT_CHOICEBOOK_PAGE_CHANGED($self, -1, sub { $self->_update_shape; }); # right pane with preview canvas my $canvas = $self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, [250,-1], wxTAB_TRAVERSAL); EVT_PAINT($canvas, sub { $self->_repaint_canvas; }); # main sizer my $top_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $top_sizer->Add($sbsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); $top_sizer->Add($canvas, 1, wxEXPAND | wxALL, 10) if $canvas; $self->SetSizerAndFit($top_sizer); $self->_set_shape($default); $self->_update_preview; return $self; } sub on_change { my ($self, $cb) = @_; $self->{on_change} = $cb // sub {}; } sub _set_shape { my ($self, $points) = @_; $self->{bed_shape} = $points; # is this a rectangle? if (@$points == 4) { my $polygon = Slic3r::Polygon->new_scale(@$points); my $lines = $polygon->lines; if ($lines->[0]->parallel_to_line($lines->[2]) && $lines->[1]->parallel_to_line($lines->[3])) { # okay, it's a rectangle # find origin # the || 0 hack prevents "-0" which might confuse the user my $x_min = min(map $_->[X], @$points) || 0; my $x_max = max(map $_->[X], @$points) || 0; my $y_min = min(map $_->[Y], @$points) || 0; my $y_max = max(map $_->[Y], @$points) || 0; my $origin = [-$x_min, -$y_min]; $self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR); my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR]; $optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]); $optgroup->set_value('rect_origin', $origin); $self->_update_shape; return; } } # is this a circle? { my $polygon = Slic3r::Polygon->new_scale(@$points); my $center = $polygon->bounding_box->center; my @vertex_distances = map $center->distance_to($_), @$polygon; my $avg_dist = sum(@vertex_distances)/@vertex_distances; if (!defined first { abs($_ - $avg_dist) > 10*scaled_epsilon } @vertex_distances) { # all vertices are equidistant to center $self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR); my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR]; $optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2))); $self->_update_shape; return; } } $self->{shape_options_book}->SetSelection(SHAPE_CUSTOM); $self->_update_shape; } sub _update_shape { my ($self) = @_; my $page_idx = $self->{shape_options_book}->GetSelection; if ($page_idx == SHAPE_RECTANGULAR) { my $rect_size = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_size'); my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin'); my ($x, $y) = @$rect_size; return if !$x || !$y; # empty strings my ($x0, $y0) = (0,0); my ($x1, $y1) = ($x,$y); { my ($dx, $dy) = @$rect_origin; return if $dx eq '' || $dy eq ''; # empty strings $x0 -= $dx; $x1 -= $dx; $y0 -= $dy; $y1 -= $dy; } $self->{bed_shape} = [ [$x0,$y0], [$x1,$y0], [$x1,$y1], [$x0,$y1], ]; } elsif ($page_idx == SHAPE_CIRCULAR) { my $diameter = $self->{optgroups}[SHAPE_CIRCULAR]->get_value('diameter'); return if !$diameter; my $r = $diameter/2; my $twopi = 2*PI; my $edges = 60; my $polygon = Slic3r::Polygon->new_scale( map [ $r * cos $_, $r * sin $_ ], map { $twopi/$edges*$_ } 1..$edges ); $self->{bed_shape} = [ map [ unscale($_->x), unscale($_->y) ], @$polygon #)) ]; } $self->{on_change}->(); $self->_update_preview; } sub _update_preview { my ($self) = @_; $self->{canvas}->Refresh if $self->{canvas}; } sub _repaint_canvas { my ($self) = @_; my $canvas = $self->{canvas}; my $dc = Wx::PaintDC->new($canvas); my ($cw, $ch) = $canvas->GetSizeWH; return if $cw == 0; # when canvas is not rendered yet, size is 0,0 # turn $cw and $ch from sizes to max coordinates $cw--; $ch--; my $cbb = Slic3r::Geometry::BoundingBoxf->new_from_points([ Slic3r::Pointf->new(0, 0), Slic3r::Pointf->new($cw, $ch), ]); # leave space for origin point $cbb->set_x_min($cbb->x_min + 2); $cbb->set_y_max($cbb->y_max - 2); # leave space for origin label $cbb->set_y_max($cbb->y_max - 10); # read new size ($cw, $ch) = @{$cbb->size}; my $ccenter = $cbb->center; # get bounding box of bed shape in G-code coordinates my $bed_shape = $self->{bed_shape}; my $bed_polygon = Slic3r::Polygon->new_scale(@$bed_shape); my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($bed_shape); $bb->merge_point(Slic3r::Pointf->new(0,0)); # origin needs to be in the visible area my ($bw, $bh) = @{$bb->size}; my $bcenter = $bb->center; # calculate the scaling factor for fitting bed shape in canvas area my $sfactor = min($cw/$bw, $ch/$bh); my $shift = [ $ccenter->x - $bcenter->x * $sfactor, $ccenter->y - $bcenter->y * $sfactor, #- ]; # prepare function to convert G-code coordinates into pixels my $to_pixel = sub { my ($point) = @_; my $p = Slic3r::Pointf->new(@$point); $p->scale($sfactor); $p->translate(@$shift); return [ $p->x + $cbb->x_min, $ch-$p->y + $cbb->y_min ]; #++ }; # draw bed fill { $dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID)); $dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxSOLID)); $dc->DrawPolygon([ map $to_pixel->($_), @$bed_shape ], 0, 0); } # draw grid { my $step = 10; # 1cm grid my @polylines = (); for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) { push @polylines, Slic3r::Polyline->new_scale([$x, $bb->y_min], [$x, $bb->y_max]); } for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) { push @polylines, Slic3r::Polyline->new_scale([$bb->x_min, $y], [$bb->x_max, $y]); } @polylines = @{intersection_pl(\@polylines, [$bed_polygon])}; $dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID)); $dc->DrawLine(map @{$to_pixel->([map unscale($_), @$_])}, @$_[0,-1]) for @polylines; } # draw bed contour { $dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID)); $dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxTRANSPARENT)); $dc->DrawPolygon([ map $to_pixel->($_), @$bed_shape ], 0, 0); } my $origin_px = $to_pixel->(Slic3r::Pointf->new(0,0)); # draw axes { my $axes_len = 50; my $arrow_len = 6; my $arrow_angle = deg2rad(45); $dc->SetPen(Wx::Pen->new(Wx::Colour->new(255,0,0), 2, wxSOLID)); # red my $x_end = Slic3r::Pointf->new($origin_px->[X] + $axes_len, $origin_px->[Y]); $dc->DrawLine(@$origin_px, @$x_end); foreach my $angle (-$arrow_angle, +$arrow_angle) { my $end = $x_end->clone; $end->translate(-$arrow_len, 0); $end->rotate($angle, $x_end); $dc->DrawLine(@$x_end, @$end); } $dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,255,0), 2, wxSOLID)); # green my $y_end = Slic3r::Pointf->new($origin_px->[X], $origin_px->[Y] - $axes_len); $dc->DrawLine(@$origin_px, @$y_end); foreach my $angle (-$arrow_angle, +$arrow_angle) { my $end = $y_end->clone; $end->translate(0, +$arrow_len); $end->rotate($angle, $y_end); $dc->DrawLine(@$y_end, @$end); } } # draw origin { $dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID)); $dc->SetBrush(Wx::Brush->new(Wx::Colour->new(0,0,0), wxSOLID)); $dc->DrawCircle(@$origin_px, 3); $dc->SetTextForeground(Wx::Colour->new(0,0,0)); $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)); $dc->DrawText("(0,0)", $origin_px->[X] + 1, $origin_px->[Y] + 2); } } sub _init_shape_options_page { my ($self, $title) = @_; my $panel = Wx::Panel->new($self->{shape_options_book}); my $optgroup; push @{$self->{optgroups}}, $optgroup = Slic3r::GUI::OptionsGroup->new( parent => $panel, title => 'Settings', label_width => 100, on_change => sub { my ($opt_id) = @_; #$self->{"_$opt_id"} = $optgroup->get_value($opt_id); $self->_update_shape; }, ); $panel->SetSizerAndFit($optgroup->sizer); $self->{shape_options_book}->AddPage($panel, $title); return $optgroup; } sub _load_stl { my ($self) = @_; my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } my $input_file = Slic3r::decode_path($dialog->GetPaths); $dialog->Destroy; my $model = Slic3r::Model->read_from_file($input_file); my $mesh = $model->raw_mesh; my $expolygons = $mesh->horizontal_projection; if (@$expolygons == 0) { Slic3r::GUI::show_error($self, "The selected file contains no geometry."); return; } if (@$expolygons > 1) { Slic3r::GUI::show_error($self, "The selected file contains several disjoint areas. This is not supported."); return; } my $polygon = $expolygons->[0]->contour; $self->{bed_shape} = [ map [ unscale($_->x), unscale($_->y) ], @$polygon ]; #)) } sub GetValue { my ($self) = @_; return $self->{bed_shape}; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/BonjourBrowser.pm000066400000000000000000000030761254023100400205300ustar00rootroot00000000000000package Slic3r::GUI::BonjourBrowser; use strict; use warnings; use utf8; use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL); use Wx::Event qw(EVT_CLOSE); use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, -1, "Device Browser", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); # look for devices eval "use Net::Bonjour; 1"; my $res = Net::Bonjour->new('http'); $res->discover; $self->{devices} = [ $res->entries ]; # label my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize); # selector $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, [ map $_->name, @{$self->{devices}} ]); my $main_sizer = Wx::BoxSizer->new(wxVERTICAL); $main_sizer->Add($text, 1, wxEXPAND | wxALL, 10); $main_sizer->Add($choice, 1, wxEXPAND | wxALL, 10); $main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND); $self->SetSizer($main_sizer); $self->SetMinSize($self->GetSize); $main_sizer->SetSizeHints($self); # needed to actually free memory EVT_CLOSE($self, sub { $self->EndModal(wxID_OK); $self->Destroy; }); return $self; } sub GetValue { my ($self) = @_; return $self->{devices}[ $self->{choice}->GetSelection ]->address; } sub GetPort { my ($self) = @_; return $self->{devices}[ $self->{choice}->GetSelection ]->port; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/ConfigWizard.pm000066400000000000000000000275241254023100400201400ustar00rootroot00000000000000package Slic3r::GUI::ConfigWizard; use strict; use warnings; use utf8; use Wx; use base 'Wx::Wizard'; use Slic3r::Geometry qw(unscale); # adhere to various human interface guidelines our $wizard = 'Wizard'; $wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, -1, "Configuration $wizard"); # initialize an empty repository $self->{config} = Slic3r::Config->new; $self->add_page(Slic3r::GUI::ConfigWizard::Page::Welcome->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Filament->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Temperature->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::BedTemperature->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Finished->new($self)); $_->build_index for @{$self->{pages}}; return $self; } sub add_page { my $self = shift; my ($page) = @_; my $n = push @{$self->{pages}}, $page; # add first page to the page area sizer $self->GetPageAreaSizer->Add($page) if $n == 1; # link pages $self->{pages}[$n-2]->set_next_page($page) if $n >= 2; $page->set_previous_page($self->{pages}[$n-2]) if $n >= 2; } sub run { my $self = shift; if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) { # it would be cleaner to have these defined inside each page class, # in some event getting called before leaving the page { # set first_layer_height + layer_height based on nozzle_diameter my $nozzle = $self->{config}->nozzle_diameter; $self->{config}->set('first_layer_height', $nozzle->[0]); $self->{config}->set('layer_height', $nozzle->[0] - 0.1); # set first_layer_temperature to temperature + 5 $self->{config}->set('first_layer_temperature', [$self->{config}->temperature->[0] + 5]); # set first_layer_bed_temperature to temperature + 5 $self->{config}->set('first_layer_bed_temperature', ($self->{config}->bed_temperature > 0) ? ($self->{config}->bed_temperature + 5) : 0); } $self->Destroy; return $self->{config}; } else { $self->Destroy; return undef; } } package Slic3r::GUI::ConfigWizard::Index; use Wx qw(:bitmap :dc :font :misc :sizer :systemsettings :window); use Wx::Event qw(EVT_ERASE_BACKGROUND EVT_PAINT); use base 'Wx::Panel'; sub new { my $class = shift; my ($parent, $title) = @_; my $self = $class->SUPER::new($parent); push @{$self->{titles}}, $title; $self->{own_index} = 0; $self->{bullets}->{before} = Wx::Bitmap->new("$Slic3r::var/bullet_black.png", wxBITMAP_TYPE_PNG); $self->{bullets}->{own} = Wx::Bitmap->new("$Slic3r::var/bullet_blue.png", wxBITMAP_TYPE_PNG); $self->{bullets}->{after} = Wx::Bitmap->new("$Slic3r::var/bullet_white.png", wxBITMAP_TYPE_PNG); $self->{background} = Wx::Bitmap->new("$Slic3r::var/Slic3r_192px_transparent.png", wxBITMAP_TYPE_PNG); $self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight)); EVT_PAINT($self, \&repaint); return $self; } sub repaint { my ($self, $event) = @_; my $size = $self->GetClientSize; my $gap = 5; my $dc = Wx::PaintDC->new($self); $dc->SetBackgroundMode(wxTRANSPARENT); $dc->SetFont($self->GetFont); $dc->SetTextForeground($self->GetForegroundColour); my $background_h = $self->{background}->GetHeight; my $background_w = $self->{background}->GetWidth; $dc->DrawBitmap($self->{background}, ($size->GetWidth - $background_w) / 2, ($size->GetHeight - $background_h) / 2, 1); my $label_h = $self->{bullets}->{own}->GetHeight; $label_h = $dc->GetCharHeight if $dc->GetCharHeight > $label_h; my $label_w = $size->GetWidth; my $i = 0; foreach (@{$self->{titles}}) { my $bullet = $self->{bullets}->{own}; $bullet = $self->{bullets}->{before} if $i < $self->{own_index}; $bullet = $self->{bullets}->{after} if $i > $self->{own_index}; $dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index}; $dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h)); $i++; } $event->Skip; } sub prepend_title { my $self = shift; my ($title) = @_; unshift @{$self->{titles}}, $title; $self->{own_index}++; $self->Refresh; } sub append_title { my $self = shift; my ($title) = @_; push @{$self->{titles}}, $title; $self->Refresh; } package Slic3r::GUI::ConfigWizard::Page; use Wx qw(:font :misc :sizer :staticline :systemsettings); use base 'Wx::WizardPage'; sub new { my $class = shift; my ($parent, $title, $short_title) = @_; my $self = $class->SUPER::new($parent); my $sizer = Wx::FlexGridSizer->new(0, 2, 10, 10); $sizer->AddGrowableCol(1, 1); $sizer->AddGrowableRow(1, 1); $sizer->AddStretchSpacer(0); $self->SetSizer($sizer); # title my $text = Wx::StaticText->new($self, -1, $title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $bold_font->SetWeight(wxFONTWEIGHT_BOLD); $bold_font->SetPointSize(14); $text->SetFont($bold_font); $sizer->Add($text, 0, wxALIGN_LEFT, 0); # index $self->{short_title} = $short_title ? $short_title : $title; $self->{index} = Slic3r::GUI::ConfigWizard::Index->new($self, $self->{short_title}); $sizer->Add($self->{index}, 1, wxEXPAND | wxTOP | wxRIGHT, 10); # contents $self->{width} = 430; $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{vsizer}, 1); return $self; } sub append_text { my $self = shift; my ($text) = @_; my $para = Wx::StaticText->new($self, -1, $text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $para->Wrap($self->{width}); $para->SetMinSize([$self->{width}, -1]); $self->{vsizer}->Add($para, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10); } sub append_option { my $self = shift; my ($full_key) = @_; # populate repository with the factory default my ($opt_key, $opt_index) = split /#/, $full_key, 2; $self->config->apply(Slic3r::Config->new_from_defaults($opt_key)); # draw the control my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( parent => $self, title => '', config => $self->config, full_labels => 1, ); $optgroup->append_single_option_line($opt_key, $opt_index); $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); } sub append_panel { my ($self, $panel) = @_; $self->{vsizer}->Add($panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); } sub set_previous_page { my $self = shift; my ($previous_page) = @_; $self->{previous_page} = $previous_page; } sub GetPrev { my $self = shift; return $self->{previous_page}; } sub set_next_page { my $self = shift; my ($next_page) = @_; $self->{next_page} = $next_page; } sub GetNext { my $self = shift; return $self->{next_page}; } sub get_short_title { my $self = shift; return $self->{short_title}; } sub build_index { my $self = shift; my $page = $self; $self->{index}->prepend_title($page->get_short_title) while ($page = $page->GetPrev); $page = $self; $self->{index}->append_title($page->get_short_title) while ($page = $page->GetNext); } sub config { my ($self) = @_; return $self->GetParent->{config}; } package Slic3r::GUI::ConfigWizard::Page::Welcome; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome'); $self->append_text('Hello, welcome to Slic3r! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); $self->append_text('To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); $self->append_text('To continue, click Next.'); return $self; } package Slic3r::GUI::ConfigWizard::Page::Firmware; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Firmware Type'); $self->append_text('Choose the type of firmware used by your printer, then click Next.'); $self->append_option('gcode_flavor'); return $self; } package Slic3r::GUI::ConfigWizard::Page::Bed; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Bed Size'); $self->append_text('Set the shape of your printer\'s bed, then click Next.'); $self->config->apply(Slic3r::Config->new_from_defaults('bed_shape')); $self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape); $self->{bed_shape_panel}->on_change(sub { $self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue); }); $self->append_panel($self->{bed_shape_panel}); return $self; } package Slic3r::GUI::ConfigWizard::Page::Nozzle; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Nozzle Diameter'); $self->append_text('Enter the diameter of your printer\'s hot end nozzle, then click Next.'); $self->append_option('nozzle_diameter#0'); return $self; } package Slic3r::GUI::ConfigWizard::Page::Filament; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Filament Diameter'); $self->append_text('Enter the diameter of your filament, then click Next.'); $self->append_text('Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.'); $self->append_option('filament_diameter#0'); return $self; } package Slic3r::GUI::ConfigWizard::Page::Temperature; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Extrusion Temperature'); $self->append_text('Enter the temperature needed for extruding your filament, then click Next.'); $self->append_text('A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.'); $self->append_option('temperature#0'); return $self; } package Slic3r::GUI::ConfigWizard::Page::BedTemperature; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Bed Temperature'); $self->append_text('Enter the bed temperature needed for getting your filament to stick to your heated bed, then click Next.'); $self->append_text('A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.'); $self->append_option('bed_temperature'); return $self; } package Slic3r::GUI::ConfigWizard::Page::Finished; use base 'Slic3r::GUI::ConfigWizard::Page'; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, 'Congratulations!', 'Finish'); $self->append_text("You have successfully completed the Slic3r Configuration $wizard. " . 'Slic3r is now configured for your printer and filament.'); $self->append_text('To close this '.lc($wizard).' and apply the newly created configuration, click Finish.'); return $self; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/MainFrame.pm000066400000000000000000000701551254023100400174070ustar00rootroot00000000000000package Slic3r::GUI::MainFrame; use strict; use warnings; use utf8; use File::Basename qw(basename dirname); use List::Util qw(min); use Slic3r::Geometry qw(X Y Z); use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog :font :icon wxTheApp); use Wx::Event qw(EVT_CLOSE EVT_MENU); use base 'Wx::Frame'; our $qs_last_input_file; our $qs_last_output_file; our $last_config; sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); $self->SetIcon(Wx::Icon->new("$Slic3r::var/Slic3r_128px.png", wxBITMAP_TYPE_PNG) ); # store input params $self->{mode} = $params{mode}; $self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/; $self->{no_plater} = $params{no_plater}; $self->{loaded} = 0; # initialize tabpanel and menubar $self->_init_tabpanel; $self->_init_menubar; # initialize status bar $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1); $self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://slic3r.org/"); $self->SetStatusBar($self->{statusbar}); $self->{loaded} = 1; # initialize layout { my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{tabpanel}, 1, wxEXPAND); $sizer->SetSizeHints($self); $self->SetSizer($sizer); $self->Fit; $self->SetMinSize([760, 490]); if (defined $Slic3r::GUI::Settings->{_}{main_frame_size}) { my $size = [ split ',', $Slic3r::GUI::Settings->{_}{main_frame_size}, 2 ]; $self->SetSize($size); my $display = Wx::Display->new->GetClientArea(); my $pos = [ split ',', $Slic3r::GUI::Settings->{_}{main_frame_pos}, 2 ]; if (($pos->[X] + $size->[X]/2) < $display->GetRight && ($pos->[Y] + $size->[Y]/2) < $display->GetBottom) { $self->Move($pos); } $self->Maximize(1) if $Slic3r::GUI::Settings->{_}{main_frame_maximized}; } else { $self->SetSize($self->GetMinSize); } $self->Show; $self->Layout; } # declare events EVT_CLOSE($self, sub { my (undef, $event) = @_; if ($event->CanVeto && !$self->check_unsaved_changes) { $event->Veto; return; } # save window size $Slic3r::GUI::Settings->{_}{main_frame_pos} = join ',', $self->GetScreenPositionXY; $Slic3r::GUI::Settings->{_}{main_frame_size} = join ',', $self->GetSizeWH; $Slic3r::GUI::Settings->{_}{main_frame_maximized} = $self->IsMaximized; wxTheApp->save_settings; # propagate event $event->Skip; }); return $self; } sub _init_tabpanel { my ($self) = @_; $self->{tabpanel} = my $panel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); if (!$self->{no_plater}) { $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); } $self->{options_tabs} = {}; my $simple_config; if ($self->{mode} eq 'simple') { $simple_config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini") if -e Slic3r::encode_path("$Slic3r::GUI::datadir/simple.ini"); } my $class_prefix = $self->{mode} eq 'simple' ? "Slic3r::GUI::SimpleTab::" : "Slic3r::GUI::Tab::"; for my $tab_name (qw(print filament printer)) { my $tab; $tab = $self->{options_tabs}{$tab_name} = ($class_prefix . ucfirst $tab_name)->new($panel); $tab->on_value_change(sub { my ($opt_key, $value) = @_; my $config = $tab->config; if ($self->{plater}) { $self->{plater}->on_config_change($config); # propagate config change events to the plater $self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count'; } if ($self->{loaded}) { # don't save while loading for the first time if ($self->{mode} eq 'simple') { # save config $self->config->save("$Slic3r::GUI::datadir/simple.ini"); # save a copy into each preset section # so that user gets the config when switching to expert mode $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $tab->name, 'Simple Mode'); $Slic3r::GUI::Settings->{presets}{$tab->name} = 'Simple Mode.ini'; wxTheApp->save_settings; } $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; } }); $tab->on_presets_changed(sub { if ($self->{plater}) { $self->{plater}->update_presets($tab_name, @_); $self->{plater}->on_config_change($tab->config); } }); $tab->load_presets; $panel->AddPage($tab, $tab->title); $tab->load_config($simple_config) if $simple_config; } if ($self->{plater}) { $self->{plater}->on_select_preset(sub { my ($group, $i) = @_; $self->{options_tabs}{$group}->select_preset($i); }); # load initial config $self->{plater}->on_config_change($self->config); } } sub _init_menubar { my ($self) = @_; # File menu my $fileMenu = Wx::Menu->new; { $self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub { $self->load_config_file; }, undef, 'plugin_add.png'); $self->_append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub { $self->export_config; }, undef, 'plugin_go.png'); $self->_append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub { $self->load_configbundle; }, undef, 'lorry_add.png'); $self->_append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub { $self->export_configbundle; }, undef, 'lorry_go.png'); $fileMenu->AppendSeparator(); my $repeat; $self->_append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice file', sub { wxTheApp->CallAfter(sub { $self->quick_slice; $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); }); }, undef, 'cog_go.png'); $self->_append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice file and save as', sub { wxTheApp->CallAfter(sub { $self->quick_slice(save_as => 1); $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); }); }, undef, 'cog_go.png'); $repeat = $self->_append_menu_item($fileMenu, "&Repeat Last Quick Slice\tCtrl+Shift+U", 'Repeat last quick slice', sub { wxTheApp->CallAfter(sub { $self->quick_slice(reslice => 1); }); }, undef, 'cog_go.png'); $repeat->Enable(0); $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG', sub { $self->quick_slice(save_as => 1, export_svg => 1); }, undef, 'shape_handles.png'); $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, "Repair STL file…", 'Automatically repair an STL file', sub { $self->repair_stl; }, undef, 'wrench.png'); $fileMenu->AppendSeparator(); # Cmd+, is standard on OS X - what about other operating systems? $self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub { Slic3r::GUI::Preferences->new($self)->ShowModal; }, wxID_PREFERENCES); $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, "&Quit", 'Quit Slic3r', sub { $self->Close(0); }, wxID_EXIT); } # Plater menu unless ($self->{no_plater}) { my $plater = $self->{plater}; $self->{plater_menu} = Wx::Menu->new; $self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub { $plater->export_gcode; }, undef, 'cog_go.png'); $self->_append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub { $plater->export_stl; }, undef, 'brick_go.png'); $self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub { $plater->export_amf; }, undef, 'brick_go.png'); $self->{object_menu} = $self->{plater}->object_menu; $self->on_plater_selection_changed(0); } # Window menu my $windowMenu = Wx::Menu->new; { my $tab_count = $self->{no_plater} ? 3 : 4; $self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { $self->select_tab(0); }, undef, 'application_view_tile.png') unless $self->{no_plater}; $self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { $self->select_tab($tab_count-3); }, undef, 'cog.png'); $self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub { $self->select_tab($tab_count-2); }, undef, 'spool.png'); $self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub { $self->select_tab($tab_count-1); }, undef, 'printer_empty.png'); } # Help menu my $helpMenu = Wx::Menu->new; { $self->_append_menu_item($helpMenu, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard", sub { $self->config_wizard; }); $helpMenu->AppendSeparator(); $self->_append_menu_item($helpMenu, "Slic3r &Website", 'Open the Slic3r website in your browser', sub { Wx::LaunchDefaultBrowser('http://slic3r.org/'); }); my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub { wxTheApp->check_version(1); }); $versioncheck->Enable(wxTheApp->have_version_check); $self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub { Wx::LaunchDefaultBrowser('http://manual.slic3r.org/'); }); $helpMenu->AppendSeparator(); $self->_append_menu_item($helpMenu, "&About Slic3r", 'Show about dialog', sub { wxTheApp->about; }); } # menubar # assign menubar to frame after appending items, otherwise special items # will not be handled correctly { my $menubar = Wx::MenuBar->new; $menubar->Append($fileMenu, "&File"); $menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu}; $menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu}; $menubar->Append($windowMenu, "&Window"); $menubar->Append($helpMenu, "&Help"); $self->SetMenuBar($menubar); } } sub is_loaded { my ($self) = @_; return $self->{loaded}; } sub on_plater_selection_changed { my ($self, $have_selection) = @_; return if !defined $self->{object_menu}; $self->{object_menu}->Enable($_->GetId, $have_selection) for $self->{object_menu}->GetMenuItems; } sub quick_slice { my $self = shift; my %params = @_; my $progress_dialog; eval { # validate configuration my $config = $self->config; $config->validate; # select input file my $input_file; my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; if (!$params{reslice}) { my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } $input_file = Slic3r::decode_path($dialog->GetPaths); $dialog->Destroy; $qs_last_input_file = $input_file unless $params{export_svg}; } else { if (!defined $qs_last_input_file) { Wx::MessageDialog->new($self, "No previously sliced file.", 'Error', wxICON_ERROR | wxOK)->ShowModal(); return; } if (! -e $qs_last_input_file) { Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.", 'File Not Found', wxICON_ERROR | wxOK)->ShowModal(); return; } $input_file = $qs_last_input_file; } my $input_file_basename = basename($input_file); $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); wxTheApp->save_settings; my $print_center; { my $bed_shape = Slic3r::Polygon->new_scale(@{$config->bed_shape}); $print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center}); } my $sprint = Slic3r::Print::Simple->new( print_center => $print_center, status_cb => sub { my ($percent, $message) = @_; return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/; $progress_dialog->Update($percent, "$message…"); }, ); # keep model around my $model = Slic3r::Model->read_from_file($input_file); $sprint->apply_config($config); $sprint->set_model($model); { my $extra = $self->extra_variables; $sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; } # select output file my $output_file; if ($params{reslice}) { $output_file = $qs_last_output_file if defined $qs_last_output_file; } elsif ($params{save_as}) { $output_file = $sprint->expanded_output_filepath; $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', wxTheApp->output_path(dirname($output_file)), basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; } $output_file = Slic3r::decode_path($dlg->GetPath); $qs_last_output_file = $output_file unless $params{export_svg}; $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); wxTheApp->save_settings; $dlg->Destroy; } # show processbar dialog $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…", 100, $self, 0); $progress_dialog->Pulse; { my @warnings = (); local $SIG{__WARN__} = sub { push @warnings, $_[0] }; $sprint->output_file($output_file); if ($params{export_svg}) { $sprint->export_svg; } else { $sprint->export_gcode; } $sprint->status_cb(undef); Slic3r::GUI::warning_catcher($self)->($_) for @warnings; } $progress_dialog->Destroy; undef $progress_dialog; my $message = "$input_file_basename was successfully sliced."; wxTheApp->notify($message); Wx::MessageDialog->new($self, $message, 'Slicing Done!', wxOK | wxICON_INFORMATION)->ShowModal; }; Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog }); } sub repair_stl { my $self = shift; my $input_file; { my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } $input_file = Slic3r::decode_path($dialog->GetPaths); $dialog->Destroy; } my $output_file = $input_file; { $output_file =~ s/\.stl$/_fixed.obj/i; my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file), basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; } $output_file = Slic3r::decode_path($dlg->GetPath); $dlg->Destroy; } my $tmesh = Slic3r::TriangleMesh->new; $tmesh->ReadSTLFile(Slic3r::encode_path($input_file)); $tmesh->repair; $tmesh->WriteOBJFile(Slic3r::encode_path($output_file)); Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); } sub extra_variables { my $self = shift; my %extra_variables = (); if ($self->{mode} eq 'expert') { $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->get_current_preset->name for qw(print filament printer); } return { %extra_variables }; } sub export_config { my $self = shift; my $config = $self->config; eval { # validate configuration $config->validate; }; Slic3r::GUI::catch_error($self) and return; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $filename = $last_config ? basename($last_config) : "config.ini"; my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal == wxID_OK) { my $file = Slic3r::decode_path($dlg->GetPath); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; $last_config = $file; $config->save($file); } $dlg->Destroy; } sub load_config_file { my $self = shift; my ($file) = @_; if (!$file) { return unless $self->check_unsaved_changes; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = Slic3r::decode_path($dlg->GetPaths); $dlg->Destroy; } $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; $last_config = $file; for my $tab (values %{$self->{options_tabs}}) { $tab->load_config_file($file); } } sub export_configbundle { my $self = shift; eval { # validate current configuration in case it's dirty $self->config->validate; }; Slic3r::GUI::catch_error($self) and return; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $filename = "Slic3r_config_bundle.ini"; my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal == wxID_OK) { my $file = Slic3r::decode_path($dlg->GetPath); $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; # leave default category empty to prevent the bundle from being parsed as a normal config file my $ini = { _ => {} }; $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter mode); $ini->{presets} = $Slic3r::GUI::Settings->{presets}; if (-e "$Slic3r::GUI::datadir/simple.ini") { my $config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini"); $ini->{simple} = $config->as_ini->{_}; } foreach my $section (qw(print filament printer)) { my %presets = wxTheApp->presets($section); foreach my $preset_name (keys %presets) { my $config = Slic3r::Config->load($presets{$preset_name}); $ini->{"$section:$preset_name"} = $config->as_ini->{_}; } } Slic3r::Config->write_ini($file, $ini); } $dlg->Destroy; } sub load_configbundle { my $self = shift; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; my $file = Slic3r::decode_path($dlg->GetPaths); $dlg->Destroy; $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; # load .ini file my $ini = Slic3r::Config->read_ini($file); if ($ini->{settings}) { $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}}; wxTheApp->save_settings; } if ($ini->{presets}) { $Slic3r::GUI::Settings->{presets} = $ini->{presets}; wxTheApp->save_settings; } if ($ini->{simple}) { my $config = Slic3r::Config->load_ini_hash($ini->{simple}); $config->save("$Slic3r::GUI::datadir/simple.ini"); if ($self->{mode} eq 'simple') { foreach my $tab (values %{$self->{options_tabs}}) { $tab->load_config($config) for values %{$self->{options_tabs}}; } } } my $imported = 0; foreach my $ini_category (sort keys %$ini) { next unless $ini_category =~ /^(print|filament|printer):(.+)$/; my ($section, $preset_name) = ($1, $2); my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category}); $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name); $imported++; } if ($self->{mode} eq 'expert') { foreach my $tab (values %{$self->{options_tabs}}) { $tab->load_presets; } } my $message = sprintf "%d presets successfully imported.", $imported; if ($self->{mode} eq 'simple' && $Slic3r::GUI::Settings->{_}{mode} eq 'expert') { Slic3r::GUI::show_info($self, "$message You need to restart Slic3r to make the changes effective."); } else { Slic3r::GUI::show_info($self, $message); } } sub load_config { my $self = shift; my ($config) = @_; foreach my $tab (values %{$self->{options_tabs}}) { $tab->load_config($config); } if ($self->{plater}) { $self->{plater}->on_config_change($config); } } sub config_wizard { my $self = shift; return unless $self->check_unsaved_changes; if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { if ($self->{mode} eq 'expert') { for my $tab (values %{$self->{options_tabs}}) { $tab->select_default_preset; } } else { # TODO: select default settings in simple mode } $self->load_config($config); if ($self->{mode} eq 'expert') { for my $tab (values %{$self->{options_tabs}}) { $tab->save_preset('My Settings'); } } } } =head2 config This method collects all config values from the tabs and merges them into a single config object. =cut sub config { my $self = shift; return Slic3r::Config->new_from_defaults if !exists $self->{options_tabs}{print} || !exists $self->{options_tabs}{filament} || !exists $self->{options_tabs}{printer}; # retrieve filament presets and build a single config object for them my $filament_config; if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') { $filament_config = $self->{options_tabs}{filament}->config; } else { my $i = -1; foreach my $preset_idx ($self->{plater}->filament_presets) { $i++; my $config; if ($preset_idx == $self->{options_tabs}{filament}->current_preset) { # the selected preset for this extruder is the one in the tab # use the tab's config instead of the preset in case it is dirty # perhaps plater shouldn't expose dirty presets at all in multi-extruder environments. $config = $self->{options_tabs}{filament}->config; } else { my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); $config = $self->{options_tabs}{filament}->get_preset_config($preset); } if (!$filament_config) { $filament_config = $config->clone; next; } foreach my $opt_key (@{$config->get_keys}) { 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); } } } my $config = Slic3r::Config->merge( Slic3r::Config->new_from_defaults, $self->{options_tabs}{print}->config, $self->{options_tabs}{printer}->config, $filament_config, ); if ($self->{mode} eq 'simple') { # set some sensible defaults $config->set('first_layer_height', $config->nozzle_diameter->[0]); $config->set('avoid_crossing_perimeters', 1); $config->set('infill_every_layers', 10); } else { my $extruders_count = $self->{options_tabs}{printer}{extruders_count}; $config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count)) for qw(perimeter infill solid_infill support_material support_material_interface); } return $config; } sub check_unsaved_changes { my $self = shift; my @dirty = (); foreach my $tab (values %{$self->{options_tabs}}) { push @dirty, $tab->title if $tab->is_dirty; } if (@dirty) { my $titles = join ', ', @dirty; my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?", 'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); return ($confirm->ShowModal == wxID_YES); } return 1; } sub select_tab { my ($self, $tab) = @_; $self->{tabpanel}->ChangeSelection($tab); } sub _append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; $id //= &Wx::NewId(); my $item = $menu->Append($id, $string, $description); $self->_set_menu_item_icon($item, $icon); EVT_MENU($self, $id, $cb); return $item; } sub _set_menu_item_icon { my ($self, $menuItem, $icon) = @_; # SetBitmap was not available on OS X before Wx 0.9927 if ($icon && $menuItem->can('SetBitmap')) { $menuItem->SetBitmap(Wx::Bitmap->new("$Slic3r::var/$icon", wxBITMAP_TYPE_PNG)); } } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Notifier.pm000066400000000000000000000024451254023100400173240ustar00rootroot00000000000000package Slic3r::GUI::Notifier; use Moo; has 'growler' => (is => 'rw'); my $icon = "$Slic3r::var/Slic3r.png"; sub BUILD { my ($self) = @_; if (eval 'use Growl::GNTP; 1') { # register with growl eval { $self->growler(Growl::GNTP->new(AppName => 'Slic3r', AppIcon => $icon)); $self->growler->register([{Name => 'SKEIN_DONE', DisplayName => 'Slicing Done'}]); }; # if register() fails (for example because of a timeout), disable growler at all $self->growler(undef) if $@; } } sub notify { my ($self, $message) = @_; my $title = 'Slicing Done!'; eval { $self->growler->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message) if $self->growler; }; # Net::DBus is broken in multithreaded environment if (0 && eval 'use Net::DBus; 1') { eval { my $session = Net::DBus->session; my $serv = $session->get_service('org.freedesktop.Notifications'); my $notifier = $serv->get_object('/org/freedesktop/Notifications', 'org.freedesktop.Notifications'); $notifier->Notify('Slic3r', 0, $icon, $title, $message, [], {}, -1); undef $Net::DBus::bus_session; }; } } 1; Slic3r-1.2.9/lib/Slic3r/GUI/OptionsGroup.pm000066400000000000000000000355041254023100400202170ustar00rootroot00000000000000package Slic3r::GUI::OptionsGroup; use Moo; use List::Util qw(first); use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl wxTheApp); use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER); has 'parent' => (is => 'ro', required => 1); has 'title' => (is => 'ro', required => 1); has 'on_change' => (is => 'rw', default => sub { sub {} }); has 'staticbox' => (is => 'ro', default => sub { 1 }); has 'label_width' => (is => 'rw', default => sub { 180 }); has 'extra_column' => (is => 'rw', default => sub { undef }); has 'label_font' => (is => 'rw'); has 'sidetext_font' => (is => 'rw', default => sub { Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }); has 'sizer' => (is => 'rw'); has '_disabled' => (is => 'rw', default => sub { 0 }); has '_grid_sizer' => (is => 'rw'); has '_options' => (is => 'ro', default => sub { {} }); has '_fields' => (is => 'ro', default => sub { {} }); sub BUILD { my $self = shift; if ($self->staticbox) { my $box = Wx::StaticBox->new($self->parent, -1, $self->title); $self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL)); } else { $self->sizer(Wx::BoxSizer->new(wxVERTICAL)); } my $num_columns = 1; ++$num_columns if $self->label_width != 0; ++$num_columns if $self->extra_column; $self->_grid_sizer(Wx::FlexGridSizer->new(0, $num_columns, 0, 0)); $self->_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $self->_grid_sizer->AddGrowableCol($self->label_width != 0); # TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific $self->sizer->Add($self->_grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5); } # this method accepts a Slic3r::GUI::OptionsGroup::Line object sub append_line { my ($self, $line) = @_; if ($line->sizer || ($line->widget && $line->full_width)) { # full-width widgets are appended *after* the grid sizer, so after all the non-full-width lines my $sizer = $line->sizer // $line->widget->($self->parent); $self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); return; } my $grid_sizer = $self->_grid_sizer; # if we have an extra column, build it if ($self->extra_column) { if (defined (my $item = $self->extra_column->($line))) { $grid_sizer->Add($item, 0, wxALIGN_CENTER_VERTICAL, 0); } else { # if the callback provides no sizer for the extra cell, put a spacer $grid_sizer->AddSpacer(1); } } # build label if we have it my $label; if ($self->label_width != 0) { $label = Wx::StaticText->new($self->parent, -1, $line->label ? $line->label . ":" : "", wxDefaultPosition, [$self->label_width, -1]); $label->SetFont($self->label_font) if $self->label_font; $label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug $grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0); $label->SetToolTipString($line->label_tooltip) if $line->label_tooltip; } # if we have a widget, add it to the sizer if ($line->widget) { my $widget_sizer = $line->widget->($self->parent); $grid_sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); return; } # if we have a single option with no sidetext just add it directly to the grid sizer my @options = @{$line->get_options}; $self->_options->{$_->opt_id} = $_ for @options; if (@options == 1 && !$options[0]->sidetext && !@{$line->get_extra_widgets}) { my $option = $options[0]; my $field = $self->_build_field($option); $grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); return; } # if we're here, we have more than one option or a single option with sidetext # so we need a horizontal sizer to arrange these things my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $grid_sizer->Add($sizer, 0, 0, 0); foreach my $option (@options) { # add label if any if ($option->label) { my $field_label = Wx::StaticText->new($self->parent, -1, $option->label . ":", wxDefaultPosition, wxDefaultSize); $field_label->SetFont($self->sidetext_font); $sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0); } # add field my $field = $self->_build_field($option); $sizer->Add($field, 0, wxALIGN_CENTER_VERTICAL, 0); # add sidetext if any if ($option->sidetext) { my $sidetext = Wx::StaticText->new($self->parent, -1, $option->sidetext, wxDefaultPosition, wxDefaultSize); $sidetext->SetFont($self->sidetext_font); $sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4); } } # add extra sizers if any foreach my $extra_widget (@{$line->get_extra_widgets}) { $sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4); } } sub create_single_option_line { my ($self, $option) = @_; my $line = Slic3r::GUI::OptionsGroup::Line->new( label => $option->label, label_tooltip => $option->tooltip, ); $option->label(""); $line->append_option($option); return $line; } sub append_single_option_line { my ($self, $option) = @_; return $self->append_line($self->create_single_option_line($option)); } sub _build_field { my $self = shift; my ($opt) = @_; my $opt_id = $opt->opt_id; my $on_change = sub { my ($opt_id, $value) = @_; $self->_on_change($opt_id, $value) unless $self->_disabled; }; my $on_kill_focus = sub { my ($opt_id) = @_; $self->_on_kill_focus($opt_id); }; my $type = $opt->{gui_type} || $opt->{type}; my $field; if ($type eq 'bool') { $field = Slic3r::GUI::OptionsGroup::Field::Checkbox->new( parent => $self->parent, option => $opt, ); } elsif ($type eq 'i') { $field = Slic3r::GUI::OptionsGroup::Field::SpinCtrl->new( parent => $self->parent, option => $opt, ); } elsif ($type eq 'color') { $field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new( parent => $self->parent, option => $opt, ); } elsif ($type =~ /^(f|s|s@|percent)$/) { $field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new( parent => $self->parent, option => $opt, ); } elsif ($type eq 'select') { $field = Slic3r::GUI::OptionsGroup::Field::Choice->new( parent => $self->parent, option => $opt, ); } elsif ($type eq 'f_enum_open' || $type eq 'i_enum_open' || $type eq 'i_enum_closed') { $field = Slic3r::GUI::OptionsGroup::Field::NumericChoice->new( parent => $self->parent, option => $opt, ); } elsif ($type eq 'point') { $field = Slic3r::GUI::OptionsGroup::Field::Point->new( parent => $self->parent, option => $opt, ); } elsif ($type eq 'slider') { $field = Slic3r::GUI::OptionsGroup::Field::Slider->new( parent => $self->parent, option => $opt, ); } return undef if !$field; $field->on_change($on_change); $field->on_kill_focus($on_kill_focus); $self->_fields->{$opt_id} = $field; return $field->isa('Slic3r::GUI::OptionsGroup::Field::wxWindow') ? $field->wxWindow : $field->wxSizer; } sub get_option { my ($self, $opt_id) = @_; return undef if !exists $self->_options->{$opt_id}; return $self->_options->{$opt_id}; } sub get_field { my ($self, $opt_id) = @_; return undef if !exists $self->_fields->{$opt_id}; return $self->_fields->{$opt_id}; } sub get_value { my ($self, $opt_id) = @_; return if !exists $self->_fields->{$opt_id}; return $self->_fields->{$opt_id}->get_value; } sub set_value { my ($self, $opt_id, $value) = @_; return if !exists $self->_fields->{$opt_id}; $self->_fields->{$opt_id}->set_value($value); } sub _on_change { my ($self, $opt_id) = @_; $self->on_change->($opt_id); } sub _on_kill_focus { my ($self, $opt_id) = @_; # nothing } package Slic3r::GUI::OptionsGroup::Line; use Moo; has 'label' => (is => 'rw', default => sub { "" }); has 'full_width' => (is => 'rw', default => sub { 0 }); has 'label_tooltip' => (is => 'rw', default => sub { "" }); has 'sizer' => (is => 'rw'); has 'widget' => (is => 'rw'); has '_options' => (is => 'ro', default => sub { [] }); has '_extra_widgets' => (is => 'ro', default => sub { [] }); # this method accepts a Slic3r::GUI::OptionsGroup::Option object sub append_option { my ($self, $option) = @_; push @{$self->_options}, $option; } sub append_widget { my ($self, $widget) = @_; push @{$self->_extra_widgets}, $widget; } sub get_options { my ($self) = @_; return [ @{$self->_options} ]; } sub get_extra_widgets { my ($self) = @_; return [ @{$self->_extra_widgets} ]; } package Slic3r::GUI::OptionsGroup::Option; use Moo; has 'opt_id' => (is => 'rw', required => 1); has 'type' => (is => 'rw', required => 1); has 'default' => (is => 'rw', required => 1); has 'gui_type' => (is => 'rw', default => sub { undef }); has 'gui_flags' => (is => 'rw', default => sub { "" }); has 'label' => (is => 'rw', default => sub { "" }); has 'sidetext' => (is => 'rw', default => sub { "" }); has 'tooltip' => (is => 'rw', default => sub { "" }); has 'multiline' => (is => 'rw', default => sub { 0 }); has 'full_width' => (is => 'rw', default => sub { 0 }); has 'width' => (is => 'rw', default => sub { undef }); has 'height' => (is => 'rw', default => sub { undef }); has 'min' => (is => 'rw', default => sub { undef }); has 'max' => (is => 'rw', default => sub { undef }); has 'labels' => (is => 'rw', default => sub { [] }); has 'values' => (is => 'rw', default => sub { [] }); has 'readonly' => (is => 'rw', default => sub { 0 }); package Slic3r::GUI::ConfigOptionsGroup; use Moo; use List::Util qw(first); extends 'Slic3r::GUI::OptionsGroup'; has 'config' => (is => 'ro', required => 1); has 'full_labels' => (is => 'ro', default => sub { 0 }); has '_opt_map' => (is => 'ro', default => sub { {} }); sub get_option { my ($self, $opt_key, $opt_index) = @_; $opt_index //= -1; if (!$self->config->has($opt_key)) { die "No $opt_key in ConfigOptionsGroup config"; } my $opt_id = ($opt_index == -1 ? $opt_key : "${opt_key}#${opt_index}"); $self->_opt_map->{$opt_id} = [ $opt_key, $opt_index ]; my $optdef = $Slic3r::Config::Options->{$opt_key}; # we should access this from $self->config my $default_value = $self->_get_config_value($opt_key, $opt_index, $optdef->{gui_flags} =~ /\bserialized\b/); return Slic3r::GUI::OptionsGroup::Option->new( opt_id => $opt_id, type => $optdef->{type}, default => $default_value, gui_type => $optdef->{gui_type}, gui_flags => $optdef->{gui_flags}, label => ($self->full_labels && defined $optdef->{full_label}) ? $optdef->{full_label} : $optdef->{label}, sidetext => $optdef->{sidetext}, tooltip => $optdef->{tooltip} . " (default: " . $default_value . ")", multiline => $optdef->{multiline}, width => $optdef->{width}, min => $optdef->{min}, max => $optdef->{max}, labels => $optdef->{labels}, values => $optdef->{values}, readonly => $optdef->{readonly}, ); } sub create_single_option_line { my ($self, $opt_key, $opt_index) = @_; my $option; if (ref($opt_key)) { $option = $opt_key; } else { $option = $self->get_option($opt_key, $opt_index); } return $self->SUPER::create_single_option_line($option); } sub append_single_option_line { my ($self, $option, $opt_index) = @_; return $self->append_line($self->create_single_option_line($option, $opt_index)); } sub reload_config { my ($self) = @_; foreach my $opt_id (keys %{ $self->_opt_map }) { my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} }; my $option = $self->_options->{$opt_id}; $self->set_value($opt_id, $self->_get_config_value($opt_key, $opt_index, $option->gui_flags =~ /\bserialized\b/)); } } sub get_fieldc { my ($self, $opt_key, $opt_index) = @_; $opt_index //= -1; my $opt_id = first { $self->_opt_map->{$_}[0] eq $opt_key && $self->_opt_map->{$_}[1] == $opt_index } keys %{$self->_opt_map}; return defined($opt_id) ? $self->get_field($opt_id) : undef; } sub _get_config_value { my ($self, $opt_key, $opt_index, $deserialize) = @_; if ($deserialize) { die "Can't deserialize option indexed value" if $opt_index != -1; return $self->config->serialize($opt_key); } else { return $opt_index == -1 ? $self->config->get($opt_key) : $self->config->get_at($opt_key, $opt_index); } } sub _on_change { my ($self, $opt_id) = @_; if (exists $self->_opt_map->{$opt_id}) { my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} }; my $option = $self->_options->{$opt_id}; # get value my $field_value = $self->get_value($opt_id); if ($option->gui_flags =~ /\bserialized\b/) { die "Can't set serialized option indexed value" if $opt_index != -1; $self->config->set_deserialize($opt_key, $field_value); } else { if ($opt_index == -1) { $self->config->set($opt_key, $field_value); } else { my $value = $self->config->get($opt_key); $value->[$opt_index] = $field_value; $self->config->set($opt_key, $value); } } } $self->SUPER::_on_change($opt_id); } sub _on_kill_focus { my ($self, $opt_id) = @_; # when a field loses focus, reapply the config value to it # (thus discarding any invalid input and reverting to the last # accepted value) $self->reload_config; } package Slic3r::GUI::OptionsGroup::StaticText; use Wx qw(:misc :systemsettings); use base 'Wx::StaticText'; sub new { my ($class, $parent) = @_; my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize); my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $self->SetFont($font); return $self; } sub SetText { my ($self, $value) = @_; $self->SetLabel($value); $self->Wrap(400); $self->GetParent->Layout; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/OptionsGroup/000077500000000000000000000000001254023100400176525ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/GUI/OptionsGroup/Field.pm000066400000000000000000000341201254023100400212330ustar00rootroot00000000000000package Slic3r::GUI::OptionsGroup::Field; use Moo; # This is a base class for option fields. has 'parent' => (is => 'ro', required => 1); has 'option' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option has 'on_change' => (is => 'rw', default => sub { sub {} }); has 'on_kill_focus' => (is => 'rw', default => sub { sub {} }); has 'disable_change_event' => (is => 'rw', default => sub { 0 }); # This method should not fire the on_change event sub set_value { my ($self, $value) = @_; die "Method not implemented"; } sub get_value { my ($self) = @_; die "Method not implemented"; } sub set_tooltip { my ($self, $tooltip) = @_; $self->SetToolTipString($tooltip) if $tooltip && $self->can('SetToolTipString'); } sub toggle { my ($self, $enable) = @_; $enable ? $self->enable : $self->disable; } sub _on_change { my ($self, $opt_id) = @_; $self->on_change->($opt_id) unless $self->disable_change_event; } sub _on_kill_focus { my ($self, $opt_id, $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_id); } package Slic3r::GUI::OptionsGroup::Field::wxWindow; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field'; has 'wxWindow' => (is => 'rw', trigger => 1); # wxWindow object sub _default_size { my ($self) = @_; # default width on Windows is too large return Wx::Size->new($self->option->width || 60, $self->option->height || -1); } sub _trigger_wxWindow { my ($self) = @_; $self->wxWindow->SetToolTipString($self->option->tooltip) if $self->option->tooltip && $self->wxWindow->can('SetToolTipString'); } sub set_value { my ($self, $value) = @_; $self->disable_change_event(1); $self->wxWindow->SetValue($value); $self->disable_change_event(0); } sub get_value { my ($self) = @_; return $self->wxWindow->GetValue; } sub enable { my ($self) = @_; $self->wxWindow->Enable; $self->wxWindow->Refresh; } sub disable { my ($self) = @_; $self->wxWindow->Disable; $self->wxWindow->Refresh; } package Slic3r::GUI::OptionsGroup::Field::Checkbox; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use Wx qw(:misc); use Wx::Event qw(EVT_CHECKBOX); sub BUILD { my ($self) = @_; my $field = Wx::CheckBox->new($self->parent, -1, ""); $self->wxWindow($field); $field->SetValue($self->option->default); $field->Disable if $self->option->readonly; EVT_CHECKBOX($self->parent, $field, sub { $self->_on_change($self->option->opt_id); }); } package Slic3r::GUI::OptionsGroup::Field::SpinCtrl; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use Wx qw(:misc); use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS); has 'tmp_value' => (is => 'rw'); sub BUILD { my ($self) = @_; my $field = Wx::SpinCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size, 0, $self->option->min || 0, $self->option->max || 2147483647, $self->option->default); $self->wxWindow($field); EVT_SPINCTRL($self->parent, $field, sub { $self->tmp_value(undef); $self->_on_change($self->option->opt_id); }); EVT_TEXT($self->parent, $field, sub { my ($s, $event) = @_; # On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value # when it was changed from the text control, so the on_change callback # gets the old one, and on_kill_focus resets the control to the old value. # As a workaround, we get the new value from $event->GetString and store # here temporarily so that we can return it from $self->get_value $self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/; $self->_on_change($self->option->opt_id); # We don't reset tmp_value here because _on_change might put callbacks # in the CallAfter queue, and we want the tmp value to be available from # them as well. }); EVT_KILL_FOCUS($field, sub { $self->tmp_value(undef); $self->_on_kill_focus($self->option->opt_id, @_); }); } sub get_value { my ($self) = @_; return $self->tmp_value // $self->wxWindow->GetValue; } package Slic3r::GUI::OptionsGroup::Field::TextCtrl; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use Wx qw(:misc :textctrl); use Wx::Event qw(EVT_TEXT EVT_KILL_FOCUS); sub BUILD { my ($self) = @_; my $style = 0; $style = wxTE_MULTILINE if $self->option->multiline; my $field = Wx::TextCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size, $style); $self->wxWindow($field); # TODO: test loading a config that has empty string for multi-value options like 'wipe' EVT_TEXT($self->parent, $field, sub { $self->_on_change($self->option->opt_id); }); EVT_KILL_FOCUS($field, sub { $self->_on_kill_focus($self->option->opt_id, @_); }); } sub enable { my ($self) = @_; $self->wxWindow->Enable; $self->wxWindow->SetEditable(1); } sub disable { my ($self) = @_; $self->wxWindow->Disable; $self->wxWindow->SetEditable(0); } package Slic3r::GUI::OptionsGroup::Field::Choice; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use List::Util qw(first); use Wx qw(:misc :combobox); use Wx::Event qw(EVT_COMBOBOX); sub BUILD { my ($self) = @_; my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size, $self->option->labels || $self->option->values, wxCB_READONLY); $self->wxWindow($field); $self->set_value($self->option->default); EVT_COMBOBOX($self->parent, $field, sub { $self->_on_change($self->option->opt_id); }); } sub set_value { my ($self, $value) = @_; my $idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values}; $self->disable_change_event(1); $self->wxWindow->SetSelection($idx); $self->disable_change_event(0); } sub get_value { my ($self) = @_; return $self->option->values->[$self->wxWindow->GetSelection]; } package Slic3r::GUI::OptionsGroup::Field::NumericChoice; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use List::Util qw(first); use Wx qw(wxTheApp :misc :combobox); use Wx::Event qw(EVT_COMBOBOX EVT_TEXT); # if option has no 'values', indices are values # if option has no 'labels', values are labels sub BUILD { my ($self) = @_; my $field = Wx::ComboBox->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size, $self->option->labels || $self->option->values); $self->wxWindow($field); $self->set_value($self->option->default); EVT_COMBOBOX($self->parent, $field, sub { my $disable_change_event = $self->disable_change_event; $self->disable_change_event(1); my $idx = $field->GetSelection; # get index of selected value my $label; if ($self->option->labels && $idx <= $#{$self->option->labels}) { $label = $self->option->labels->[$idx]; } elsif ($self->option->values && $idx <= $#{$self->option->values}) { $label = $self->option->values->[$idx]; } else { $label = $idx; } # The MSW implementation of wxComboBox will leave the field blank if we call # SetValue() in the EVT_COMBOBOX event handler, so we postpone the call. wxTheApp->CallAfter(sub { my $dce = $self->disable_change_event; $self->disable_change_event(1); # ChangeValue() is not exported in wxPerl $field->SetValue($label); $self->disable_change_event($dce); }); $self->disable_change_event($disable_change_event); $self->_on_change($self->option->opt_id); }); EVT_TEXT($self->parent, $field, sub { $self->_on_change($self->option->opt_id); }); } sub set_value { my ($self, $value) = @_; $self->disable_change_event(1); my $field = $self->wxWindow; if ($self->option->gui_flags =~ /\bshow_value\b/) { $field->SetValue($value); } else { if ($self->option->values) { # check whether we have a value index my $value_idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values}; if (defined $value_idx) { $field->SetSelection($value_idx); $self->disable_change_event(0); return; } } elsif ($self->option->labels && $value <= $#{$self->option->labels}) { # if we have no values, we expect value to be an index $field->SetValue($self->option->labels->[$value]); $self->disable_change_event(0); return; } $field->SetValue($value); } $self->disable_change_event(0); } sub get_value { my ($self) = @_; my $label = $self->wxWindow->GetValue; my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels}; if (defined $value_idx) { if ($self->option->values) { return $self->option->values->[$value_idx]; } return $value_idx; } return $label; } package Slic3r::GUI::OptionsGroup::Field::ColourPicker; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow'; use Wx qw(:misc :colour); use Wx::Event qw(EVT_COLOURPICKER_CHANGED); sub BUILD { my ($self) = @_; my $field = Wx::ColourPickerCtrl->new($self->parent, -1, $self->_string_to_colour($self->option->default), wxDefaultPosition, $self->_default_size); $self->wxWindow($field); EVT_COLOURPICKER_CHANGED($self->parent, $field, sub { $self->_on_change($self->option->opt_id); }); } sub set_value { my ($self, $value) = @_; $self->disable_change_event(1); $self->wxWindow->SetColour($self->_string_to_colour($value)); $self->disable_change_event(0); } sub get_value { my ($self) = @_; return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX); } sub _string_to_colour { my ($self, $string) = @_; $string =~ s/^#//; return Wx::Colour->new(unpack 'C*', pack 'H*', $string); } package Slic3r::GUI::OptionsGroup::Field::wxSizer; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field'; has 'wxSizer' => (is => 'rw'); # wxSizer object package Slic3r::GUI::OptionsGroup::Field::Point; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer'; has 'x_textctrl' => (is => 'rw'); has 'y_textctrl' => (is => 'rw'); use Slic3r::Geometry qw(X Y); use Wx qw(:misc :sizer); use Wx::Event qw(EVT_TEXT); sub BUILD { my ($self) = @_; my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->wxSizer($sizer); my $field_size = Wx::Size->new(40, -1); $self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size)); $self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size)); my @items = ( Wx::StaticText->new($self->parent, -1, "x:"), $self->x_textctrl, Wx::StaticText->new($self->parent, -1, " y:"), $self->y_textctrl, ); $sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items; if ($self->option->tooltip) { foreach my $item (@items) { $item->SetToolTipString($self->option->tooltip) if $item->can('SetToolTipString'); } } EVT_TEXT($self->parent, $_, sub { $self->_on_change($self->option->opt_id); }) for $self->x_textctrl, $self->y_textctrl; } sub set_value { my ($self, $value) = @_; $self->disable_change_event(1); $self->x_textctrl->SetValue($value->[X]); $self->y_textctrl->SetValue($value->[Y]); $self->disable_change_event(0); } sub get_value { my ($self) = @_; return [ $self->x_textctrl->GetValue, $self->y_textctrl->GetValue, ]; } sub enable { my ($self) = @_; $self->x_textctrl->Enable; $self->y_textctrl->Enable; } sub disable { my ($self) = @_; $self->x_textctrl->Disable; $self->y_textctrl->Disable; } package Slic3r::GUI::OptionsGroup::Field::Slider; use Moo; extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer'; has 'scale' => (is => 'rw', default => sub { 10 }); has 'slider' => (is => 'rw'); has 'statictext' => (is => 'rw'); use Slic3r::Geometry qw(X Y); use Wx qw(:misc :sizer); use Wx::Event qw(EVT_SLIDER); sub BUILD { my ($self) = @_; my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->wxSizer($sizer); my $slider = Wx::Slider->new( $self->parent, -1, ($self->option->default // $self->option->min) * $self->scale, ($self->option->min // 0) * $self->scale, ($self->option->max // 100) * $self->scale, wxDefaultPosition, [ $self->option->width // -1, $self->option->height // -1 ], ); $self->slider($slider); my $statictext = Wx::StaticText->new($self->parent, -1, $slider->GetValue/$self->scale, wxDefaultPosition, [20,-1]); $self->statictext($statictext); $sizer->Add($slider, 1, wxALIGN_CENTER_VERTICAL, 0); $sizer->Add($statictext, 0, wxALIGN_CENTER_VERTICAL, 0); EVT_SLIDER($self->parent, $slider, sub { $self->_update_statictext; $self->_on_change($self->option->opt_id); }); } sub set_value { my ($self, $value) = @_; $self->disable_change_event(1); $self->slider->SetValue($value); $self->_update_statictext; $self->disable_change_event(0); } sub get_value { my ($self) = @_; return $self->slider->GetValue/$self->scale; } sub _update_statictext { my ($self) = @_; $self->statictext->SetLabel($self->get_value); } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater.pm000066400000000000000000002055641254023100400170030ustar00rootroot00000000000000package Slic3r::GUI::Plater; use strict; use warnings; use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad); use threads::shared qw(shared_clone); use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc :panel :sizer :toolbar :window wxTheApp :notebook :combobox); 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 EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); use base 'Wx::Panel'; use constant TB_ADD => &Wx::NewId; use constant TB_REMOVE => &Wx::NewId; use constant TB_RESET => &Wx::NewId; use constant TB_ARRANGE => &Wx::NewId; use constant TB_EXPORT_GCODE => &Wx::NewId; use constant TB_EXPORT_STL => &Wx::NewId; use constant TB_MORE => &Wx::NewId; use constant TB_FEWER => &Wx::NewId; use constant TB_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; use constant TB_CUT => &Wx::NewId; use constant TB_SETTINGS => &Wx::NewId; # package variables to avoid passing lexicals to threads our $THUMBNAIL_DONE_EVENT : shared = Wx::NewEventType; our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType; our $ERROR_EVENT : shared = Wx::NewEventType; our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType; our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; use constant FILAMENT_CHOOSERS_SPACING => 0; use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds my $PreventListEvents = 0; sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config->new_from_defaults(qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width octoprint_host octoprint_apikey )); $self->{model} = Slic3r::Model->new; $self->{print} = Slic3r::Print->new; $self->{objects} = []; $self->{print}->set_status_cb(sub { my ($percent, $message) = @_; if ($Slic3r::have_threads) { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message]))); } else { $self->on_progress_event($percent, $message); } }); # Initialize preview notebook $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); # Initialize handlers for canvases my $on_select_object = sub { my ($obj_idx) = @_; $self->select_object($obj_idx); }; my $on_double_click = sub { $self->object_settings_dialog if $self->selected_object; }; my $on_right_click = sub { my ($canvas, $click_pos) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $menu = $self->object_menu; $canvas->PopupMenu($menu, $click_pos); $menu->Destroy; }; my $on_instances_moved = sub { $self->update; }; # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config}); $self->{preview_notebook}->AddPage($self->{canvas3D}, '3D'); $self->{canvas3D}->set_on_select_object($on_select_object); $self->{canvas3D}->set_on_double_click($on_double_click); $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); }); $self->{canvas3D}->set_on_instances_moved($on_instances_moved); $self->{canvas3D}->on_viewport_changed(sub { $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D}); }); } # Initialize 2D preview canvas $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); $self->{preview_notebook}->AddPage($self->{canvas}, '2D'); $self->{canvas}->on_select_object($on_select_object); $self->{canvas}->on_double_click($on_double_click); $self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); }); $self->{canvas}->on_instances_moved($on_instances_moved); # Initialize 3D toolpaths preview if ($Slic3r::GUI::have_OpenGL) { $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}); $self->{preview3D}->canvas->on_viewport_changed(sub { $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas); }); $self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview'); $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; } # Initialize toolpaths preview if ($Slic3r::GUI::have_OpenGL) { $self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print}); $self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers'); } EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { if ($self->{preview_notebook}->GetSelection == $self->{preview3D_page_idx}) { $self->{preview3D}->load_print; } }); # toolbar for object manipulation if (!&Wx::wxMSW) { Wx::ToolTip::Enable(1); $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); $self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new("$Slic3r::var/brick_delete.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new("$Slic3r::var/cross.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new("$Slic3r::var/bricks.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); $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}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG), ''); } else { my %tbar_buttons = ( add => "Add…", remove => "Delete", reset => "Delete All", arrange => "Arrange", increase => "", decrease => "", rotate45ccw => "", rotate45cw => "", changescale => "Scale…", split => "Split", cut => "Cut…", settings => "Settings…", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } } $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); $self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); EVT_KEY_DOWN($self->{list}, sub { my ($list, $event) = @_; if ($event->GetKeyCode == WXK_TAB) { $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); } else { $event->Skip; } }); # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_send_gcode} = Wx::Button->new($self, -1, "Send to printer", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT); #$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); #$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); $self->{btn_send_gcode}->Hide; if ($Slic3r::GUI::have_button_icons) { my %icons = qw( add brick_add.png remove brick_delete.png reset cross.png arrange bricks.png export_gcode cog_go.png send_gcode arrow_up.png export_stl brick_go.png increase add.png decrease delete.png rotate45cw arrow_rotate_clockwise.png rotate45ccw arrow_rotate_anticlockwise.png changescale arrow_out.png split shape_ungroup.png cut package.png settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/$icons{$_}", wxBITMAP_TYPE_PNG)); } } $self->selection_changed(0); $self->object_list_changed; EVT_BUTTON($self, $self->{btn_export_gcode}, sub { $self->export_gcode; Slic3r::thread_cleanup(); }); EVT_BUTTON($self, $self->{btn_send_gcode}, sub { $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir()); Slic3r::thread_cleanup(); }); EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); if ($self->{htoolbar}) { EVT_TOOL($self, TB_ADD, sub { $self->add; }); EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove EVT_TOOL($self, TB_RESET, sub { $self->reset; }); EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; }); EVT_TOOL($self, TB_MORE, sub { $self->increase; }); EVT_TOOL($self, TB_FEWER, sub { $self->decrease; }); EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) }); EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) }); EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); }); EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); } else { EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; }); EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; }); EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; }); EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; }); EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) }); EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45) }); EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); }); EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; my ($obj_idx) = @{$event->GetData}; return if !$self->{objects}[$obj_idx]; # object was deleted before thumbnail generation completed $self->on_thumbnail_made($obj_idx); }); EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { my ($self, $event) = @_; my ($percent, $message) = @{$event->GetData}; $self->on_progress_event($percent, $message); }); EVT_COMMAND($self, -1, $ERROR_EVENT, sub { my ($self, $event) = @_; Slic3r::GUI::show_error($self, @{$event->GetData}); }); EVT_COMMAND($self, -1, $EXPORT_COMPLETED_EVENT, sub { my ($self, $event) = @_; $self->on_export_completed($event->GetData); }); EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub { my ($self, $event) = @_; $self->on_process_completed($event->GetData); Slic3r::thread_cleanup(); }); if ($Slic3r::have_threads) { my $timer_id = Wx::NewId(); $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); EVT_TIMER($self, $timer_id, sub { my ($self, $event) = @_; $self->async_apply_config; }); } $self->{canvas}->update_bed_size; if ($self->{canvas3D}) { $self->{canvas3D}->update_bed_size; $self->{canvas3D}->zoom_to_bed; } if ($self->{preview3D}) { $self->{preview3D}->set_bed_shape($self->{config}->bed_shape); } $self->update; { my $presets; if ($self->GetFrame->{mode} eq 'expert') { $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); $presets->AddGrowableCol(1, 1); $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( print => 'Print settings', filament => 'Filament', printer => 'Printer', ); $self->{preset_choosers} = {}; for my $group (qw(print filament printer)) { my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); $self->{preset_choosers}{$group} = [$choice]; # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { $self->_on_select_preset($group, $choice); }); }); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); } } my $object_info_sizer; { my $box = Wx::StaticBox->new($self, -1, "Info"); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([350,-1]); my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(3, 1); $object_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( size => "Size", volume => "Volume", facets => "Facets", materials => "Materials", manifold => "Manifold", ); while (my $field = shift @info) { my $label = shift @info; my $text = Wx::StaticText->new($self, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $text->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($text, 0); $self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new("$Slic3r::var/error.png", wxBITMAP_TYPE_PNG)); $self->{object_info_manifold_warning_icon}->Hide; my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0); $h_sizer->Add($self->{"object_info_$field"}, 0); $grid_sizer->Add($h_sizer, 0, wxEXPAND); } else { $grid_sizer->Add($self->{"object_info_$field"}, 0); } } } my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $buttons_sizer->AddStretchSpacer(1); $buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); $right_sizer->Add($self->{list}, 1, wxEXPAND, 5); $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); $hsizer->Add($right_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar}; $sizer->Add($hsizer, 1, wxEXPAND, 0); $sizer->SetSizeHints($self); $self->SetSizer($sizer); } return $self; } # sets the callback sub on_select_preset { my ($self, $cb) = @_; $self->{on_select_preset} = $cb; } sub _on_select_preset { my $self = shift; my ($group, $choice) = @_; # if user changed filament preset, don't propagate this to the tabs if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { my @filament_presets = $self->filament_presets; $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0]) . ".ini"; $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_]) for 1 .. $#filament_presets; wxTheApp->save_settings; return; } # call GetSelection() in scalar context as it's context-aware $self->{on_select_preset}->($group, scalar $choice->GetSelection) if $self->{on_select_preset}; # get new config and generate on_config_change() event for updating plater and other things $self->on_config_change($self->GetFrame->config); } sub GetFrame { my ($self) = @_; return &Wx::GetTopLevelParent($self); } sub update_presets { my $self = shift; my ($group, $presets, $selected, $is_dirty) = @_; my @choosers = @{ $self->{preset_choosers}{$group} }; foreach my $choice (@choosers) { if ($group eq 'filament' && @choosers > 1) { # if we have more than one filament chooser, keep our selection # instead of importing the one from the tab $selected = $choice->GetSelection; $is_dirty = 0; } $choice->Clear; foreach my $preset (@$presets) { my $bitmap; if ($group eq 'filament') { my $config = $preset->config(['filament_colour']); my $rgb_hex = $config->filament_colour->[0]; if ($preset->default) { $bitmap = Wx::Bitmap->new("$Slic3r::var/spool.png", wxBITMAP_TYPE_PNG); } else { $rgb_hex =~ s/^#//; my @rgb = unpack 'C*', pack 'H*', $rgb_hex; my $image = Wx::Image->new(16,16); $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); $bitmap = Wx::Bitmap->new($image); } } elsif ($group eq 'print') { $bitmap = Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG); } elsif ($group eq 'printer') { $bitmap = Wx::Bitmap->new("$Slic3r::var/printer_empty.png", wxBITMAP_TYPE_PNG); } $choice->AppendString($preset->name, $bitmap); } if ($selected <= $#$presets) { if ($is_dirty) { $choice->SetString($selected, $choice->GetString($selected) . " (modified)"); } # call SetSelection() only after SetString() otherwise the new string # won't be picked up as the visible string $choice->SetSelection($selected); } } } sub filament_presets { my $self = shift; # force scalar context for GetSelection() as it's context-aware return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} }; } sub add { my $self = shift; my @input_files = wxTheApp->open_model($self); $self->load_file($_) for @input_files; } sub load_file { my $self = shift; my ($input_file) = @_; $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); wxTheApp->save_settings; my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0); $process_dialog->Pulse; local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); my $model = eval { Slic3r::Model->read_from_file($input_file) }; Slic3r::GUI::show_error($self, $@) if $@; if (defined $model) { $self->load_model_objects(@{$model->objects}); $self->statusbar->SetStatusText("Loaded " . basename($input_file)); } $process_dialog->Destroy; } sub load_model_objects { my ($self, @model_objects) = @_; my $bed_centerf = $self->bed_centerf; my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape}); my $bed_size = $bed_shape->bounding_box->size; my $need_arrange = 0; my $scaled_down = 0; my @obj_idx = (); foreach my $model_object (@model_objects) { my $o = $self->{model}->add_object($model_object); push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new( name => basename($model_object->input_file), ); push @obj_idx, $#{ $self->{objects} }; 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; # also aligns object to Z = 0 $o->add_instance(offset => $bed_centerf); } { # if the object is too large (more than 5 times the bed), scale it down my $size = $o->bounding_box->size; my $ratio = max(@$size[X,Y]) / unscale(max(@$bed_size[X,Y])); if ($ratio > 5) { $_->set_scaling_factor(1/$ratio) for @{$o->instances}; $scaled_down = 1; } } $self->{print}->auto_assign_extruders($o); $self->{print}->add_model_object($o); } # if user turned autocentering off, automatic arranging would disappoint them if (!$Slic3r::GUI::Settings->{_}{autocenter}) { $need_arrange = 0; } if ($scaled_down) { Slic3r::GUI::show_info( $self, 'Your object appears to be too large, so it was automatically scaled down to fit your print bed.', 'Object too large?', ); } foreach my $obj_idx (@obj_idx) { my $object = $self->{objects}[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx]; $self->{list}->InsertStringItem($obj_idx, $object->name); $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); $self->make_thumbnail($obj_idx); } $self->arrange if $need_arrange; $self->update; # zoom to objects $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D}; $self->{list}->Update; $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; } sub bed_centerf { my ($self) = @_; my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape}); my $bed_center = $bed_shape->bounding_box->center; return Slic3r::Pointf->new(unscale($bed_center->x), unscale($bed_center->y)); #) } sub remove { my $self = shift; my ($obj_idx) = @_; $self->stop_background_process; # Prevent toolpaths preview from rendering while we modify the Print object $self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D}; $self->{preview3D}->enabled(0) if $self->{preview3D}; # if no object index is supplied, remove the selected one if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; } splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); $self->{list}->DeleteItem($obj_idx); $self->object_list_changed; $self->select_object(undef); $self->update; $self->schedule_background_process; } sub reset { my $self = shift; $self->stop_background_process; # Prevent toolpaths preview from rendering while we modify the Print object $self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D}; $self->{preview3D}->enabled(0) if $self->{preview3D}; @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; $self->{list}->DeleteAllItems; $self->object_list_changed; $self->select_object(undef); $self->update; } sub increase { my ($self, $copies) = @_; $copies //= 1; my ($obj_idx, $object) = $self->selected_object; my $model_object = $self->{model}->objects->[$obj_idx]; my $instance = $model_object->instances->[-1]; for my $i (1..$copies) { $instance = $model_object->add_instance( offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}), scaling_factor => $instance->scaling_factor, rotation => $instance->rotation, ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; if ($Slic3r::GUI::Settings->{_}{autocenter}) { $self->arrange; } else { $self->update; } $self->schedule_background_process; } sub decrease { my ($self, $copies) = @_; $copies //= 1; $self->stop_background_process; my ($obj_idx, $object) = $self->selected_object; my $model_object = $self->{model}->objects->[$obj_idx]; if ($model_object->instances_count > $copies) { for my $i (1..$copies) { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); } else { $self->remove; } if ($self->{objects}[$obj_idx]) { $self->{list}->Select($obj_idx, 0); $self->{list}->Select($obj_idx, 1); } $self->update; $self->schedule_background_process; } sub set_number_of_copies { my ($self) = @_; $self->pause_background_process; # get current number of copies my ($obj_idx, $object) = $self->selected_object; my $model_object = $self->{model}->objects->[$obj_idx]; # prompt user my $copies = Wx::GetNumberFromUser("", "Enter the number of copies of the selected object:", "Copies", $model_object->instances_count, 0, 1000, $self); my $diff = $copies - $model_object->instances_count; if ($diff == 0) { # no variation $self->resume_background_process; } elsif ($diff > 0) { $self->increase($diff); } elsif ($diff < 0) { $self->decrease(-$diff); } } sub rotate { my $self = shift; my ($angle, $axis) = @_; # angle is in degrees $axis //= Z; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; # we need thumbnail to be computed before allowing rotation return if !$object->thumbnail; if (!defined $angle) { my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate around $axis_name axis", $model_instance->rotation, -364, 364, $self); return if !$angle || $angle == -1; $angle = 0 - $angle; # rotate clockwise (be consistent with button icon) } $self->stop_background_process; if ($axis == Z) { my $new_angle = $model_instance->rotation + deg2rad($angle); $_->set_rotation($new_angle) for @{ $model_object->instances }; $object->transform_thumbnail($self->{model}, $obj_idx); } else { # rotation around X and Y needs to be performed on mesh # so we first apply any Z rotation if ($model_instance->rotation != 0) { $model_object->rotate($model_instance->rotation, Z); $_->set_rotation(0) for @{ $model_object->instances }; } $model_object->rotate(deg2rad($angle), $axis); # realign object to Z = 0 $model_object->center_around_origin; $self->make_thumbnail($obj_idx); } $model_object->update_bounding_box; # update print and start background processing $self->{print}->add_model_object($model_object, $obj_idx); $self->selection_changed; # refresh info (size etc.) $self->update; $self->schedule_background_process; } sub flip { my ($self, $axis) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; # apply Z rotation before flipping if ($model_instance->rotation != 0) { $model_object->rotate($model_instance->rotation, Z); $_->set_rotation(0) for @{ $model_object->instances }; } $model_object->flip($axis); $model_object->update_bounding_box; # realign object to Z = 0 $model_object->center_around_origin; $self->make_thumbnail($obj_idx); # update print and start background processing $self->stop_background_process; $self->{print}->add_model_object($model_object, $obj_idx); $self->selection_changed; # refresh info (size etc.) $self->update; $self->schedule_background_process; } sub changescale { my ($self, $axis) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; # we need thumbnail to be computed before allowing scaling return if !$object->thumbnail; if (defined $axis) { my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale along $axis_name", 100, 0, 100000, $self); return if !$scale || $scale < 0; # apply Z rotation before scaling if ($model_instance->rotation != 0) { $model_object->rotate($model_instance->rotation, Z); $_->set_rotation(0) for @{ $model_object->instances }; } my $versor = [1,1,1]; $versor->[$axis] = $scale/100; $model_object->scale_xyz(Slic3r::Pointf3->new(@$versor)); # object was already aligned to Z = 0, so no need to realign it $self->make_thumbnail($obj_idx); } else { # max scale factor should be above 2540 to allow importing files exported in inches my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", 'Scale', $model_instance->scaling_factor*100, 0, 100000, $self); return if !$scale || $scale < 0; $self->{list}->SetItem($obj_idx, 2, "$scale%"); $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; foreach my $range (@{ $model_object->layer_height_ranges }) { $range->[0] *= $variation; $range->[1] *= $variation; } $_->set_scaling_factor($scale) for @{ $model_object->instances }; $object->transform_thumbnail($self->{model}, $obj_idx); } $model_object->update_bounding_box; # update print and start background processing $self->stop_background_process; $self->{print}->add_model_object($model_object, $obj_idx); $self->selection_changed(1); # refresh info (size, volume etc.) $self->update; $self->schedule_background_process; } sub arrange { my $self = shift; $self->pause_background_process; my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); eval { $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); }; # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # when parts don't fit in print bed $self->update(1); } sub split_object { my $self = shift; my ($obj_idx, $current_object) = $self->selected_object; # we clone model object because split_object() adds the split volumes # into the same model object, thus causing duplicates when we call load_model_objects() my $new_model = $self->{model}->clone; # store this before calling get_object() my $current_model_object = $new_model->get_object($obj_idx); if ($current_model_object->volumes_count > 1) { Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material."); return; } $self->pause_background_process; my @model_objects = @{$current_model_object->split_object}; if (@model_objects == 1) { $self->resume_background_process; Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part."); $self->resume_background_process; return; } foreach my $object (@model_objects) { $object->instances->[$_]->offset->translate($_ * 10, $_ * 10) for 1..$#{ $object->instances }; # we need to center this single object around origin $object->center_around_origin; } # 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 $self->load_model_objects(@model_objects); } sub schedule_background_process { my ($self) = @_; if (defined $self->{apply_config_timer}) { $self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot } } sub async_apply_config { my ($self) = @_; # reset preview canvases $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; # pause process thread before applying new config # since we don't want to touch data that is being used by the threads $self->pause_background_process; # apply new config my $invalidated = $self->{print}->apply_config($self->GetFrame->config); return if !$Slic3r::GUI::Settings->{_}{background_processing}; if ($invalidated) { # kill current thread if any $self->stop_background_process; } else { $self->resume_background_process; } # schedule a new process thread in case it wasn't running $self->start_background_process; } sub start_background_process { my ($self) = @_; return if !$Slic3r::have_threads; return if !@{$self->{objects}}; return if $self->{process_thread}; # It looks like declaring a local $SIG{__WARN__} prevents the ugly # "Attempt to free unreferenced scalar" warning... local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); # don't start process thread if config is not valid eval { # this will throw errors if config is not valid $self->GetFrame->config->validate; $self->{print}->validate; }; if ($@) { $self->statusbar->SetStatusText($@); return; } # apply extra variables { my $extra = $self->GetFrame->extra_variables; $self->{print}->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; } # start thread @_ = (); $self->{process_thread} = Slic3r::spawn_thread(sub { eval { $self->{print}->process; }; if ($@) { Slic3r::debugf "Discarding background process error: $@\n"; Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, $@)); } else { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, undef)); } Slic3r::thread_cleanup(); }); Slic3r::debugf "Background processing started.\n"; } sub stop_background_process { my ($self) = @_; $self->{apply_config_timer}->Stop if defined $self->{apply_config_timer}; $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $self->statusbar->SetStatusText(""); $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; if ($self->{process_thread}) { Slic3r::debugf "Killing background process.\n"; Slic3r::kill_all_threads(); $self->{process_thread} = undef; } else { Slic3r::debugf "No background process running.\n"; } # if there's an export process, kill that one as well if ($self->{export_thread}) { Slic3r::debugf "Killing background export process.\n"; Slic3r::kill_all_threads(); $self->{export_thread} = undef; } } sub pause_background_process { my ($self) = @_; if ($self->{process_thread} || $self->{export_thread}) { Slic3r::pause_all_threads(); return 1; } elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) { $self->{apply_config_timer}->Stop; return 1; } return 0; } sub resume_background_process { my ($self) = @_; if ($self->{process_thread} || $self->{export_thread}) { Slic3r::resume_all_threads(); } } sub export_gcode { my ($self, $output_file) = @_; return if !@{$self->{objects}}; if ($self->{export_gcode_output_file}) { Wx::MessageDialog->new($self, "Another export job is currently running.", 'Error', wxOK | wxICON_ERROR)->ShowModal; return; } # if process is not running, validate config # (we assume that if it is running, config is valid) eval { # this will throw errors if config is not valid $self->GetFrame->config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print my $config = $self->GetFrame->config; eval { # this will throw errors if config is not valid $config->validate; $self->{print}->apply_config($config); $self->{print}->validate; }; if (!$Slic3r::have_threads) { Slic3r::GUI::catch_error($self) and return; } # select output file if ($output_file) { $self->{export_gcode_output_file} = $self->{print}->expanded_output_filepath($output_file); } else { my $default_output_file = $self->{print}->expanded_output_filepath($main::opt{output}); my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)), basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; } my $path = Slic3r::decode_path($dlg->GetPath); $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path); wxTheApp->save_settings; $self->{export_gcode_output_file} = $path; $dlg->Destroy; } $self->statusbar->StartBusy; if ($Slic3r::have_threads) { $self->statusbar->SetCancelCallback(sub { $self->stop_background_process; $self->statusbar->SetStatusText("Export cancelled"); $self->{export_gcode_output_file} = undef; $self->{send_gcode_file} = undef; # this updates buttons status $self->object_list_changed; }); # start background process, whose completion event handler # will detect $self->{export_gcode_output_file} and proceed with export $self->start_background_process; } else { eval { $self->{print}->process; $self->{print}->export_gcode(output_file => $self->{export_gcode_output_file}); }; my $result = !Slic3r::GUI::catch_error($self); $self->on_export_completed($result); } # this updates buttons status $self->object_list_changed; return $self->{export_gcode_output_file}; } # This gets called only if we have threads. sub on_process_completed { my ($self, $error) = @_; $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $self->statusbar->SetStatusText($error // ""); Slic3r::debugf "Background processing completed.\n"; $self->{process_thread}->detach if $self->{process_thread}; $self->{process_thread} = undef; return if $error; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; # if we have an export filename, start a new thread for exporting G-code if ($self->{export_gcode_output_file}) { @_ = (); # workaround for "Attempt to free un referenced scalar..." our $_thread_self = $self; $self->{export_thread} = Slic3r::spawn_thread(sub { eval { $_thread_self->{print}->export_gcode(output_file => $_thread_self->{export_gcode_output_file}); }; if ($@) { Wx::PostEvent($_thread_self, Wx::PlThreadEvent->new(-1, $ERROR_EVENT, shared_clone([ $@ ]))); Wx::PostEvent($_thread_self, Wx::PlThreadEvent->new(-1, $EXPORT_COMPLETED_EVENT, 0)); } else { Wx::PostEvent($_thread_self, Wx::PlThreadEvent->new(-1, $EXPORT_COMPLETED_EVENT, 1)); } Slic3r::thread_cleanup(); }); Slic3r::debugf "Background G-code export started.\n"; } } # This gets called also if we have no threads. sub on_progress_event { my ($self, $percent, $message) = @_; $self->statusbar->SetProgress($percent); $self->statusbar->SetStatusText("$message…"); } # This gets called also if we don't have threads. sub on_export_completed { my ($self, $result) = @_; $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $self->statusbar->SetStatusText(""); Slic3r::debugf "Background export process completed.\n"; $self->{export_thread}->detach if $self->{export_thread}; $self->{export_thread} = undef; my $message; my $send_gcode = 0; if ($result) { if ($self->{send_gcode_file}) { $message = "Sending G-code file to the OctoPrint server..."; $send_gcode = 1; } else { $message = "G-code file exported to " . $self->{export_gcode_output_file}; } } else { $message = "Export failed"; } $self->{export_gcode_output_file} = undef; $self->statusbar->SetStatusText($message); wxTheApp->notify($message); $self->send_gcode if $send_gcode; $self->{send_gcode_file} = undef; # this updates buttons status $self->object_list_changed; } sub send_gcode { my ($self) = @_; $self->statusbar->StartBusy; my $ua = LWP::UserAgent->new; $ua->timeout(180); my $path = Slic3r::encode_path($self->{send_gcode_file}); my $res = $ua->post( "http://" . $self->{config}->octoprint_host . "/api/files/local", Content_Type => 'form-data', 'X-Api-Key' => $self->{config}->octoprint_apikey, Content => [ # OctoPrint doesn't like Windows paths so we use basename() # Also, since we need to read from filesystem we process it through encode_path() file => [ $path, basename($path) ], ], ); $self->statusbar->StopBusy; if ($res->is_success) { $self->statusbar->SetStatusText("G-code file successfully uploaded to the OctoPrint server"); } else { my $message = "Error while uploading to the OctoPrint server: " . $res->status_line; Slic3r::GUI::show_error($self, $message); $self->statusbar->SetStatusText($message); } } sub export_stl { my $self = shift; return if !@{$self->{objects}}; my $output_file = $self->_get_export_file('STL') or return; Slic3r::Format::STL->write_file($output_file, $self->{model}, binary => 1); $self->statusbar->SetStatusText("STL file exported to $output_file"); # this method gets executed in a separate thread by wxWidgets since it's a button handler Slic3r::thread_cleanup() if $Slic3r::have_threads; } sub export_object_stl { my $self = shift; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; my $output_file = $self->_get_export_file('STL') or return; Slic3r::Format::STL->write_file($output_file, $model_object->mesh, binary => 1); $self->statusbar->SetStatusText("STL file exported to $output_file"); } sub export_amf { my $self = shift; return if !@{$self->{objects}}; my $output_file = $self->_get_export_file('AMF') or return; Slic3r::Format::AMF->write_file($output_file, $self->{model}); $self->statusbar->SetStatusText("AMF file exported to $output_file"); # this method gets executed in a separate thread by wxWidgets since it's a menu handler Slic3r::thread_cleanup() if $Slic3r::have_threads; } sub _get_export_file { my $self = shift; my ($format) = @_; my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; my $output_file = $main::opt{output}; { $output_file = $self->{print}->expanded_output_filepath($output_file); $output_file =~ s/\.gcode$/$suffix/i; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; } $output_file = Slic3r::decode_path($dlg->GetPath); $dlg->Destroy; } return $output_file; } sub make_thumbnail { my $self = shift; my ($obj_idx) = @_; my $plater_object = $self->{objects}[$obj_idx]; $plater_object->thumbnail(Slic3r::ExPolygon::Collection->new); my $cb = sub { $plater_object->make_thumbnail($self->{model}, $obj_idx); if ($Slic3r::have_threads) { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx ]))); Slic3r::thread_cleanup(); threads->exit; } else { $self->on_thumbnail_made($obj_idx); } }; @_ = (); $Slic3r::have_threads ? threads->create(sub { $cb->(); Slic3r::thread_cleanup(); })->detach : $cb->(); } sub on_thumbnail_made { my $self = shift; my ($obj_idx) = @_; $self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx); $self->refresh_canvases; } # 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 { my ($self, $force_autocenter) = @_; if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); } my $running = $self->pause_background_process; my $invalidated = $self->{print}->reload_model_instances(); # The mere fact that no steps were invalidated when reloading model instances # doesn't mean that all steps were done: for example, validation might have # failed upon previous instance move, so we have no running thread and no steps # are invalidated on this move, thus we need to schedule a new run. if ($invalidated || !$running) { $self->schedule_background_process; } else { $self->resume_background_process; } $self->refresh_canvases; } sub on_extruders_change { my ($self, $num_extruders) = @_; my $choices = $self->{preset_choosers}{filament}; while (@$choices < $num_extruders) { # copy strings from first choice my @presets = $choices->[0]->GetStrings; # initialize new choice my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); push @$choices, $choice; # copy icons from first choice $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; # insert new choice into sizer $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { $self->_on_select_preset('filament', $choice); }); }); # initialize selection my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; $choice->SetSelection($i || 0); } # remove unused choices if any while (@$choices > $num_extruders) { $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice $choices->[-1]->Destroy; pop @$choices; } $self->Layout; } sub on_config_change { my $self = shift; my ($config) = @_; foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); if ($opt_key eq 'bed_shape') { $self->{canvas}->update_bed_size; $self->{canvas3D}->update_bed_size if $self->{canvas3D}; $self->{preview3D}->set_bed_shape($self->{config}->bed_shape) if $self->{preview3D}; $self->update; } elsif ($opt_key eq 'octoprint_host') { if ($config->get('octoprint_host')) { $self->{btn_send_gcode}->Show; } else { $self->{btn_send_gcode}->Hide; } $self->Layout; } } return if !$self->GetFrame->is_loaded; # (re)start timer $self->schedule_background_process; } sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->refresh_canvases; } } sub list_item_selected { my ($self, $event) = @_; return if $PreventListEvents; my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->refresh_canvases; } sub list_item_activated { my ($self, $event, $obj_idx) = @_; $obj_idx //= $event->GetIndex; $self->object_settings_dialog($obj_idx); } sub object_cut_dialog { my $self = shift; my ($obj_idx) = @_; if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; } if (!$Slic3r::GUI::have_OpenGL) { Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions)."); return; } 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(grep defined($_), @new_objects); $self->arrange; } } sub object_settings_dialog { my $self = shift; my ($obj_idx) = @_; if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; } my $model_object = $self->{model}->objects->[$obj_idx]; # validate config before opening the settings dialog because # that dialog can't be closed if validation fails, but user # can't fix any error which is outside that dialog return unless $self->validate_config; my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, object => $self->{objects}[$obj_idx], model_object => $model_object, ); $self->pause_background_process; $dlg->ShowModal; # update thumbnail since parts may have changed if ($dlg->PartsChanged) { # recenter and re-align to Z = 0 $model_object->center_around_origin; $self->make_thumbnail($obj_idx); } # update print if ($dlg->PartsChanged || $dlg->PartSettingsChanged) { $self->stop_background_process; $self->{print}->reload_object($obj_idx); $self->schedule_background_process; } else { $self->resume_background_process; } } sub object_list_changed { my $self = shift; my $have_objects = @{$self->{objects}} ? 1 : 0; my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(reset arrange export_gcode export_stl send_gcode); if ($self->{export_gcode_output_file} || $self->{send_gcode_file}) { $self->{btn_export_gcode}->Disable; $self->{btn_send_gcode}->Disable; } if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_objects) for (TB_RESET, TB_ARRANGE); } } sub selection_changed { my $self = shift; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); } if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { my $model_object = $self->{model}->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); $self->{object_info_materials}->SetLabel($model_object->materials_count); if (my $stats = $model_object->mesh_stats) { $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3))); $self->{object_info_facets}->SetLabel(sprintf('%d (%d shells)', $model_object->facets_count, $stats->{number_of_parts})); if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) { $self->{object_info_manifold}->SetLabel(sprintf("Auto-repaired (%d errors)", $errors)); $self->{object_info_manifold_warning_icon}->Show; # we don't show normals_fixed because we never provide normals # to admesh, so it generates normals for all facets my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges', @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)}; $self->{object_info_manifold}->SetToolTipString($message); $self->{object_info_manifold_warning_icon}->SetToolTipString($message); } else { $self->{object_info_manifold}->SetLabel("Yes"); } } else { $self->{object_info_facets}->SetLabel($object->facets); } } else { $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); $self->{object_info_manifold_warning_icon}->Hide; $self->{object_info_manifold}->SetToolTipString(""); } $self->Layout; } # prepagate the event to the frame (a custom Wx event would be cleaner) $self->GetFrame->on_plater_selection_changed($have_sel); } sub select_object { my ($self, $obj_idx) = @_; $_->selected(0) for @{ $self->{objects} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # We use this flag to avoid circular event handling # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, # whose event handler calls this method again and again and again $PreventListEvents = 1; $self->{list}->Select($obj_idx, 1); $PreventListEvents = 0; } else { # TODO: deselect all in list } $self->selection_changed(1); } sub selected_object { my $self = shift; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; return undef if !defined $obj_idx; return ($obj_idx, $self->{objects}[$obj_idx]), } sub refresh_canvases { my ($self) = @_; $self->{canvas}->Refresh; $self->{canvas3D}->update if $self->{canvas3D}; $self->{preview3D}->reload_print if $self->{preview3D}; } sub validate_config { my $self = shift; eval { $self->GetFrame->config->validate; }; return 0 if Slic3r::GUI::catch_error($self); return 1; } sub statusbar { my $self = shift; return $self->GetFrame->{statusbar}; } sub object_menu { my ($self) = @_; my $frame = $self->GetFrame; my $menu = Wx::Menu->new; $frame->_append_menu_item($menu, "Delete\tCtrl+Del", 'Remove the selected object', sub { $self->remove; }, undef, 'brick_delete.png'); $frame->_append_menu_item($menu, "Increase copies\tCtrl++", 'Place one more copy of the selected object', sub { $self->increase; }, undef, 'add.png'); $frame->_append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub { $self->decrease; }, undef, 'delete.png'); $frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub { $self->set_number_of_copies; }, undef, 'textfield.png'); $menu->AppendSeparator(); $frame->_append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub { $self->rotate(-45); }, undef, 'arrow_rotate_clockwise.png'); $frame->_append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub { $self->rotate(+45); }, undef, 'arrow_rotate_anticlockwise.png'); my $rotateMenu = Wx::Menu->new; my $rotateMenuItem = $menu->AppendSubMenu($rotateMenu, "Rotate", 'Rotate the selected object by an arbitrary angle'); $frame->_set_menu_item_icon($rotateMenuItem, 'textfield.png'); $frame->_append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub { $self->rotate(undef, X); }); $frame->_append_menu_item($rotateMenu, "Around Y axis…", 'Rotate the selected object by an arbitrary angle around Y axis', sub { $self->rotate(undef, Y); }); $frame->_append_menu_item($rotateMenu, "Around Z axis…", 'Rotate the selected object by an arbitrary angle around Z axis', sub { $self->rotate(undef, Z); }); my $flipMenu = Wx::Menu->new; my $flipMenuItem = $menu->AppendSubMenu($flipMenu, "Flip", 'Mirror the selected object'); $frame->_set_menu_item_icon($flipMenuItem, 'shape_flip_horizontal.png'); $frame->_append_menu_item($flipMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub { $self->flip(X); }); $frame->_append_menu_item($flipMenu, "Along Y axis…", 'Mirror the selected object along the Y axis', sub { $self->flip(Y); }); $frame->_append_menu_item($flipMenu, "Along Z axis…", 'Mirror the selected object along the Z axis', sub { $self->flip(Z); }); my $scaleMenu = Wx::Menu->new; my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis'); $frame->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png'); $frame->_append_menu_item($scaleMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef); }); $frame->_append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { $self->changescale(X); }); $frame->_append_menu_item($scaleMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { $self->changescale(Y); }); $frame->_append_menu_item($scaleMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { $self->changescale(Z); }); $frame->_append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub { $self->split_object; }, undef, 'shape_ungroup.png'); $frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub { $self->object_cut_dialog; }, undef, 'package.png'); $menu->AppendSeparator(); $frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub { $self->object_settings_dialog; }, undef, 'cog.png'); $menu->AppendSeparator(); $frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub { $self->export_object_stl; }, undef, 'brick_go.png'); return $menu; } package Slic3r::GUI::Plater::DropTarget; use Wx::DND; use base 'Wx::FileDropTarget'; sub new { my $class = shift; my ($window) = @_; my $self = $class->SUPER::new; $self->{window} = $window; return $self; } sub OnDropFiles { my $self = shift; my ($x, $y, $filenames) = @_; # stop scalars leaking on older perl # https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602 @_ = (); # only accept STL, OBJ and AMF files return 0 if grep !/\.(?:stl|obj|amf(?:\.xml)?)$/i, @$filenames; $self->{window}->load_file($_) for @$filenames; } package Slic3r::GUI::Plater::Object; use Moo; use List::Util qw(first); use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad); has 'name' => (is => 'rw', required => 1); has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms has 'transformed_thumbnail' => (is => 'rw'); has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units has 'selected' => (is => 'rw', default => sub { 0 }); sub make_thumbnail { my ($self, $model, $obj_idx) = @_; # make method idempotent $self->thumbnail->clear; my $mesh = $model->objects->[$obj_idx]->raw_mesh; if ($mesh->facets_count <= 5000) { # remove polygons with area <= 1mm my $area_threshold = Slic3r::Geometry::scale 1; $self->thumbnail->append( grep $_->area >= $area_threshold, @{ $mesh->horizontal_projection }, # horizontal_projection returns scaled expolygons ); $self->thumbnail->simplify(0.5); } else { my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull); $self->thumbnail->append($convex_hull); } return $self->thumbnail; } sub transform_thumbnail { my ($self, $model, $obj_idx) = @_; return unless defined $self->thumbnail; my $model_object = $model->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; # the order of these transformations MUST be the same everywhere, including # in Slic3r::Print->add_model_object() my $t = $self->thumbnail->clone; $t->rotate($model_instance->rotation, Slic3r::Point->new(0,0)); $t->scale($model_instance->scaling_factor); $self->transformed_thumbnail($t); } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/000077500000000000000000000000001254023100400164315ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/GUI/Plater/2D.pm000066400000000000000000000316361254023100400172450ustar00rootroot00000000000000package Slic3r::GUI::Plater::2D; use strict; use warnings; use utf8; use List::Util qw(min max first); use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl); use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_SIZE); 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_right_click} = sub {}; $self->{on_instances_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); EVT_SIZE($self, sub { $self->update_bed_size; $self->Refresh; }); 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_right_click { my ($self, $cb) = @_; $self->{on_right_click} = $cb; } sub on_instances_moved { my ($self, $cb) = @_; $self->{on_instances_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}); $dc->DrawLine(map @$_, @$_) for @{$self->{grid}}; # draw bed { $dc->SetPen($self->{print_center_pen}); $dc->SetBrush($self->{transparent_brush}); $dc->DrawPolygon($self->scaled_points_to_pixel($self->{bed_polygon}, 1), 0, 0); } # draw print center if (@{$self->{objects}} && $Slic3r::GUI::Settings->{_}{autocenter}) { my $center = $self->unscaled_point_to_pixel($self->{print_center}); $dc->SetPen($self->{print_center_pen}); $dc->DrawLine($center->[X], 0, $center->[X], $size[Y]); $dc->DrawLine(0, $center->[Y], $size[X], $center->[Y]); $dc->SetTextForeground(Wx::Colour->new(0,0,0)); $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)); $dc->DrawLabel("X = " . sprintf('%.0f', $self->{print_center}->[X]), Wx::Rect->new(0, 0, $center->[X]*2, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM); $dc->DrawRotatedText("Y = " . sprintf('%.0f', $self->{print_center}->[Y]), 0, $center->[Y]+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->scaled_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->scaled_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 max($self->{config}->brim_width + $self->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))}; $dc->SetPen($self->{skirt_pen}); $dc->SetBrush($self->{transparent_brush}); $dc->DrawPolygon($self->scaled_points_to_pixel($convex_hull, 1), 0, 0); } } $event->Skip; } sub mouse_event { my ($self, $event) = @_; my $pos = $event->GetPosition; my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]] if ($event->ButtonDown) { $self->{on_select_object}->(undef); # traverse objects and instances in reverse order, so that if they're overlapping # we get the one that gets drawn last, thus on top (as user expects that to move) OBJECTS: for my $obj_idx (reverse 0 .. $#{$self->{objects}}) { my $object = $self->{objects}->[$obj_idx]; for my $instance_idx (reverse 0 .. $#{ $object->instance_thumbnails }) { my $thumbnail = $object->instance_thumbnails->[$instance_idx]; if (defined first { $_->contour->contains_point($point) } @$thumbnail) { $self->{on_select_object}->($obj_idx); if ($event->LeftDown) { # start dragging 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 $point->x - $instance_origin->[X], $point->y - $instance_origin->[Y], #- ]; $self->{drag_object} = [ $obj_idx, $instance_idx ]; } elsif ($event->RightDown) { $self->{on_right_click}->($pos); } last OBJECTS; } } } $self->Refresh; } elsif ($event->LeftUp) { $self->{on_instances_moved}->() if $self->{drag_object}; $self->{drag_start_pos} = undef; $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); } elsif ($event->LeftDClick) { $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($point->[X] - $self->{drag_start_pos}[X]), unscale($point->[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($point) } map @$_, map @{$_->instance_thumbnails}, @{ $self->{objects} }) { $cursor = Wx::Cursor->new(wxCURSOR_HAND); } $self->SetCursor($cursor); } } sub update_bed_size { my $self = shift; # when the canvas is not rendered yet, its GetSize() method returns 0,0 my $canvas_size = $self->GetSize; my ($canvas_w, $canvas_h) = ($canvas_size->GetWidth, $canvas_size->GetHeight); return if $canvas_w == 0; # get bed shape polygon $self->{bed_polygon} = my $polygon = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape}); my $bb = $polygon->bounding_box; my $size = $bb->size; # calculate the scaling factor needed for constraining print bed area inside preview # scaling_factor is expressed in pixel / mm $self->{scaling_factor} = min($canvas_w / unscale($size->x), $canvas_h / unscale($size->y)); #) # calculate the displacement needed to center bed $self->{bed_origin} = [ $canvas_w/2 - (unscale($bb->x_max + $bb->x_min)/2 * $self->{scaling_factor}), $canvas_h - ($canvas_h/2 - (unscale($bb->y_max + $bb->y_min)/2 * $self->{scaling_factor})), ]; # calculate print center my $center = $bb->center; $self->{print_center} = [ unscale($center->x), unscale($center->y) ]; #)) # cache bed contours and grid { my $step = scale 10; # 1cm grid my @polylines = (); for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) { push @polylines, Slic3r::Polyline->new([$x, $bb->y_min], [$x, $bb->y_max]); } for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) { push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]); } @polylines = @{intersection_pl(\@polylines, [$polygon])}; $self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ]; } } sub clean_instance_thumbnails { my ($self) = @_; foreach my $object (@{ $self->{objects} }) { @{ $object->instance_thumbnails } = (); } } # convert a model coordinate into a pixel coordinate sub unscaled_point_to_pixel { my ($self, $point) = @_; my $canvas_height = $self->GetSize->GetHeight; my $zero = $self->{bed_origin}; return [ $point->[X] * $self->{scaling_factor} + $zero->[X], $canvas_height - $point->[Y] * $self->{scaling_factor} + ($zero->[Y] - $canvas_height), ]; } sub scaled_points_to_pixel { my ($self, $points, $unscale) = @_; my $result = []; foreach my $point (@$points) { $point = [ map unscale($_), @$point ] if $unscale; push @$result, $self->unscaled_point_to_pixel($point); } return $result; } sub point_to_model_units { my ($self, $point) = @_; my $zero = $self->{bed_origin}; return Slic3r::Point->new( scale ($point->[X] - $zero->[X]) / $self->{scaling_factor}, scale ($zero->[Y] - $point->[Y]) / $self->{scaling_factor}, ); } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/2DToolpaths.pm000066400000000000000000000553661254023100400211510ustar00rootroot00000000000000package Slic3r::GUI::Plater::2DToolpaths; use strict; use warnings; use utf8; use Slic3r::Print::State ':steps'; use Wx qw(:misc :sizer :slider :statictext wxWHITE); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN); use base qw(Wx::Panel Class::Accessor); __PACKAGE__->mk_accessors(qw(print enabled)); sub new { my $class = shift; my ($parent, $print) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); $self->SetBackgroundColour(wxWHITE); # init GUI elements my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print); my $slider = $self->{slider} = Wx::Slider->new( $self, -1, 0, # default 0, # min # we set max to a bogus non-zero value because the MSW implementation of wxSlider # will skip drawing the slider if max <= min: 1, # max wxDefaultPosition, wxDefaultSize, wxVERTICAL | wxSL_INVERSE, ); my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, [40,-1], wxALIGN_CENTRE_HORIZONTAL); $z_label->SetFont($Slic3r::GUI::small_font); my $vsizer = Wx::BoxSizer->new(wxVERTICAL); $vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3); $vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3); my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0); $sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); EVT_SLIDER($self, $slider, sub { $self->set_z($self->{layers_z}[$slider->GetValue]) if $self->enabled; }); EVT_KEY_DOWN($canvas, sub { my ($s, $event) = @_; my $key = $event->GetKeyCode; if ($key == 85 || $key == 315) { $slider->SetValue($slider->GetValue + 1); $self->set_z($self->{layers_z}[$slider->GetValue]); } elsif ($key == 68 || $key == 317) { $slider->SetValue($slider->GetValue - 1); $self->set_z($self->{layers_z}[$slider->GetValue]); } }); $self->SetSizer($sizer); $self->SetMinSize($self->GetSize); $sizer->SetSizeHints($self); # init print $self->{print} = $print; $self->reload_print; return $self; } sub reload_print { my ($self) = @_; # we require that there's at least one object and the posSlice step # is performed on all of them (this ensures that _shifted_copies was # populated and we know the number of layers) if (!$self->print->object_step_done(STEP_SLICE)) { $self->enabled(0); $self->{slider}->Hide; $self->{canvas}->Refresh; # clears canvas return; } $self->{canvas}->bb($self->print->total_bounding_box); $self->{canvas}->_dirty(1); my %z = (); # z => 1 foreach my $object (@{$self->{print}->objects}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { $z{$layer->print_z} = 1; } } $self->enabled(1); $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; $self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1); if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) { $self->set_z($self->{layers_z}[$z_idx]); } else { $self->{slider}->SetValue(0); $self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}}; } $self->{slider}->Show; $self->Layout; } sub set_z { my ($self, $z) = @_; return if !$self->enabled; $self->{z_label}->SetLabel(sprintf '%.2f', $z); $self->{canvas}->set_z($z); } package Slic3r::GUI::Plater::2DToolpaths::Canvas; use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); use Wx::GLCanvas qw(:all); use List::Util qw(min max first); use Slic3r::Geometry qw(scale unscale epsilon X Y); use Slic3r::Print::State ':steps'; __PACKAGE__->mk_accessors(qw( print z layers color init bb _camera_bb _dirty _zoom _camera_target _drag_start_xy )); # make OpenGL::Array thread-safe { no warnings 'redefine'; *OpenGL::Array::CLONE_SKIP = sub { 1 }; } sub new { my ($class, $parent, $print) = @_; my $self = $class->SUPER::new($parent); $self->print($print); $self->_zoom(1); # 2D point in model space $self->_camera_target(Slic3r::Pointf->new(0,0)); EVT_PAINT($self, sub { my $dc = Wx::PaintDC->new($self); $self->Render($dc); }); EVT_SIZE($self, sub { $self->_dirty(1) }); EVT_IDLE($self, sub { return unless $self->_dirty; return if !$self->IsShownOnScreen; $self->Resize; $self->Refresh; }); EVT_MOUSEWHEEL($self, sub { my ($self, $e) = @_; my $old_zoom = $self->_zoom; # Calculate the zoom delta and apply it to the current zoom factor my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); $zoom = max(min($zoom, 4), -4); $zoom /= 10; $self->_zoom($self->_zoom / (1-$zoom)); $self->_zoom(1) if $self->_zoom > 1; # prevent from zooming out too much { # In order to zoom around the mouse point we need to translate # the camera target. This math is almost there but not perfect yet... my $camera_bb_size = $self->_camera_bb->size; my $size = Slic3r::Pointf->new($self->GetSizeWH); my $pos = Slic3r::Pointf->new($e->GetPositionXY); # calculate the zooming center in pixel coordinates relative to the viewport center my $vec = Slic3r::Pointf->new($pos->x - $size->x/2, $pos->y - $size->y/2); #- # calculate where this point will end up after applying the new zoom my $vec2 = $vec->clone; $vec2->scale($old_zoom / $self->_zoom); # move the camera target by the difference of the two positions $self->_camera_target->translate( -($vec->x - $vec2->x) * $camera_bb_size->x / $size->x, ($vec->y - $vec2->y) * $camera_bb_size->y / $size->y, #// ); } $self->_dirty(1); $self->Refresh; }); EVT_MOUSE_EVENTS($self, \&mouse_event); return $self; } sub mouse_event { my ($self, $e) = @_; my $pos = Slic3r::Pointf->new($e->GetPositionXY); if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; } elsif ($e->Dragging) { if ($e->LeftIsDown || $e->MiddleIsDown || $e->RightIsDown) { # if dragging, translate view if (defined $self->_drag_start_xy) { my $move = $self->_drag_start_xy->vector_to($pos); # in pixels # get viewport and camera size in order to convert pixel to model units my ($x, $y) = $self->GetSizeWH; my $camera_bb_size = $self->_camera_bb->size; # compute translation in model units $self->_camera_target->translate( -$move->x * $camera_bb_size->x / $x, $move->y * $camera_bb_size->y / $y, # /** ); $self->_dirty(1); $self->Refresh; } $self->_drag_start_xy($pos); } } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) { $self->_drag_start_xy(undef); } else { $e->Skip(); } } sub set_z { my ($self, $z) = @_; my $print = $self->print; # can we have interlaced layers? my $interlaced = (defined first { $_->config->support_material } @{$print->objects}) || (defined first { $_->config->infill_every_layers > 1 } @{$print->regions}); my $max_layer_height = $print->max_allowed_layer_height; my @layers = (); foreach my $object (@{$print->objects}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { if ($interlaced) { push @layers, $layer if $z > ($layer->print_z - $max_layer_height - epsilon) && $z <= $layer->print_z + epsilon; } else { push @layers, $layer if abs($layer->print_z - $z) < epsilon; } } } # reverse layers so that we draw the lowermost (i.e. current) on top $self->z($z); $self->layers([ reverse @layers ]); $self->Refresh; } sub Render { my ($self, $dc) = @_; # prevent calling SetCurrent() when window is not shown yet return unless $self->IsShownOnScreen; return unless my $context = $self->GetContext; $self->SetCurrent($context); $self->InitGL; glClearColor(1, 1, 1, 0); glClear(GL_COLOR_BUFFER_BIT); if (!$self->GetParent->enabled || !$self->layers) { glFlush(); $self->SwapBuffers; return; } glDisable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); # anti-alias if (0) { glEnable(GL_LINE_SMOOTH); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE); glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE); } my $tess; if (!(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) { # We can't use the GLU tesselator on MSW with older OpenGL versions # because of an upstream bug: # http://sourceforge.net/p/pogl/bugs/16/ $tess = gluNewTess(); gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_END, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT'); gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT'); } foreach my $layer (@{$self->layers}) { my $object = $layer->object; # only draw the slice for the current layer next unless abs($layer->print_z - $self->z) < epsilon; # draw slice contour glLineWidth(1); foreach my $copy (@{ $object->_shifted_copies }) { glPushMatrix(); glTranslatef(@$copy, 0); foreach my $slice (@{$layer->slices}) { glColor3f(0.95, 0.95, 0.95); if ($tess) { gluTessBeginPolygon($tess); foreach my $polygon (@$slice) { gluTessBeginContour($tess); gluTessVertex_p($tess, @$_, 0) for @$polygon; gluTessEndContour($tess); } gluTessEndPolygon($tess); } glColor3f(0.9, 0.9, 0.9); foreach my $polygon (@$slice) { foreach my $line (@{$polygon->lines}) { glBegin(GL_LINES); glVertex2f(@{$line->a}); glVertex2f(@{$line->b}); glEnd(); } } } glPopMatrix(); } } my $skirt_drawn = 0; my $brim_drawn = 0; foreach my $layer (@{$self->layers}) { my $object = $layer->object; my $print_z = $layer->print_z; # draw brim if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) { $self->color([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->brim}; $brim_drawn = 1; } if ($self->print->step_done(STEP_SKIRT) && ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id) && !$skirt_drawn) { $self->color([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->skirt}; $skirt_drawn = 1; } foreach my $layerm (@{$layer->regions}) { if ($object->step_done(STEP_PERIMETERS)) { $self->color([0.7, 0, 0]); $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters}; } if ($object->step_done(STEP_INFILL)) { $self->color([0, 0, 0.7]); $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills}; } } if ($object->step_done(STEP_SUPPORTMATERIAL)) { if ($layer->isa('Slic3r::Layer::Support')) { $self->color([0, 0, 0]); $self->_draw($object, $print_z, $_) for @{$layer->support_fills}; $self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills}; } } } gluDeleteTess($tess) if $tess; glFlush(); $self->SwapBuffers; } sub _draw { my ($self, $object, $print_z, $path) = @_; my @paths = $path->isa('Slic3r::ExtrusionLoop') ? @$path : ($path); $self->_draw_path($object, $print_z, $_) for @paths; } sub _draw_path { my ($self, $object, $print_z, $path) = @_; return if $print_z - $path->height > $self->z - epsilon; if (abs($print_z - $self->z) < epsilon) { glColor3f(@{$self->color}); } else { glColor3f(0.8, 0.8, 0.8); } glLineWidth(1); if (defined $object) { foreach my $copy (@{ $object->_shifted_copies }) { glPushMatrix(); glTranslatef(@$copy, 0); foreach my $line (@{$path->polyline->lines}) { glBegin(GL_LINES); glVertex2f(@{$line->a}); glVertex2f(@{$line->b}); glEnd(); } glPopMatrix(); } } else { foreach my $line (@{$path->polyline->lines}) { glBegin(GL_LINES); glVertex2f(@{$line->a}); glVertex2f(@{$line->b}); glEnd(); } } } sub InitGL { my $self = shift; return if $self->init; return unless $self->GetContext; $self->init(1); } sub GetContext { my ($self) = @_; if (Wx::wxVERSION >= 2.009) { return $self->{context} ||= Wx::GLContext->new($self); } else { return $self->SUPER::GetContext; } } sub SetCurrent { my ($self, $context) = @_; if (Wx::wxVERSION >= 2.009) { return $self->SUPER::SetCurrent($context); } else { return $self->SUPER::SetCurrent; } } sub Resize { my ($self) = @_; return unless $self->GetContext; return unless $self->bb; $self->_dirty(0); $self->SetCurrent($self->GetContext); my ($x, $y) = $self->GetSizeWH; glViewport(0, 0, $x, $y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); my $bb = $self->bb->clone; # center bounding box around origin before scaling it my $bb_center = $bb->center; $bb->translate(@{$bb_center->negative}); # scale bounding box according to zoom factor $bb->scale($self->_zoom); # reposition bounding box around original center $bb->translate(@{$bb_center}); # translate camera $bb->translate(@{$self->_camera_target}); # keep camera_bb within total bb # (i.e. prevent user from panning outside the bounding box) { my @translate = (0,0); if ($bb->x_min < $self->bb->x_min) { $translate[X] += $self->bb->x_min - $bb->x_min; } if ($bb->y_min < $self->bb->y_min) { $translate[Y] += $self->bb->y_min - $bb->y_min; } if ($bb->x_max > $self->bb->x_max) { $translate[X] -= $bb->x_max - $self->bb->x_max; } if ($bb->y_max > $self->bb->y_max) { $translate[Y] -= $bb->y_max - $self->bb->y_max; } $self->_camera_target->translate(@translate); $bb->translate(@translate); } # save camera $self->_camera_bb($bb); my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max); if (($x2 - $x1)/($y2 - $y1) > $x/$y) { # adjust Y my $new_y = $y * ($x2 - $x1) / $x; $y1 = ($y2 + $y1)/2 - $new_y/2; $y2 = $y1 + $new_y; } else { my $new_x = $x * ($y2 - $y1) / $y; $x1 = ($x2 + $x1)/2 - $new_x/2; $x2 = $x1 + $new_x; } glOrtho($x1, $x2, $y1, $y2, 0, 1); glMatrixMode(GL_MODELVIEW); } sub line { my ( $x1, $y1, $x2, $y2, # coordinates of the line $w, # width/thickness of the line in pixel $Cr, $Cg, $Cb, # RGB color components $Br, $Bg, $Bb, # color of background when alphablend=false # Br=alpha of color when alphablend=true $alphablend, # use alpha blend or not ) = @_; my $t; my $R; my $f = $w - int($w); my $A; if ($alphablend) { $A = $Br; } else { $A = 1; } # determine parameters t,R if ($w >= 0 && $w < 1) { $t = 0.05; $R = 0.48 + 0.32 * $f; if (!$alphablend) { $Cr += 0.88 * (1-$f); $Cg += 0.88 * (1-$f); $Cb += 0.88 * (1-$f); $Cr = 1.0 if ($Cr > 1.0); $Cg = 1.0 if ($Cg > 1.0); $Cb = 1.0 if ($Cb > 1.0); } else { $A *= $f; } } elsif ($w >= 1.0 && $w < 2.0) { $t = 0.05 + $f*0.33; $R = 0.768 + 0.312*$f; } elsif ($w >= 2.0 && $w < 3.0) { $t = 0.38 + $f*0.58; $R = 1.08; } elsif ($w >= 3.0 && $w < 4.0) { $t = 0.96 + $f*0.48; $R = 1.08; } elsif ($w >= 4.0 && $w < 5.0) { $t= 1.44 + $f*0.46; $R = 1.08; } elsif ($w >= 5.0 && $w < 6.0) { $t= 1.9 + $f*0.6; $R = 1.08; } elsif ($w >= 6.0) { my $ff = $w - 6.0; $t = 2.5 + $ff*0.50; $R = 1.08; } #printf( "w=%f, f=%f, C=%.4f\n", $w, $f, $C); # determine angle of the line to horizontal my $tx = 0; my $ty = 0; # core thinkness of a line my $Rx = 0; my $Ry = 0; # fading edge of a line my $cx = 0; my $cy = 0; # cap of a line my $ALW = 0.01; my $dx = $x2 - $x1; my $dy = $y2 - $y1; if (abs($dx) < $ALW) { # vertical $tx = $t; $ty = 0; $Rx = $R; $Ry = 0; if ($w > 0.0 && $w < 1.0) { $tx *= 8; } elsif ($w == 1.0) { $tx *= 10; } } elsif (abs($dy) < $ALW) { #horizontal $tx = 0; $ty = $t; $Rx = 0; $Ry = $R; if ($w > 0.0 && $w < 1.0) { $ty *= 8; } elsif ($w == 1.0) { $ty *= 10; } } else { if ($w < 3) { # approximate to make things even faster my $m = $dy/$dx; # and calculate tx,ty,Rx,Ry if ($m > -0.4142 && $m <= 0.4142) { # -22.5 < $angle <= 22.5, approximate to 0 (degree) $tx = $t * 0.1; $ty = $t; $Rx = $R * 0.6; $Ry = $R; } elsif ($m > 0.4142 && $m <= 2.4142) { # 22.5 < $angle <= 67.5, approximate to 45 (degree) $tx = $t * -0.7071; $ty = $t * 0.7071; $Rx = $R * -0.7071; $Ry = $R * 0.7071; } elsif ($m > 2.4142 || $m <= -2.4142) { # 67.5 < $angle <= 112.5, approximate to 90 (degree) $tx = $t; $ty = $t*0.1; $Rx = $R; $Ry = $R*0.6; } elsif ($m > -2.4142 && $m < -0.4142) { # 112.5 < angle < 157.5, approximate to 135 (degree) $tx = $t * 0.7071; $ty = $t * 0.7071; $Rx = $R * 0.7071; $Ry = $R * 0.7071; } else { # error in determining angle printf("error in determining angle: m=%.4f\n", $m); } } else { # calculate to exact $dx= $y1 - $y2; $dy= $x2 - $x1; my $L = sqrt($dx*$dx + $dy*$dy); $dx /= $L; $dy /= $L; $cx = -0.6*$dy; $cy=0.6*$dx; $tx = $t*$dx; $ty = $t*$dy; $Rx = $R*$dx; $Ry = $R*$dy; } } # draw the line by triangle strip glBegin(GL_TRIANGLE_STRIP); if (!$alphablend) { glColor3f($Br, $Bg, $Bb); } else { glColor4f($Cr, $Cg, $Cb, 0); } glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry); # fading edge glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry); if (!$alphablend) { glColor3f($Cr, $Cg, $Cb); } else { glColor4f($Cr, $Cg, $Cb, $A); } glVertex2f($x1 - $tx, $y1 - $ty); # core glVertex2f($x2 - $tx, $y2 - $ty); glVertex2f($x1 + $tx, $y1 + $ty); glVertex2f($x2 + $tx, $y2 + $ty); if ((abs($dx) < $ALW || abs($dy) < $ALW) && $w <= 1.0) { # printf("skipped one fading edge\n"); } else { if (!$alphablend) { glColor3f($Br, $Bg, $Bb); } else { glColor4f($Cr, $Cg, $Cb, 0); } glVertex2f($x1 + $tx+ $Rx, $y1 + $ty + $Ry); # fading edge glVertex2f($x2 + $tx+ $Rx, $y2 + $ty + $Ry); } glEnd(); # cap if ($w < 3) { # do not draw cap } else { # draw cap glBegin(GL_TRIANGLE_STRIP); if (!$alphablend) { glColor3f($Br, $Bg, $Bb); } else { glColor4f($Cr, $Cg, $Cb, 0); } glVertex2f($x1 - $Rx + $cx, $y1 - $Ry + $cy); glVertex2f($x1 + $Rx + $cx, $y1 + $Ry + $cy); glColor3f($Cr, $Cg, $Cb); glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry); glVertex2f($x1 + $tx + $Rx, $y1 + $ty + $Ry); glEnd(); glBegin(GL_TRIANGLE_STRIP); if (!$alphablend) { glColor3f($Br, $Bg, $Bb); } else { glColor4f($Cr, $Cg, $Cb, 0); } glVertex2f($x2 - $Rx - $cx, $y2 - $Ry - $cy); glVertex2f($x2 + $Rx - $cx, $y2 + $Ry - $cy); glColor3f($Cr, $Cg, $Cb); glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry); glVertex2f($x2 + $tx + $Rx, $y2 + $ty + $Ry); glEnd(); } } package Slic3r::GUI::Plater::2DToolpaths::Dialog; use Wx qw(:dialog :id :misc :sizer); use Wx::Event qw(EVT_CLOSE); use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, $print) = @_; my $self = $class->SUPER::new($parent, -1, "Toolpaths", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($self, $print), 1, wxEXPAND, 0); $self->SetSizer($sizer); $self->SetMinSize($self->GetSize); # needed to actually free memory EVT_CLOSE($self, sub { $self->EndModal(wxID_OK); $self->Destroy; }); return $self; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/3D.pm000066400000000000000000000050611254023100400172370ustar00rootroot00000000000000package Slic3r::GUI::Plater::3D; use strict; use warnings; use utf8; use List::Util qw(); use Slic3r::Geometry qw(); use Slic3r::Geometry::Clipper qw(); use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); use Wx::Event qw(); use base qw(Slic3r::GUI::3DScene Class::Accessor); sub new { my $class = shift; my ($parent, $objects, $model, $config) = @_; my $self = $class->SUPER::new($parent); $self->enable_picking(1); $self->enable_moving(1); $self->select_by('object'); $self->drag_by('instance'); $self->{objects} = $objects; $self->{model} = $model; $self->{config} = $config; $self->{on_select_object} = sub {}; $self->{on_instances_moved} = sub {}; $self->on_select(sub { my ($volume_idx) = @_; my $obj_idx = undef; if ($volume_idx != -1) { $obj_idx = $self->object_idx($volume_idx); } $self->{on_select_object}->($obj_idx) if $self->{on_select_object}; }); $self->on_move(sub { my @volume_idxs = @_; foreach my $volume_idx (@volume_idxs) { my $volume = $self->volumes->[$volume_idx]; my $obj_idx = $self->object_idx($volume_idx); my $instance_idx = $self->instance_idx($volume_idx); my $model_object = $self->{model}->get_object($obj_idx); $model_object ->instances->[$instance_idx] ->offset ->translate($volume->origin->x, $volume->origin->y); #)) $model_object->invalidate_bounding_box; } $self->{on_instances_moved}->() if $self->{on_instances_moved}; }); return $self; } sub set_on_select_object { my ($self, $cb) = @_; $self->{on_select_object} = $cb; } sub set_on_double_click { my ($self, $cb) = @_; $self->on_double_click($cb); } sub set_on_right_click { my ($self, $cb) = @_; $self->on_right_click($cb); } sub set_on_instances_moved { my ($self, $cb) = @_; $self->{on_instances_moved} = $cb; } sub update { my ($self) = @_; $self->reset_objects; $self->update_bed_size; foreach my $obj_idx (0..$#{$self->{model}->objects}) { my @volume_idxs = $self->load_object($self->{model}, $obj_idx); if ($self->{objects}[$obj_idx]->selected) { $self->select_volume($_) for @volume_idxs; } } } sub update_bed_size { my ($self) = @_; $self->set_bed_shape($self->{config}->bed_shape); } 1;Slic3r-1.2.9/lib/Slic3r/GUI/Plater/3DPreview.pm000066400000000000000000000107311254023100400206010ustar00rootroot00000000000000package Slic3r::GUI::Plater::3DPreview; use strict; use warnings; use utf8; use Slic3r::Print::State ':steps'; use Wx qw(:misc :sizer :slider :statictext wxWHITE); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN); use base qw(Wx::Panel Class::Accessor); __PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider)); sub new { my $class = shift; my ($parent, $print) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); # init GUI elements my $canvas = Slic3r::GUI::3DScene->new($self); $self->canvas($canvas); my $slider = Wx::Slider->new( $self, -1, 0, # default 0, # min # we set max to a bogus non-zero value because the MSW implementation of wxSlider # will skip drawing the slider if max <= min: 1, # max wxDefaultPosition, wxDefaultSize, wxVERTICAL | wxSL_INVERSE, ); $self->slider($slider); my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, [40,-1], wxALIGN_CENTRE_HORIZONTAL); $z_label->SetFont($Slic3r::GUI::small_font); my $vsizer = Wx::BoxSizer->new(wxVERTICAL); $vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3); $vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3); my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0); $sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); EVT_SLIDER($self, $slider, sub { $self->set_z($self->{layers_z}[$slider->GetValue]) if $self->enabled; }); EVT_KEY_DOWN($canvas, sub { my ($s, $event) = @_; my $key = $event->GetKeyCode; if ($key == 85 || $key == 315) { $slider->SetValue($slider->GetValue + 1); $self->set_z($self->{layers_z}[$slider->GetValue]); } elsif ($key == 68 || $key == 317) { $slider->SetValue($slider->GetValue - 1); $self->set_z($self->{layers_z}[$slider->GetValue]); } }); $self->SetSizer($sizer); $self->SetMinSize($self->GetSize); $sizer->SetSizeHints($self); # init canvas $self->print($print); $self->reload_print; return $self; } sub reload_print { my ($self) = @_; $self->canvas->reset_objects; $self->_loaded(0); $self->load_print; } sub load_print { my ($self) = @_; return if $self->_loaded; # we require that there's at least one object and the posSlice step # is performed on all of them (this ensures that _shifted_copies was # populated and we know the number of layers) if (!$self->print->object_step_done(STEP_SLICE)) { $self->enabled(0); $self->slider->Hide; $self->canvas->Refresh; # clears canvas return; } { my %z = (); # z => 1 foreach my $object (@{$self->{print}->objects}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { $z{$layer->print_z} = 1; } } $self->enabled(1); $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; $self->slider->SetRange(0, scalar(@{$self->{layers_z}})-1); if ((my $z_idx = $self->slider->GetValue) <= $#{$self->{layers_z}} && $self->slider->GetValue != 0) { $self->set_z($self->{layers_z}[$z_idx]); } else { $self->slider->SetValue(scalar(@{$self->{layers_z}})-1); $self->set_z($self->{layers_z}[-1]) if @{$self->{layers_z}}; } $self->slider->Show; $self->Layout; } if ($self->IsShown) { # load skirt and brim $self->canvas->load_print_toolpaths($self->print); foreach my $object (@{$self->print->objects}) { $self->canvas->load_print_object_toolpaths($object); #my @volume_ids = $self->canvas->load_object($object->model_object); #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids; } $self->canvas->zoom_to_volumes; $self->_loaded(1); } } sub set_z { my ($self, $z) = @_; return if !$self->enabled; $self->{z_label}->SetLabel(sprintf '%.2f', $z); $self->canvas->set_toolpaths_range(0, $z); $self->canvas->Refresh if $self->IsShown; } sub set_bed_shape { my ($self, $bed_shape) = @_; $self->canvas->set_bed_shape($bed_shape); } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm000066400000000000000000000136541254023100400220020ustar00rootroot00000000000000package Slic3r::GUI::Plater::ObjectCutDialog; use strict; use warnings; use utf8; use Slic3r::Geometry qw(PI X); 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 $optgroup; $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new( parent => $self, title => 'Cut', on_change => sub { my ($opt_id) = @_; $self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id); $self->_update; }, label_width => 120, ); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'z', type => 'slider', label => 'Z', default => $self->{cut_options}{z}, min => 0, max => $self->{model_object}->bounding_box->size->z, full_width => 1, )); { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Keep', ); $line->append_option(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'keep_upper', type => 'bool', label => 'Upper part', default => $self->{cut_options}{keep_upper}, )); $line->append_option(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'keep_lower', type => 'bool', label => 'Lower part', default => $self->{cut_options}{keep_lower}, )); $optgroup->append_line($line); } $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'rotate_lower', label => 'Rotate lower part upwards', type => 'bool', 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}, )); { 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); $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new( sizer => $cut_button_sizer, )); } # 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::3DScene->new($self); $canvas->enable_cutting(1); $canvas->load_object($self->{model_object}, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->SetMinSize($canvas->GetSize); $canvas->zoom_to_volumes; } $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 }); $self->_update; return $self; } sub _update { my ($self) = @_; my $optgroup = $self->{optgroup}; # update canvas if ($self->{canvas}) { $self->{canvas}->SetCuttingPlane($self->{cut_options}{z}); $self->{canvas}->Render; } # update controls my $z = $self->{cut_options}{z}; $optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1); $optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1); $optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower}); # update cut button if (($self->{cut_options}{keep_upper} && $have_upper) || ($self->{cut_options}{keep_lower} && $have_lower)) { $self->{btn_cut}->Enable; } else { $self->{btn_cut}->Disable; } } sub perform_cut { my ($self) = @_; # scale Z down to original size since we're using the transformed mesh for 3D preview # and cut dialog but ModelObject::cut() needs Z without any instance transformation my $z = $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor; my ($new_model) = $self->{model_object}->cut($z); my ($upper_object, $lower_object) = @{$new_model->objects}; $self->{new_model} = $new_model; $self->{new_model_objects} = []; if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) { $upper_object->center_around_origin; # align to Z = 0 push @{$self->{new_model_objects}}, $upper_object; } if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) { push @{$self->{new_model_objects}}, $lower_object; if ($self->{cut_options}{rotate_lower}) { $lower_object->rotate(PI, X); $lower_object->center_around_origin; # align to Z = 0 } } $self->Close; } sub NewModelObjects { my ($self) = @_; return @{ $self->{new_model_objects} }; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm000066400000000000000000000265701254023100400222010ustar00rootroot00000000000000package Slic3r::GUI::Plater::ObjectPartsPanel; use strict; use warnings; use utf8; use File::Basename qw(basename); use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED); use base 'Wx::Panel'; use constant ICON_OBJECT => 0; use constant ICON_SOLIDMESH => 1; use constant ICON_MODIFIERMESH => 2; sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); my $object = $self->{model_object} = $params{model_object}; # create TreeCtrl my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100], wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT | wxTR_SINGLE | wxTR_NO_BUTTONS); { $self->{tree_icons} = Wx::ImageList->new(16, 16, 1); $tree->AssignImageList($self->{tree_icons}); $self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/brick.png", wxBITMAP_TYPE_PNG)); # ICON_OBJECT $self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH $self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/plugin.png", wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH my $rootId = $tree->AddRoot("Object", ICON_OBJECT); $tree->SetPlData($rootId, { type => 'object' }); } # buttons $self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); if ($Slic3r::GUI::have_button_icons) { $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG)); $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG)); $self->{btn_delete}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_delete.png", wxBITMAP_TYPE_PNG)); } # buttons sizer my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $buttons_sizer->Add($self->{btn_load_part}, 0); $buttons_sizer->Add($self->{btn_load_modifier}, 0); $buttons_sizer->Add($self->{btn_delete}, 0); $self->{btn_load_part}->SetFont($Slic3r::GUI::small_font); $self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font); $self->{btn_delete}->SetFont($Slic3r::GUI::small_font); # part settings panel $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; }); my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); # left pane with tree my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); $left_sizer->Add($tree, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($settings_sizer, 1, wxEXPAND | wxALL, 0); # right pane with preview canvas my $canvas; if ($Slic3r::GUI::have_OpenGL) { $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self); $canvas->enable_picking(1); $canvas->select_by('volume'); $canvas->on_select(sub { my ($volume_idx) = @_; # convert scene volume to model object volume $self->reload_tree($canvas->volume_idx($volume_idx)); }); $canvas->load_object($self->{model_object}, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->zoom_to_volumes; } $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); $self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxALL, 0); $self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas; $self->SetSizer($self->{sizer}); $self->{sizer}->SetSizeHints($self); # attach events EVT_TREE_ITEM_COLLAPSING($self, $tree, sub { my ($self, $event) = @_; $event->Veto; }); EVT_TREE_SEL_CHANGED($self, $tree, sub { my ($self, $event) = @_; return if $self->{disable_tree_sel_changed_event}; $self->selection_changed; }); EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) }); EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) }); EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete); $self->reload_tree; return $self; } sub reload_tree { my ($self, $selected_volume_idx) = @_; $selected_volume_idx //= -1; my $object = $self->{model_object}; my $tree = $self->{tree}; my $rootId = $tree->GetRootItem; # despite wxWidgets states that DeleteChildren "will not generate any events unlike Delete() method", # the MSW implementation of DeleteChildren actually calls Delete() for each item, so # EVT_TREE_SEL_CHANGED is being called, with bad effects (the event handler is called; this # subroutine is never continued; an invisible EndModal is called on the dialog causing Plater # to continue its logic and rescheduling the background process etc. GH #2774) $self->{disable_tree_sel_changed_event} = 1; $tree->DeleteChildren($rootId); $self->{disable_tree_sel_changed_event} = 0; my $selectedId = $rootId; foreach my $volume_id (0..$#{$object->volumes}) { my $volume = $object->volumes->[$volume_id]; my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon); if ($volume_id == $selected_volume_idx) { $selectedId = $itemId; } $tree->SetPlData($itemId, { type => 'volume', volume_id => $volume_id, }); } $tree->ExpandAll; Slic3r::GUI->CallAfter(sub { $self->{tree}->SelectItem($selectedId); # SelectItem() should trigger EVT_TREE_SEL_CHANGED as per wxWidgets docs, # but in fact it doesn't if the given item is already selected (this happens # on first load) $self->selection_changed; }); } sub get_selection { my ($self) = @_; my $nodeId = $self->{tree}->GetSelection; if ($nodeId->IsOk) { return $self->{tree}->GetPlData($nodeId); } return undef; } sub selection_changed { my ($self) = @_; # deselect all meshes if ($self->{canvas}) { $_->selected(0) for @{$self->{canvas}->volumes}; } # disable things as if nothing is selected $self->{btn_delete}->Disable; $self->{settings_panel}->disable; $self->{settings_panel}->set_config(undef); if (my $itemData = $self->get_selection) { my ($config, @opt_keys); if ($itemData->{type} eq 'volume') { # select volume in 3D preview if ($self->{canvas}) { $self->{canvas}->volumes->[ $itemData->{volume_id} ]{selected} = 1; } $self->{btn_delete}->Enable; # attach volume config to settings panel my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; $config = $volume->config; $self->{staticbox}->SetLabel('Part Settings'); # get default values @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; } elsif ($itemData->{type} eq 'object') { # select nothing in 3D preview # attach object config to settings panel $self->{staticbox}->SetLabel('Object Settings'); @opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new); $config = $self->{model_object}->config; } # get default values my $default_config = Slic3r::Config->new_from_defaults(@opt_keys); # append default extruder push @opt_keys, 'extruder'; $default_config->set('extruder', 0); $config->set_ifndef('extruder', 0); $self->{settings_panel}->set_default_config($default_config); $self->{settings_panel}->set_config($config); $self->{settings_panel}->set_opt_keys(\@opt_keys); $self->{settings_panel}->set_fixed_options([qw(extruder)]); $self->{settings_panel}->enable; } $self->{canvas}->Render if $self->{canvas}; } sub on_btn_load { my ($self, $is_modifier) = @_; my @input_files = wxTheApp->open_model($self); foreach my $input_file (@input_files) { my $model = eval { Slic3r::Model->read_from_file($input_file) }; if ($@) { Slic3r::GUI::show_error($self, $@); next; } foreach my $object (@{$model->objects}) { foreach my $volume (@{$object->volumes}) { my $new_volume = $self->{model_object}->add_volume($volume); $new_volume->set_modifier($is_modifier); $new_volume->set_name(basename($input_file)); # apply the same translation we applied to the object $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); # set a default extruder value, since user can't add it manually $new_volume->config->set_ifndef('extruder', 0); $self->{parts_changed} = 1; } } } $self->_parts_changed; } sub on_btn_delete { my ($self) = @_; my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}]; # if user is deleting the last solid part, throw error if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) { Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object."); return; } $self->{model_object}->delete_volume($itemData->{volume_id}); $self->{parts_changed} = 1; } $self->_parts_changed; } sub _parts_changed { my ($self) = @_; $self->reload_tree; if ($self->{canvas}) { $self->{canvas}->reset_objects; $self->{canvas}->load_object($self->{model_object}); $self->{canvas}->zoom_to_volumes; $self->{canvas}->Render; } } sub CanClose { my $self = shift; return 1; # skip validation for now # validate options before allowing user to dismiss the dialog # the validate method only works on full configs so we have # to merge our settings with the default ones my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config); eval { $config->validate; }; return 0 if Slic3r::GUI::catch_error($self); return 1; } sub PartsChanged { my ($self) = @_; return $self->{parts_changed}; } sub PartSettingsChanged { my ($self) = @_; return $self->{part_settings_changed}; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm000066400000000000000000000127321254023100400230430ustar00rootroot00000000000000package Slic3r::GUI::Plater::ObjectSettingsDialog; use strict; use warnings; use utf8; use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL); use Wx::Event qw(EVT_BUTTON); use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $self->{$_} = $params{$_} for keys %params; $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); $self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers"); my $buttons = $self->CreateStdDialogButtonSizer(wxOK); EVT_BUTTON($self, wxID_OK, sub { # validate user input return if !$self->{parts}->CanClose; return if !$self->{layers}->CanClose; # notify tabs $self->{layers}->Closing; $self->EndModal(wxID_OK); $self->Destroy; }); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{tabpanel}, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); $self->SetSizer($sizer); $self->SetMinSize($self->GetSize); return $self; } sub PartsChanged { my ($self) = @_; return $self->{parts}->PartsChanged; } sub PartSettingsChanged { my ($self) = @_; return $self->{parts}->PartSettingsChanged || $self->{layers}->LayersChanged; } package Slic3r::GUI::Plater::ObjectDialog::BaseTab; use base 'Wx::Panel'; sub model_object { my ($self) = @_; return $self->GetParent->GetParent->{model_object}; } package Slic3r::GUI::Plater::ObjectDialog::LayersTab; use Wx qw(:dialog :id :misc :sizer :systemsettings); use Wx::Grid; use Wx::Event qw(EVT_GRID_CELL_CHANGED); use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab'; sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); my $sizer = Wx::BoxSizer->new(wxVERTICAL); { my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object. Set layer height to zero to skip portions of the input file.", wxDefaultPosition, [-1, 40]); $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); $sizer->Add($label, 0, wxEXPAND | wxALL, 10); } my $grid = $self->{grid} = Wx::Grid->new($self, -1, wxDefaultPosition, wxDefaultSize); $sizer->Add($grid, 1, wxEXPAND | wxALL, 10); $grid->CreateGrid(0, 3); $grid->DisableDragRowSize; $grid->HideRowLabels if &Wx::wxVERSION_STRING !~ / 2\.8\./; $grid->SetColLabelValue(0, "Min Z (mm)"); $grid->SetColLabelValue(1, "Max Z (mm)"); $grid->SetColLabelValue(2, "Layer height (mm)"); $grid->SetColSize($_, 135) for 0..2; $grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE); # load data foreach my $range (@{ $self->model_object->layer_height_ranges }) { $grid->AppendRows(1); my $i = $grid->GetNumberRows-1; $grid->SetCellValue($i, $_, $range->[$_]) for 0..2; } $grid->AppendRows(1); # append one empty row EVT_GRID_CELL_CHANGED($grid, sub { my ($grid, $event) = @_; # remove any non-numeric character my $value = $grid->GetCellValue($event->GetRow, $event->GetCol); $value =~ s/,/./g; $value =~ s/[^0-9.]//g; $grid->SetCellValue($event->GetRow, $event->GetCol, $value); # if there's no empty row, let's append one for my $i (0 .. $grid->GetNumberRows-1) { if (!grep $grid->GetCellValue($i, $_), 0..2) { return; } } $grid->AppendRows(1); $self->{layers_changed} = 1; }); $self->SetSizer($sizer); $sizer->SetSizeHints($self); return $self; } sub CanClose { my $self = shift; # validate ranges before allowing user to dismiss the dialog foreach my $range ($self->_get_ranges) { my ($min, $max, $height) = @$range; if ($max <= $min) { Slic3r::GUI::show_error($self, "Invalid Z range $min-$max."); return 0; } if ($min < 0 || $max < 0) { Slic3r::GUI::show_error($self, "Invalid Z range $min-$max."); return 0; } if ($height < 0) { Slic3r::GUI::show_error($self, "Invalid layer height $height."); return 0; } # TODO: check for overlapping ranges } return 1; } sub Closing { my $self = shift; # save ranges into the plater object $self->model_object->set_layer_height_ranges([ $self->_get_ranges ]); } sub _get_ranges { my $self = shift; my @ranges = (); for my $i (0 .. $self->{grid}->GetNumberRows-1) { my ($min, $max, $height) = map $self->{grid}->GetCellValue($i, $_), 0..2; next if $min eq '' || $max eq '' || $height eq ''; push @ranges, [ $min, $max, $height ]; } return sort { $a->[0] <=> $b->[0] } @ranges; } sub LayersChanged { my ($self) = @_; return $self->{layers_changed}; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm000066400000000000000000000121661254023100400232550ustar00rootroot00000000000000package Slic3r::GUI::Plater::OverrideSettingsPanel; use strict; use warnings; use utf8; use List::Util qw(first); use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU); use base 'Wx::ScrolledWindow'; use constant ICON_MATERIAL => 0; use constant ICON_SOLIDMESH => 1; use constant ICON_MODIFIERMESH => 2; sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{default_config} = Slic3r::Config->new; $self->{config} = Slic3r::Config->new; $self->{on_change} = $params{on_change}; $self->{fixed_options} = {}; $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0); # option selector { # create the button my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; foreach my $opt_key (@{$self->{options}}) { my $id = &Wx::NewId(); $menu->Append($id, $self->{option_labels}{$opt_key}); EVT_MENU($menu, $id, sub { $self->{config}->set($opt_key, $self->{default_config}->get($opt_key)); $self->update_optgroup; $self->{on_change}->() if $self->{on_change}; }); } $self->PopupMenu($menu, $btn->GetPosition); $menu->Destroy; }); my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $h_sizer->Add($btn, 0, wxALL, 0); $self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxBOTTOM, 10); } $self->SetSizer($self->{sizer}); $self->SetScrollbars(0, 1, 0, 1); $self->set_opt_keys($params{opt_keys}) if $params{opt_keys}; $self->update_optgroup; return $self; } sub set_default_config { my ($self, $config) = @_; $self->{default_config} = $config; } sub set_config { my ($self, $config) = @_; $self->{config} = $config; $self->update_optgroup; } sub set_opt_keys { my ($self, $opt_keys) = @_; # sort options by category+label $self->{option_labels} = { map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } @$opt_keys }; $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; } sub set_fixed_options { my ($self, $opt_keys) = @_; $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; $self->update_optgroup; } sub update_optgroup { my $self = shift; $self->{options_sizer}->Clear(1); return if !defined $self->{config}; my %categories = (); foreach my $opt_key (@{$self->{config}->get_keys}) { my $category = $Slic3r::Config::Options->{$opt_key}{category}; $categories{$category} ||= []; push @{$categories{$category}}, $opt_key; } foreach my $category (sort keys %categories) { my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( parent => $self, title => $category, config => $self->{config}, full_labels => 1, label_font => $Slic3r::GUI::small_font, sidetext_font => $Slic3r::GUI::small_font, label_width => 120, on_change => sub { $self->{on_change}->() if $self->{on_change} }, extra_column => sub { my ($line) = @_; my $opt_key = $line->get_options->[0]->opt_id; # we assume that we have one option per line # disallow deleting fixed options return undef if $self->{fixed_options}{$opt_key}; my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_BUTTON($self, $btn, sub { $self->{config}->erase($opt_key); $self->{on_change}->() if $self->{on_change}; wxTheApp->CallAfter(sub { $self->update_optgroup }); }); return $btn; }, ); foreach my $opt_key (sort @{$categories{$category}}) { $optgroup->append_single_option_line($opt_key); } $self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0); } $self->Layout; } # work around a wxMAC bug causing controls not being disabled when calling Disable() on a Window sub enable { my ($self) = @_; $self->{btn_add}->Enable; $self->Enable; } sub disable { my ($self) = @_; $self->{btn_add}->Disable; $self->Disable; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Preferences.pm000066400000000000000000000071621254023100400200070ustar00rootroot00000000000000package Slic3r::GUI::Preferences; use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); use base 'Wx::Dialog'; sub new { my ($class, $parent) = @_; my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize); $self->{values} = {}; my $optgroup; $optgroup = Slic3r::GUI::OptionsGroup->new( parent => $self, title => 'General', on_change => sub { my ($opt_id) = @_; $self->{values}{$opt_id} = $optgroup->get_value($opt_id); }, label_width => 200, ); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'mode', type => 'select', label => 'Mode', tooltip => 'Choose between a simpler, basic mode and an expert mode with more options and more complicated interface.', labels => ['Simple','Expert'], values => ['simple','expert'], default => $Slic3r::GUI::Settings->{_}{mode}, width => 100, )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'version_check', type => 'bool', label => 'Check for updates', tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', default => $Slic3r::GUI::Settings->{_}{version_check} // 1, readonly => !wxTheApp->have_version_check, )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'remember_output_path', type => 'bool', label => 'Remember output directory', tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.', default => $Slic3r::GUI::Settings->{_}{remember_output_path}, )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'autocenter', type => 'bool', label => 'Auto-center parts', tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.', default => $Slic3r::GUI::Settings->{_}{autocenter}, )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'background_processing', type => 'bool', label => 'Background processing', tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.', default => $Slic3r::GUI::Settings->{_}{background_processing}, readonly => !$Slic3r::have_threads, )); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL); EVT_BUTTON($self, wxID_OK, sub { $self->_accept }); $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); $self->SetSizer($sizer); $sizer->SetSizeHints($self); return $self; } sub _accept { my $self = shift; if ($self->{values}{mode}) { Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective."); } $Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}}; wxTheApp->save_settings; $self->EndModal(wxID_OK); $self->Close; # needed on Linux } 1; Slic3r-1.2.9/lib/Slic3r/GUI/ProgressStatusBar.pm000066400000000000000000000060271254023100400212020ustar00rootroot00000000000000package Slic3r::GUI::ProgressStatusBar; use strict; use warnings; use Wx qw(:gauge :misc); use base 'Wx::StatusBar'; sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{busy} = 0; $self->{timer} = Wx::Timer->new($self); $self->{prog} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize); $self->{prog}->Hide; $self->{cancelbutton} = Wx::Button->new($self, -1, "Cancel", wxDefaultPosition, wxDefaultSize); $self->{cancelbutton}->Hide; $self->SetFieldsCount(3); $self->SetStatusWidths(-1, 150, 155); Wx::Event::EVT_TIMER($self, \&OnTimer, $self->{timer}); Wx::Event::EVT_SIZE($self, \&OnSize); Wx::Event::EVT_BUTTON($self, $self->{cancelbutton}, sub { $self->{cancel_cb}->(); $self->{cancelbutton}->Hide; }); return $self; } sub DESTROY { my $self = shift; $self->{timer}->Stop if $self->{timer} && $self->{timer}->IsRunning; } sub OnSize { my ($self, $event) = @_; my %fields = ( # 0 is reserved for status text 1 => $self->{cancelbutton}, 2 => $self->{prog}, ); foreach (keys %fields) { my $rect = $self->GetFieldRect($_); my $offset = &Wx::wxGTK ? 1 : 0; # add a cosmetic 1 pixel offset on wxGTK my $pos = [$rect->GetX + $offset, $rect->GetY + $offset]; $fields{$_}->Move($pos); $fields{$_}->SetSize($rect->GetWidth - $offset, $rect->GetHeight); } $event->Skip; } sub OnTimer { my ($self, $event) = @_; if ($self->{prog}->IsShown) { $self->{timer}->Stop; } $self->{prog}->Pulse if $self->{_busy}; } sub SetCancelCallback { my $self = shift; my ($cb) = @_; $self->{cancel_cb} = $cb; $cb ? $self->{cancelbutton}->Show : $self->{cancelbutton}->Hide; } sub Run { my $self = shift; my $rate = shift || 100; if (!$self->{timer}->IsRunning) { $self->{timer}->Start($rate); } } sub GetProgress { my $self = shift; return $self->{prog}->GetValue; } sub SetProgress { my $self = shift; my ($val) = @_; if (!$self->{prog}->IsShown) { $self->ShowProgress(1); } if ($val == $self->{prog}->GetRange) { $self->{prog}->SetValue(0); $self->ShowProgress(0); } else { $self->{prog}->SetValue($val); } } sub SetRange { my $self = shift; my ($val) = @_; if ($val != $self->{prog}->GetRange) { $self->{prog}->SetRange($val); } } sub ShowProgress { my $self = shift; my ($show) = @_; $self->{prog}->Show($show); $self->{prog}->Pulse; } sub StartBusy { my $self = shift; my $rate = shift || 100; $self->{_busy} = 1; $self->ShowProgress(1); if (!$self->{timer}->IsRunning) { $self->{timer}->Start($rate); } } sub StopBusy { my $self = shift; $self->{timer}->Stop; $self->ShowProgress(0); $self->{prog}->SetValue(0); $self->{_busy} = 0; } sub IsBusy { my $self = shift; return $self->{_busy}; } 1; Slic3r-1.2.9/lib/Slic3r/GUI/SimpleTab.pm000066400000000000000000000242751254023100400174320ustar00rootroot00000000000000package Slic3r::GUI::SimpleTab; use strict; use warnings; use utf8; use File::Basename qw(basename); use List::Util qw(first); use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :window :systemsettings); use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN); use base 'Wx::ScrolledWindow'; sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); $self->SetScrollbars(1, 1, 1, 1); $self->{config} = Slic3r::Config->new; $self->{optgroups} = []; $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); $self->SetSizer($self->{vsizer}); $self->build; $self->_update; { my $label = Wx::StaticText->new($self, -1, "Want more options? Switch to the Expert Mode.", wxDefaultPosition, wxDefaultSize); $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); $self->{vsizer}->Add($label, 0, wxEXPAND | wxALL, 10); } return $self; } sub init_config_options { my ($self, @opt_keys) = @_; $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); } sub new_optgroup { my ($self, $title, %params) = @_; my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( parent => $self, title => $title, config => $self->{config}, label_width => $params{label_width} // 200, on_change => sub { $self->_on_value_change(@_) }, ); push @{$self->{optgroups}}, $optgroup; $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); return $optgroup; } sub load_config_file { my $self = shift; my ($file) = @_; my $config = Slic3r::Config->load($file); $self->load_config($config); } sub load_config { my $self = shift; my ($config) = @_; foreach my $opt_key (@{$self->{config}->get_keys}) { next unless $config->has($opt_key); $self->{config}->set($opt_key, $config->get($opt_key)); } $_->reload_config for @{$self->{optgroups}}; $self->_update; } sub load_presets {} sub is_dirty { 0 } sub config { $_[0]->{config}->clone } sub _update {} sub on_value_change { my ($self, $cb) = @_; $self->{on_value_change} = $cb; } sub on_presets_changed {} # propagate event to the parent sub _on_value_change { my $self = shift; $self->{on_value_change}->(@_) if $self->{on_value_change}; $self->_update; } sub get_field { my ($self, $opt_key, $opt_index) = @_; foreach my $optgroup (@{ $self->{optgroups} }) { my $field = $optgroup->get_fieldc($opt_key, $opt_index); return $field if defined $field; } return undef; } package Slic3r::GUI::SimpleTab::Print; use base 'Slic3r::GUI::SimpleTab'; use Wx qw(:sizer); sub name { 'print' } sub title { 'Print Settings' } sub build { my $self = shift; $self->init_config_options(qw( layer_height perimeters top_solid_layers bottom_solid_layers fill_density fill_pattern external_fill_pattern support_material support_material_spacing raft_layers support_material_contact_distance dont_support_bridges perimeter_speed infill_speed travel_speed brim_width xy_size_compensation )); { my $optgroup = $self->new_optgroup('General'); $optgroup->append_single_option_line('layer_height'); $optgroup->append_single_option_line('perimeters'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Solid layers', ); $line->append_option($optgroup->get_option('top_solid_layers')); $line->append_option($optgroup->get_option('bottom_solid_layers')); $optgroup->append_line($line); } { my $optgroup = $self->new_optgroup('Infill'); $optgroup->append_single_option_line('fill_density'); $optgroup->append_single_option_line('fill_pattern'); $optgroup->append_single_option_line('external_fill_pattern'); } { my $optgroup = $self->new_optgroup('Support material'); $optgroup->append_single_option_line('support_material'); $optgroup->append_single_option_line('support_material_spacing'); $optgroup->append_single_option_line('support_material_contact_distance'); $optgroup->append_single_option_line('dont_support_bridges'); $optgroup->append_single_option_line('raft_layers'); } { my $optgroup = $self->new_optgroup('Speed'); $optgroup->append_single_option_line('perimeter_speed'); $optgroup->append_single_option_line('infill_speed'); $optgroup->append_single_option_line('travel_speed'); } { my $optgroup = $self->new_optgroup('Brim'); $optgroup->append_single_option_line('brim_width'); } { my $optgroup = $self->new_optgroup('Other'); $optgroup->append_single_option_line('xy_size_compensation'); } } sub _update { my ($self) = @_; my $config = $self->{config}; my $have_perimeters = $config->perimeters > 0; $self->get_field($_)->toggle($have_perimeters) for qw(perimeter_speed); my $have_infill = $config->fill_density > 0; my $have_solid_infill = $config->top_solid_layers > 0 || $config->bottom_solid_layers > 0; $self->get_field($_)->toggle($have_infill) for qw(fill_pattern); $self->get_field($_)->toggle($have_solid_infill) for qw(external_fill_pattern); $self->get_field($_)->toggle($have_infill || $have_solid_infill) for qw(infill_speed); my $have_support_material = $config->support_material || $config->raft_layers > 0; $self->get_field($_)->toggle($have_support_material) for qw(support_material_spacing dont_support_bridges support_material_contact_distance); } package Slic3r::GUI::SimpleTab::Filament; use base 'Slic3r::GUI::SimpleTab'; sub name { 'filament' } sub title { 'Filament Settings' } sub build { my $self = shift; $self->init_config_options(qw( filament_diameter extrusion_multiplier temperature first_layer_temperature bed_temperature first_layer_bed_temperature )); { my $optgroup = $self->new_optgroup('Filament'); $optgroup->append_single_option_line('filament_diameter', 0); $optgroup->append_single_option_line('extrusion_multiplier', 0); } { my $optgroup = $self->new_optgroup('Temperature (°C)'); { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Extruder', ); $line->append_option($optgroup->get_option('first_layer_temperature', 0)); $line->append_option($optgroup->get_option('temperature', 0)); $optgroup->append_line($line); } { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Bed', ); $line->append_option($optgroup->get_option('first_layer_bed_temperature')); $line->append_option($optgroup->get_option('bed_temperature')); $optgroup->append_line($line); } } } package Slic3r::GUI::SimpleTab::Printer; use base 'Slic3r::GUI::SimpleTab'; use Wx qw(:sizer :button :bitmap :misc :id); use Wx::Event qw(EVT_BUTTON); sub name { 'printer' } sub title { 'Printer Settings' } sub build { my $self = shift; $self->init_config_options(qw( bed_shape z_offset gcode_flavor nozzle_diameter retract_length retract_lift wipe start_gcode end_gcode )); { my $bed_shape_widget = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); if ($Slic3r::GUI::have_button_icons) { $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG)); } my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($btn); EVT_BUTTON($self, $btn, sub { my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); if ($dlg->ShowModal == wxID_OK) { my $value = $dlg->GetValue; $self->{config}->set('bed_shape', $value); $self->_on_value_change('bed_shape', $value); } }); return $sizer; }; my $optgroup = $self->new_optgroup('Size and coordinates'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Bed shape', widget => $bed_shape_widget, ); $optgroup->append_line($line); $optgroup->append_single_option_line('z_offset'); } { my $optgroup = $self->new_optgroup('Firmware'); $optgroup->append_single_option_line('gcode_flavor'); } { my $optgroup = $self->new_optgroup('Extruder'); $optgroup->append_single_option_line('nozzle_diameter', 0); } { my $optgroup = $self->new_optgroup('Retraction'); $optgroup->append_single_option_line('retract_length', 0); $optgroup->append_single_option_line('retract_lift', 0); $optgroup->append_single_option_line('wipe', 0); } { my $optgroup = $self->new_optgroup('Start G-code', label_width => 0, ); my $option = $optgroup->get_option('start_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } { my $optgroup = $self->new_optgroup('End G-code', label_width => 0, ); my $option = $optgroup->get_option('end_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } } sub _update { my ($self) = @_; my $config = $self->{config}; my $have_retraction = $config->retract_length->[0] > 0; $self->get_field($_, 0)->toggle($have_retraction) for qw(retract_lift wipe); } 1; Slic3r-1.2.9/lib/Slic3r/GUI/Tab.pm000066400000000000000000001602131254023100400162510ustar00rootroot00000000000000package Slic3r::GUI::Tab; use strict; use warnings; use utf8; use File::Basename qw(basename); use List::Util qw(first); use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window :button wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED); use base qw(Wx::Panel Class::Accessor); __PACKAGE__->mk_accessors(qw(current_preset)); sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); # horizontal sizer $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); $self->{sizer}->SetSizeHints($self); $self->SetSizer($self->{sizer}); # left vertical sizer my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); $self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); my $left_col_width = 150; # preset chooser { # choice menu $self->{presets_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [$left_col_width, -1], []); $self->{presets_choice}->SetFont($Slic3r::GUI::small_font); # buttons $self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/disk.png", wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title)); $self->{btn_delete_preset}->SetToolTipString("Delete this preset"); $self->{btn_delete_preset}->Disable; ### These cause GTK warnings: ###my $box = Wx::StaticBox->new($self, -1, "Presets:", wxDefaultPosition, [$left_col_width, 50]); ###my $hsizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $left_sizer->Add($hsizer, 0, wxEXPAND | wxBOTTOM, 5); $hsizer->Add($self->{presets_choice}, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, 3); $hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL); $hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL); } # tree $self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [$left_col_width, -1], wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); $left_sizer->Add($self->{treectrl}, 1, wxEXPAND); $self->{icons} = Wx::ImageList->new(16, 16, 1); $self->{treectrl}->AssignImageList($self->{icons}); $self->{iconcount} = -1; $self->{treectrl}->AddRoot("root"); $self->{pages} = []; $self->{treectrl}->SetIndent(0); $self->{disable_tree_sel_changed_event} = 0; EVT_TREE_SEL_CHANGED($parent, $self->{treectrl}, sub { return if $self->{disable_tree_sel_changed_event}; my $page = first { $_->{title} eq $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection) } @{$self->{pages}} or return; $_->Hide for @{$self->{pages}}; $page->Show; $self->{sizer}->Layout; $self->Refresh; }); EVT_KEY_DOWN($self->{treectrl}, sub { my ($treectrl, $event) = @_; if ($event->GetKeyCode == WXK_TAB) { $treectrl->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); } else { $event->Skip; } }); EVT_CHOICE($parent, $self->{presets_choice}, sub { $self->on_select_preset; $self->_on_presets_changed; }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); EVT_BUTTON($self, $self->{btn_delete_preset}, sub { my $i = $self->current_preset; return if $i == 0; # this shouldn't happen but let's trap it anyway my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; return unless $res == wxID_YES; if (-e $self->{presets}[$i]->file) { unlink $self->{presets}[$i]->file; } splice @{$self->{presets}}, $i, 1; $self->{presets_choice}->Delete($i); $self->current_preset(undef); $self->select_preset(0); $self->_on_presets_changed; }); $self->{config} = Slic3r::Config->new; $self->build; $self->update_tree; $self->_update; if ($self->hidden_options) { $self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options)); } return $self; } sub get_current_preset { my $self = shift; return $self->get_preset($self->current_preset); } sub get_preset { my ($self, $i) = @_; return $self->{presets}[$i]; } sub save_preset { my ($self, $name) = @_; # since buttons (and choices too) don't get focus on Mac, we set focus manually # to the treectrl so that the EVT_* events are fired for the input field having # focus currently. is there anything better than this? $self->{treectrl}->SetFocus; if (!defined $name) { my $preset = $self->get_current_preset; my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.ini$//i; my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], ); return unless $dlg->ShowModal == wxID_OK; $name = $dlg->get_name; } $self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name); $self->load_presets; $self->select_preset_by_name($name); $self->_on_presets_changed; } sub on_value_change { my ($self, $cb) = @_; $self->{on_value_change} = $cb; } sub on_presets_changed { my ($self, $cb) = @_; $self->{on_presets_changed} = $cb; } # This method is supposed to be called whenever new values are loaded # or changed by user (so also when a preset is loaded). # propagate event to the parent sub _on_value_change { my $self = shift; $self->{on_value_change}->(@_) if $self->{on_value_change}; $self->_update; } sub _update {} sub _on_presets_changed { my $self = shift; $self->{on_presets_changed}->( $self->{presets}, scalar($self->{presets_choice}->GetSelection), $self->is_dirty, ) if $self->{on_presets_changed}; } sub on_preset_loaded {} sub hidden_options {} sub config { $_[0]->{config}->clone } sub select_default_preset { my $self = shift; $self->select_preset(0); } sub select_preset { my $self = shift; $self->{presets_choice}->SetSelection($_[0]); $self->on_select_preset; } sub select_preset_by_name { my ($self, $name) = @_; $name = Unicode::Normalize::NFC($name); $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); } sub on_select_preset { my $self = shift; if ($self->is_dirty) { my $old_preset = $self->get_current_preset; my $name = $old_preset->default ? 'Default preset' : "Preset \"" . $old_preset->name . "\""; my @option_names = (); foreach my $opt_key (@{$self->dirty_options}) { my $opt = $Slic3r::Config::Options->{$opt_key}; my $name = $opt->{full_label} // $opt->{label}; if ($opt->{category}) { $name = $opt->{category} . " > $name"; } push @option_names, $name; } my $changes = join "\n", map "- $_", @option_names; my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?", 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if ($confirm->ShowModal == wxID_NO) { $self->{presets_choice}->SetSelection($self->current_preset); # trigger the on_presets_changed event so that we also restore the previous value # in the plater selector $self->_on_presets_changed; return; } } $self->current_preset($self->{presets_choice}->GetSelection); my $preset = $self->get_current_preset; my $preset_config = $self->get_preset_config($preset); eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); foreach my $opt_key (@{$self->{config}->get_keys}) { $self->{config}->set($opt_key, $preset_config->get($opt_key)) if $preset_config->has($opt_key); } ($preset->default || $preset->external) ? $self->{btn_delete_preset}->Disable : $self->{btn_delete_preset}->Enable; $self->_update; $self->on_preset_loaded; $self->reload_config; $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; }; if ($@) { $@ = "I was unable to load the selected config file: $@"; Slic3r::GUI::catch_error($self); $self->select_default_preset; } # use CallAfter because some field triggers schedule on_change calls using CallAfter, # and we don't want them to be called after this update_dirty() as they would mark the # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { $self->_on_presets_changed; $self->update_dirty; }); wxTheApp->save_settings; } sub init_config_options { my ($self, @opt_keys) = @_; $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); } sub add_options_page { my $self = shift; my ($title, $icon, %params) = @_; if ($icon) { my $bitmap = Wx::Bitmap->new("$Slic3r::var/$icon", wxBITMAP_TYPE_PNG); $self->{icons}->Add($bitmap); $self->{iconcount}++; } my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}); $page->Hide; $self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; return $page; } sub reload_config { my $self = shift; $_->reload_config for @{$self->{pages}}; } sub update_tree { my ($self) = @_; # get label of the currently selected item my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection); my $rootItem = $self->{treectrl}->GetRootItem; $self->{treectrl}->DeleteChildren($rootItem); my $have_selection = 0; foreach my $page (@{$self->{pages}}) { my $itemId = $self->{treectrl}->AppendItem($rootItem, $page->{title}, $page->{iconID}); if ($page->{title} eq $selected) { $self->{disable_tree_sel_changed_event} = 1; $self->{treectrl}->SelectItem($itemId); $self->{disable_tree_sel_changed_event} = 0; $have_selection = 1; } } if (!$have_selection) { # this is triggered on first load, so we don't disable the sel change event $self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem)); } } sub update_dirty { my $self = shift; foreach my $i (0..$#{$self->{presets}}) { my $preset = $self->get_preset($i); if ($i == $self->current_preset && $self->is_dirty) { $self->{presets_choice}->SetString($i, $preset->name . " (modified)"); } else { $self->{presets_choice}->SetString($i, $preset->name); } } $self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769 $self->_on_presets_changed; } sub is_dirty { my $self = shift; return @{$self->dirty_options} > 0; } sub dirty_options { my $self = shift; return [] if !defined $self->current_preset; # happens during initialization return $self->get_preset_config($self->get_current_preset)->diff($self->{config}); } sub load_presets { my $self = shift; $self->{presets} = [ Slic3r::GUI::Tab::Preset->new( default => 1, name => '- default -', ), ]; my %presets = wxTheApp->presets($self->name); foreach my $preset_name (sort keys %presets) { push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( name => $preset_name, file => $presets{$preset_name}, ); } $self->current_preset(undef); $self->{presets_choice}->Clear; $self->{presets_choice}->Append($_->name) for @{$self->{presets}}; { # load last used preset my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}}; $self->select_preset($i || 0); } $self->_on_presets_changed; } sub load_config_file { my $self = shift; my ($file) = @_; # look for the loaded config among the existing menu items my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}}; if (!$i) { my $preset_name = basename($file); # keep the .ini suffix push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( file => $file, name => $preset_name, external => 1, ); $self->{presets_choice}->Append($preset_name); $i = $#{$self->{presets}}; } $self->{presets_choice}->SetSelection($i); $self->on_select_preset; $self->_on_presets_changed; } sub load_config { my $self = shift; my ($config) = @_; foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); $self->update_dirty; } $self->reload_config; $self->_update; } sub get_preset_config { my ($self, $preset) = @_; return $preset->config($self->{config}->get_keys); } sub get_field { my ($self, $opt_key, $opt_index) = @_; foreach my $page (@{ $self->{pages} }) { my $field = $page->get_field($opt_key, $opt_index); return $field if defined $field; } return undef; } sub set_value { my $self = shift; my ($opt_key, $value) = @_; my $changed = 0; foreach my $page (@{ $self->{pages} }) { $changed = 1 if $page->set_value($opt_key, $value); } return $changed; } package Slic3r::GUI::Tab::Print; use base 'Slic3r::GUI::Tab'; use List::Util qw(first); use Wx qw(:icon :dialog :id); sub name { 'print' } sub title { 'Print Settings' } sub build { my $self = shift; $self->init_config_options(qw( layer_height first_layer_height perimeters spiral_vase top_solid_layers bottom_solid_layers extra_perimeters avoid_crossing_perimeters thin_walls overhangs seam_position external_perimeters_first fill_density fill_pattern external_fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters infill_first max_print_speed max_volumetric_speed 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 travel_speed first_layer_speed perimeter_acceleration infill_acceleration bridge_acceleration first_layer_acceleration default_acceleration skirts skirt_distance skirt_height min_skirt_length brim_width support_material support_material_threshold support_material_enforce_layers raft_layers support_material_pattern support_material_spacing support_material_angle support_material_interface_layers support_material_interface_spacing support_material_contact_distance dont_support_bridges notes complete_objects extruder_clearance_radius extruder_clearance_height gcode_comments output_filename_format post_process perimeter_extruder infill_extruder solid_infill_extruder support_material_extruder support_material_interface_extruder ooze_prevention standby_temperature_delta interface_shells 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 infill_overlap bridge_flow_ratio xy_size_compensation threads resolution )); { my $page = $self->add_options_page('Layers and perimeters', 'layers.png'); { my $optgroup = $page->new_optgroup('Layer height'); $optgroup->append_single_option_line('layer_height'); $optgroup->append_single_option_line('first_layer_height'); } { my $optgroup = $page->new_optgroup('Vertical shells'); $optgroup->append_single_option_line('perimeters'); $optgroup->append_single_option_line('spiral_vase'); } { my $optgroup = $page->new_optgroup('Horizontal shells'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Solid layers', ); $line->append_option($optgroup->get_option('top_solid_layers')); $line->append_option($optgroup->get_option('bottom_solid_layers')); $optgroup->append_line($line); } { my $optgroup = $page->new_optgroup('Quality (slower slicing)'); $optgroup->append_single_option_line('extra_perimeters'); $optgroup->append_single_option_line('avoid_crossing_perimeters'); $optgroup->append_single_option_line('thin_walls'); $optgroup->append_single_option_line('overhangs'); } { my $optgroup = $page->new_optgroup('Advanced'); $optgroup->append_single_option_line('seam_position'); $optgroup->append_single_option_line('external_perimeters_first'); } } { my $page = $self->add_options_page('Infill', 'shading.png'); { my $optgroup = $page->new_optgroup('Infill'); $optgroup->append_single_option_line('fill_density'); $optgroup->append_single_option_line('fill_pattern'); $optgroup->append_single_option_line('external_fill_pattern'); } { my $optgroup = $page->new_optgroup('Reducing printing time'); $optgroup->append_single_option_line('infill_every_layers'); $optgroup->append_single_option_line('infill_only_where_needed'); } { my $optgroup = $page->new_optgroup('Advanced'); $optgroup->append_single_option_line('solid_infill_every_layers'); $optgroup->append_single_option_line('fill_angle'); $optgroup->append_single_option_line('solid_infill_below_area'); $optgroup->append_single_option_line('only_retract_when_crossing_perimeters'); $optgroup->append_single_option_line('infill_first'); } } { my $page = $self->add_options_page('Skirt and brim', 'box.png'); { my $optgroup = $page->new_optgroup('Skirt'); $optgroup->append_single_option_line('skirts'); $optgroup->append_single_option_line('skirt_distance'); $optgroup->append_single_option_line('skirt_height'); $optgroup->append_single_option_line('min_skirt_length'); } { my $optgroup = $page->new_optgroup('Brim'); $optgroup->append_single_option_line('brim_width'); } } { my $page = $self->add_options_page('Support material', 'building.png'); { my $optgroup = $page->new_optgroup('Support material'); $optgroup->append_single_option_line('support_material'); $optgroup->append_single_option_line('support_material_threshold'); $optgroup->append_single_option_line('support_material_enforce_layers'); } { my $optgroup = $page->new_optgroup('Raft'); $optgroup->append_single_option_line('raft_layers'); } { my $optgroup = $page->new_optgroup('Options for support material and raft'); $optgroup->append_single_option_line('support_material_contact_distance'); $optgroup->append_single_option_line('support_material_pattern'); $optgroup->append_single_option_line('support_material_spacing'); $optgroup->append_single_option_line('support_material_angle'); $optgroup->append_single_option_line('support_material_interface_layers'); $optgroup->append_single_option_line('support_material_interface_spacing'); $optgroup->append_single_option_line('dont_support_bridges'); } } { my $page = $self->add_options_page('Speed', 'time.png'); { my $optgroup = $page->new_optgroup('Speed for print moves'); $optgroup->append_single_option_line('perimeter_speed'); $optgroup->append_single_option_line('small_perimeter_speed'); $optgroup->append_single_option_line('external_perimeter_speed'); $optgroup->append_single_option_line('infill_speed'); $optgroup->append_single_option_line('solid_infill_speed'); $optgroup->append_single_option_line('top_solid_infill_speed'); $optgroup->append_single_option_line('support_material_speed'); $optgroup->append_single_option_line('support_material_interface_speed'); $optgroup->append_single_option_line('bridge_speed'); $optgroup->append_single_option_line('gap_fill_speed'); } { my $optgroup = $page->new_optgroup('Speed for non-print moves'); $optgroup->append_single_option_line('travel_speed'); } { my $optgroup = $page->new_optgroup('Modifiers'); $optgroup->append_single_option_line('first_layer_speed'); } { my $optgroup = $page->new_optgroup('Acceleration control (advanced)'); $optgroup->append_single_option_line('perimeter_acceleration'); $optgroup->append_single_option_line('infill_acceleration'); $optgroup->append_single_option_line('bridge_acceleration'); $optgroup->append_single_option_line('first_layer_acceleration'); $optgroup->append_single_option_line('default_acceleration'); } { my $optgroup = $page->new_optgroup('Autospeed (advanced)'); $optgroup->append_single_option_line('max_print_speed'); $optgroup->append_single_option_line('max_volumetric_speed'); } } { my $page = $self->add_options_page('Multiple Extruders', 'funnel.png'); { my $optgroup = $page->new_optgroup('Extruders'); $optgroup->append_single_option_line('perimeter_extruder'); $optgroup->append_single_option_line('infill_extruder'); $optgroup->append_single_option_line('solid_infill_extruder'); $optgroup->append_single_option_line('support_material_extruder'); $optgroup->append_single_option_line('support_material_interface_extruder'); } { my $optgroup = $page->new_optgroup('Ooze prevention'); $optgroup->append_single_option_line('ooze_prevention'); $optgroup->append_single_option_line('standby_temperature_delta'); } { my $optgroup = $page->new_optgroup('Advanced'); $optgroup->append_single_option_line('interface_shells'); } } { my $page = $self->add_options_page('Advanced', 'wrench.png'); { my $optgroup = $page->new_optgroup('Extrusion width', label_width => 180, ); $optgroup->append_single_option_line('extrusion_width'); $optgroup->append_single_option_line('first_layer_extrusion_width'); $optgroup->append_single_option_line('perimeter_extrusion_width'); $optgroup->append_single_option_line('external_perimeter_extrusion_width'); $optgroup->append_single_option_line('infill_extrusion_width'); $optgroup->append_single_option_line('solid_infill_extrusion_width'); $optgroup->append_single_option_line('top_infill_extrusion_width'); $optgroup->append_single_option_line('support_material_extrusion_width'); } { my $optgroup = $page->new_optgroup('Overlap'); $optgroup->append_single_option_line('infill_overlap'); } { my $optgroup = $page->new_optgroup('Flow'); $optgroup->append_single_option_line('bridge_flow_ratio'); } { my $optgroup = $page->new_optgroup('Other'); $optgroup->append_single_option_line('xy_size_compensation'); $optgroup->append_single_option_line('threads') if $Slic3r::have_threads; $optgroup->append_single_option_line('resolution'); } } { my $page = $self->add_options_page('Output options', 'page_white_go.png'); { my $optgroup = $page->new_optgroup('Sequential printing'); $optgroup->append_single_option_line('complete_objects'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Extruder clearance (mm)', ); foreach my $opt_key (qw(extruder_clearance_radius extruder_clearance_height)) { my $option = $optgroup->get_option($opt_key); $option->width(60); $line->append_option($option); } $optgroup->append_line($line); } { my $optgroup = $page->new_optgroup('Output file'); $optgroup->append_single_option_line('gcode_comments'); { my $option = $optgroup->get_option('output_filename_format'); $option->full_width(1); $optgroup->append_single_option_line($option); } } { my $optgroup = $page->new_optgroup('Post-processing scripts', label_width => 0, ); my $option = $optgroup->get_option('post_process'); $option->full_width(1); $option->height(50); $optgroup->append_single_option_line($option); } } { my $page = $self->add_options_page('Notes', 'note.png'); { my $optgroup = $page->new_optgroup('Notes', label_width => 0, ); my $option = $optgroup->get_option('notes'); $option->full_width(1); $option->height(250); $optgroup->append_single_option_line($option); } } } sub _update { my ($self) = @_; my $config = $self->{config}; if ($config->spiral_vase && !($config->perimeters == 1 && $config->top_solid_layers == 0 && $config->fill_density == 0)) { my $dialog = Wx::MessageDialog->new($self, "The Spiral Vase mode requires:\n" . "- one perimeter\n" . "- no top solid layers\n" . "- 0% fill density\n" . "- no support material\n" . "\nShall I adjust those settings in order to enable Spiral Vase?", 'Spiral Vase', wxICON_WARNING | wxYES | wxNO); if ($dialog->ShowModal() == wxID_YES) { my $new_conf = Slic3r::Config->new; $new_conf->set("perimeters", 1); $new_conf->set("top_solid_layers", 0); $new_conf->set("fill_density", 0); $new_conf->set("support_material", 0); $self->load_config($new_conf); } else { my $new_conf = Slic3r::Config->new; $new_conf->set("spiral_vase", 0); $self->load_config($new_conf); } } if ($config->fill_density == 100 && !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) { my $dialog = Wx::MessageDialog->new($self, "The " . $config->fill_pattern . " infill pattern is not supposed to work at 100% density.\n" . "\nShall I switch to rectilinear fill pattern?", 'Infill', wxICON_WARNING | wxYES | wxNO); my $new_conf = Slic3r::Config->new; if ($dialog->ShowModal() == wxID_YES) { $new_conf->set("fill_pattern", 1); } else { $new_conf->set("fill_density", 40); } $self->load_config($new_conf); } my $have_perimeters = $config->perimeters > 0; $self->get_field($_)->toggle($have_perimeters) for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first external_perimeter_extrusion_width perimeter_speed small_perimeter_speed external_perimeter_speed); my $have_infill = $config->fill_density > 0; # infill_extruder uses the same logic as in Print::extruders() $self->get_field($_)->toggle($have_infill) for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers solid_infill_below_area infill_extruder); my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0); # solid_infill_extruder uses the same logic as in Print::extruders() $self->get_field($_)->toggle($have_solid_infill) for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width solid_infill_speed); $self->get_field($_)->toggle($have_infill || $have_solid_infill) for qw(fill_angle infill_extrusion_width infill_speed bridge_speed); $self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill); my $have_top_solid_infill = $config->top_solid_layers > 0; $self->get_field($_)->toggle($have_top_solid_infill) for qw(top_infill_extrusion_width top_solid_infill_speed); my $have_default_acceleration = $config->default_acceleration > 0; $self->get_field($_)->toggle($have_default_acceleration) for qw(perimeter_acceleration infill_acceleration bridge_acceleration first_layer_acceleration); my $have_skirt = $config->skirts > 0 || $config->min_skirt_length > 0; $self->get_field($_)->toggle($have_skirt) for qw(skirt_distance skirt_height); my $have_brim = $config->brim_width > 0; # perimeter_extruder uses the same logic as in Print::extruders() $self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim); my $have_support_material = $config->support_material || $config->raft_layers > 0; my $have_support_interface = $config->support_material_interface_layers > 0; $self->get_field($_)->toggle($have_support_material) for qw(support_material_threshold support_material_enforce_layers support_material_pattern support_material_spacing support_material_angle support_material_interface_layers dont_support_bridges support_material_extrusion_width support_material_contact_distance); $self->get_field($_)->toggle($have_support_material && $have_support_interface) for qw(support_material_interface_spacing support_material_interface_extruder support_material_interface_speed); $self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim); $self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt); $self->get_field('support_material_speed')->toggle($have_support_material || $have_brim || $have_skirt); my $have_sequential_printing = $config->complete_objects; $self->get_field($_)->toggle($have_sequential_printing) for qw(extruder_clearance_radius extruder_clearance_height); my $have_ooze_prevention = $config->ooze_prevention; $self->get_field($_)->toggle($have_ooze_prevention) for qw(standby_temperature_delta); } sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () } package Slic3r::GUI::Tab::Filament; use base 'Slic3r::GUI::Tab'; sub name { 'filament' } sub title { 'Filament Settings' } sub build { my $self = shift; $self->init_config_options(qw( filament_colour filament_diameter extrusion_multiplier temperature first_layer_temperature bed_temperature first_layer_bed_temperature fan_always_on cooling min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers fan_below_layer_time slowdown_below_layer_time min_print_speed )); { my $page = $self->add_options_page('Filament', 'spool.png'); { my $optgroup = $page->new_optgroup('Filament'); $optgroup->append_single_option_line('filament_colour', 0); $optgroup->append_single_option_line('filament_diameter', 0); $optgroup->append_single_option_line('extrusion_multiplier', 0); } { my $optgroup = $page->new_optgroup('Temperature (°C)'); { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Extruder', ); $line->append_option($optgroup->get_option('first_layer_temperature', 0)); $line->append_option($optgroup->get_option('temperature', 0)); $optgroup->append_line($line); } { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Bed', ); $line->append_option($optgroup->get_option('first_layer_bed_temperature')); $line->append_option($optgroup->get_option('bed_temperature')); $optgroup->append_line($line); } } } { my $page = $self->add_options_page('Cooling', 'hourglass.png'); { my $optgroup = $page->new_optgroup('Enable'); $optgroup->append_single_option_line('fan_always_on'); $optgroup->append_single_option_line('cooling'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => '', full_width => 1, widget => sub { my ($parent) = @_; return $self->{description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); }, ); $optgroup->append_line($line); } { my $optgroup = $page->new_optgroup('Fan settings'); { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Fan speed', ); $line->append_option($optgroup->get_option('min_fan_speed')); $line->append_option($optgroup->get_option('max_fan_speed')); $optgroup->append_line($line); } $optgroup->append_single_option_line('bridge_fan_speed'); $optgroup->append_single_option_line('disable_fan_first_layers'); } { my $optgroup = $page->new_optgroup('Cooling thresholds', label_width => 250, ); $optgroup->append_single_option_line('fan_below_layer_time'); $optgroup->append_single_option_line('slowdown_below_layer_time'); $optgroup->append_single_option_line('min_print_speed'); } } } sub _update { my ($self) = @_; $self->_update_description; my $cooling = $self->{config}->cooling; $self->get_field($_)->toggle($cooling) for qw(max_fan_speed fan_below_layer_time slowdown_below_layer_time min_print_speed); $self->get_field($_)->toggle($cooling || $self->{config}->fan_always_on) for qw(min_fan_speed disable_fan_first_layers); } sub _update_description { my $self = shift; my $config = $self->config; my $msg = ""; my $fan_other_layers = $config->fan_always_on ? sprintf "will always run at %d%%%s.", $config->min_fan_speed, ($config->disable_fan_first_layers > 1 ? " except for the first " . $config->disable_fan_first_layers . " layers" : $config->disable_fan_first_layers == 1 ? " except for the first layer" : "") : "will be turned off."; if ($config->cooling) { $msg = sprintf "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).", $config->slowdown_below_layer_time, $config->max_fan_speed, $config->slowdown_below_layer_time, $config->min_print_speed; if ($config->fan_below_layer_time > $config->slowdown_below_layer_time) { $msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", $config->fan_below_layer_time, $config->max_fan_speed, $config->min_fan_speed; } $msg .= "\nDuring the other layers, fan $fan_other_layers" } else { $msg = "Fan $fan_other_layers"; } $self->{description_line}->SetText($msg); } package Slic3r::GUI::Tab::Printer; use base 'Slic3r::GUI::Tab'; use Wx qw(wxTheApp :sizer :button :bitmap :misc :id); use Wx::Event qw(EVT_BUTTON); sub name { 'printer' } sub title { 'Printer Settings' } sub build { my $self = shift; $self->init_config_options(qw( bed_shape z_offset gcode_flavor use_relative_e_distances octoprint_host octoprint_apikey use_firmware_retraction pressure_advance vibration_limit use_volumetric_e start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe retract_length_toolchange retract_restart_extra_toolchange )); my $bed_shape_widget = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); if ($Slic3r::GUI::have_button_icons) { $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG)); } my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($btn); EVT_BUTTON($self, $btn, sub { my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); if ($dlg->ShowModal == wxID_OK) { my $value = $dlg->GetValue; $self->{config}->set('bed_shape', $value); $self->update_dirty; $self->_on_value_change('bed_shape', $value); } }); return $sizer; }; $self->{extruders_count} = 1; { my $page = $self->add_options_page('General', 'printer_empty.png'); { my $optgroup = $page->new_optgroup('Size and coordinates'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Bed shape', widget => $bed_shape_widget, ); $optgroup->append_line($line); $optgroup->append_single_option_line('z_offset'); } { my $optgroup = $page->new_optgroup('Capabilities'); { my $option = Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'extruders_count', type => 'i', default => 1, label => 'Extruders', tooltip => 'Number of extruders of the printer.', min => 1, ); $optgroup->append_single_option_line($option); } $optgroup->on_change(sub { my ($opt_id) = @_; if ($opt_id eq 'extruders_count') { wxTheApp->CallAfter(sub { $self->_extruders_count_changed($optgroup->get_value('extruders_count')); }); $self->update_dirty; } }); } { my $optgroup = $page->new_optgroup('OctoPrint upload'); # append two buttons to the Host line my $octoprint_host_browse = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); if ($Slic3r::GUI::have_button_icons) { $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/zoom.png", wxBITMAP_TYPE_PNG)); } if (!eval "use Net::Bonjour; 1") { $btn->Disable; } EVT_BUTTON($self, $btn, sub { my $dlg = Slic3r::GUI::BonjourBrowser->new($self); if ($dlg->ShowModal == wxID_OK) { my $value = $dlg->GetValue . ":" . $dlg->GetPort; $self->{config}->set('octoprint_host', $value); $self->update_dirty; $self->_on_value_change('octoprint_host', $value); $self->reload_config; } }); return $btn; }; my $octoprint_host_test = sub { my ($parent) = @_; my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); if ($Slic3r::GUI::have_button_icons) { $btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG)); } EVT_BUTTON($self, $btn, sub { my $ua = LWP::UserAgent->new; $ua->timeout(10); my $res = $ua->get( "http://" . $self->{config}->octoprint_host . "/api/version", 'X-Api-Key' => $self->{config}->octoprint_apikey, ); if ($res->is_success) { Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!"); } else { Slic3r::GUI::show_error($self, "I wasn't able to connect to OctoPrint (" . $res->status_line . "). " . "Check hostname and OctoPrint version (at least 1.1.0 is required)."); } }); return $btn; }; my $host_line = $optgroup->create_single_option_line('octoprint_host'); $host_line->append_widget($octoprint_host_browse); $host_line->append_widget($octoprint_host_test); $optgroup->append_line($host_line); $optgroup->append_single_option_line('octoprint_apikey'); } { my $optgroup = $page->new_optgroup('Firmware'); $optgroup->append_single_option_line('gcode_flavor'); } { my $optgroup = $page->new_optgroup('Advanced'); $optgroup->append_single_option_line('use_relative_e_distances'); $optgroup->append_single_option_line('use_firmware_retraction'); $optgroup->append_single_option_line('use_volumetric_e'); $optgroup->append_single_option_line('pressure_advance'); $optgroup->append_single_option_line('vibration_limit'); } } { my $page = $self->add_options_page('Custom G-code', 'cog.png'); { my $optgroup = $page->new_optgroup('Start G-code', label_width => 0, ); my $option = $optgroup->get_option('start_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } { my $optgroup = $page->new_optgroup('End G-code', label_width => 0, ); my $option = $optgroup->get_option('end_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } { my $optgroup = $page->new_optgroup('Before layer change G-code', label_width => 0, ); my $option = $optgroup->get_option('before_layer_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } { my $optgroup = $page->new_optgroup('After layer change G-code', label_width => 0, ); my $option = $optgroup->get_option('layer_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } { my $optgroup = $page->new_optgroup('Tool change G-code', label_width => 0, ); my $option = $optgroup->get_option('toolchange_gcode'); $option->full_width(1); $option->height(150); $optgroup->append_single_option_line($option); } } $self->{extruder_pages} = []; $self->_build_extruder_pages; } sub _extruders_count_changed { my ($self, $extruders_count) = @_; $self->{extruders_count} = $extruders_count; $self->_build_extruder_pages; $self->_on_value_change('extruders_count', $extruders_count); $self->_update; } 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 _build_extruder_pages { my $self = shift; 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 my $page = $self->{extruder_pages}[$extruder_idx] = $self->add_options_page("Extruder " . ($extruder_idx + 1), 'funnel.png'); { my $optgroup = $page->new_optgroup('Size'); $optgroup->append_single_option_line('nozzle_diameter', $extruder_idx); } { my $optgroup = $page->new_optgroup('Position (for multi-extruder printers)'); $optgroup->append_single_option_line('extruder_offset', $extruder_idx); } { my $optgroup = $page->new_optgroup('Retraction'); $optgroup->append_single_option_line($_, $extruder_idx) for qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe); } { my $optgroup = $page->new_optgroup('Retraction when tool is disabled (advanced settings for multi-extruder setups)'); $optgroup->append_single_option_line($_, $extruder_idx) for qw(retract_length_toolchange retract_restart_extra_toolchange); } } # remove extra pages if ($self->{extruders_count} <= $#{$self->{extruder_pages}}) { $_->Destroy for @{$self->{extruder_pages}}[$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}}), @{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ], ); $self->update_tree; } sub _update { my ($self) = @_; my $config = $self->{config}; if ($config->get('octoprint_host') && eval "use LWP::UserAgent; 1") { $self->{octoprint_host_test_btn}->Enable; } else { $self->{octoprint_host_test_btn}->Disable; } $self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host')); my $have_multiple_extruders = $self->{extruders_count} > 1; $self->get_field('toolchange_gcode')->toggle($have_multiple_extruders); for my $i (0 .. ($self->{extruders_count}-1)) { my $have_retract_length = $config->get_at('retract_length', $i) > 0; # when using firmware retraction, firmware decides retraction length $self->get_field('retract_length', $i)->toggle(!$config->use_firmware_retraction); # user can customize travel length if we have retraction length or we're using # firmware retraction $self->get_field('retract_before_travel', $i)->toggle($have_retract_length || $config->use_firmware_retraction); # user can customize other retraction options if retraction is enabled my $retraction = ($have_retract_length || $config->use_firmware_retraction); $self->get_field($_, $i)->toggle($retraction) for qw(retract_lift retract_layer_change); # some options only apply when not using firmware retraction $self->get_field($_, $i)->toggle($retraction && !$config->use_firmware_retraction) for qw(retract_speed retract_restart_extra wipe); $self->get_field('retract_length_toolchange', $i)->toggle($have_multiple_extruders); my $toolchange_retraction = $config->get_at('retract_length_toolchange', $i) > 0; $self->get_field('retract_restart_extra_toolchange', $i)->toggle ($have_multiple_extruders && $toolchange_retraction); } } # this gets executed after preset is loaded and before GUI fields are updated sub on_preset_loaded { my $self = shift; # update the extruders count field { # update the GUI field according to the number of nozzle diameters supplied my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; $self->set_value('extruders_count', $extruders_count); $self->_extruders_count_changed($extruders_count); } } sub load_config_file { my $self = shift; $self->SUPER::load_config_file(@_); Slic3r::GUI::warning_catcher($self)->( "Your configuration was imported. However, Slic3r is currently only able to import settings " . "for the first defined filament. We recommend you don't use exported configuration files " . "for multi-extruder setups and rely on the built-in preset management system instead.") if @{ $self->{config}->nozzle_diameter } > 1; } package Slic3r::GUI::Tab::Page; use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; sub new { my $class = shift; my ($parent, $title, $iconID) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{optgroups} = []; $self->{title} = $title; $self->{iconID} = $iconID; $self->SetScrollbars(1, 1, 1, 1); $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); $self->SetSizer($self->{vsizer}); return $self; } sub new_optgroup { my ($self, $title, %params) = @_; my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( parent => $self, title => $title, config => $self->GetParent->{config}, label_width => $params{label_width} // 200, on_change => sub { my ($opt_key, $value) = @_; wxTheApp->CallAfter(sub { $self->GetParent->update_dirty; $self->GetParent->_on_value_change($opt_key, $value); }); }, ); push @{$self->{optgroups}}, $optgroup; $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); return $optgroup; } sub reload_config { my ($self) = @_; $_->reload_config for @{$self->{optgroups}}; } sub get_field { my ($self, $opt_key, $opt_index) = @_; foreach my $optgroup (@{ $self->{optgroups} }) { my $field = $optgroup->get_fieldc($opt_key, $opt_index); return $field if defined $field; } return undef; } sub set_value { my $self = shift; my ($opt_key, $value) = @_; my $changed = 0; foreach my $optgroup (@{$self->{optgroups}}) { $changed = 1 if $optgroup->set_value($opt_key, $value); } return $changed; } package Slic3r::GUI::SavePresetWindow; use Wx qw(:combobox :dialog :id :misc :sizer); use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize); my @values = @{$params{values}}; my $text = Wx::StaticText->new($self, -1, "Save " . lc($params{title}) . " as:", wxDefaultPosition, wxDefaultSize); $self->{combo} = Wx::ComboBox->new($self, -1, $params{default}, wxDefaultPosition, wxDefaultSize, \@values, wxTE_PROCESS_ENTER); my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($text, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); $sizer->Add($self->{combo}, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); EVT_BUTTON($self, wxID_OK, \&accept); EVT_TEXT_ENTER($self, $self->{combo}, \&accept); $self->SetSizer($sizer); $sizer->SetSizeHints($self); return $self; } sub accept { my ($self, $event) = @_; if (($self->{chosen_name} = $self->{combo}->GetValue)) { if ($self->{chosen_name} !~ /^[^<>:\/\\|?*\"]+$/i) { Slic3r::GUI::show_error($self, "The supplied name is not valid; the following characters are not allowed: <>:/\|?*\""); } elsif ($self->{chosen_name} eq '- default -') { Slic3r::GUI::show_error($self, "The supplied name is not available."); } else { $self->EndModal(wxID_OK); } } } sub get_name { my $self = shift; return $self->{chosen_name}; } package Slic3r::GUI::Tab::Preset; use Moo; has 'default' => (is => 'ro', default => sub { 0 }); has 'external' => (is => 'ro', default => sub { 0 }); has 'name' => (is => 'rw', required => 1); has 'file' => (is => 'rw'); sub config { my ($self, $keys) = @_; if ($self->default) { return Slic3r::Config->new_from_defaults(@$keys); } else { if (!-e Slic3r::encode_path($self->file)) { Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); return undef; } # apply preset values on top of defaults my $config = Slic3r::Config->new_from_defaults(@$keys); my $external_config = Slic3r::Config->load($self->file); $config->set($_, $external_config->get($_)) for grep $external_config->has($_), @$keys; return $config; } } 1; Slic3r-1.2.9/lib/Slic3r/Geometry.pm000066400000000000000000000446101254023100400167140ustar00rootroot00000000000000package Slic3r::Geometry; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 Z1 Z2 MIN MAX epsilon slope line_point_belongs_to_segment points_coincide distance_between_points normalize tan move_points_3D point_in_polygon point_in_segment segment_in_segment polyline_lines polygon_lines point_along_segment polygon_segment_having_point polygon_has_subsegment deg2rad rad2deg rotate_points move_points dot perp line_intersection bounding_box bounding_box_intersect angle3points chained_path chained_path_from collinear scale unscale 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 directions_parallel directions_parallel_within ); use constant PI => 4 * atan2(1, 1); use constant A => 0; use constant B => 1; use constant X1 => 0; use constant Y1 => 1; use constant X2 => 2; use constant Y2 => 3; use constant Z1 => 4; use constant Z2 => 5; use constant MIN => 0; use constant MAX => 1; our $parallel_degrees_limit = abs(deg2rad(0.1)); sub epsilon () { 1E-4 } sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } sub tan { my ($angle) = @_; return (sin $angle) / (cos $angle); } sub slope { my ($line) = @_; return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical return ($line->[B][Y] - $line->[A][Y]) / ($line->[B][X] - $line->[A][X]); } # this subroutine checks whether a given point may belong to a given # segment given the hypothesis that it belongs to the line containing # the segment sub line_point_belongs_to_segment { my ($point, $segment) = @_; #printf " checking whether %f,%f may belong to segment %f,%f - %f,%f\n", # @$point, map @$_, @$segment; my @segment_extents = ( [ sort { $a <=> $b } map $_->[X], @$segment ], [ sort { $a <=> $b } map $_->[Y], @$segment ], ); return 0 if $point->[X] < ($segment_extents[X][0] - epsilon) || $point->[X] > ($segment_extents[X][1] + epsilon); return 0 if $point->[Y] < ($segment_extents[Y][0] - epsilon) || $point->[Y] > ($segment_extents[Y][1] + epsilon); return 1; } sub points_coincide { my ($p1, $p2) = @_; return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon; return 0; } sub distance_between_points { my ($p1, $p2) = @_; return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2); } # this will check whether a point is in a polygon regardless of polygon orientation sub point_in_polygon { my ($point, $polygon) = @_; my ($x, $y) = @$point; my $n = @$polygon; my @x = map $_->[X], @$polygon; my @y = map $_->[Y], @$polygon; # Derived from the comp.graphics.algorithms FAQ, # courtesy of Wm. Randolph Franklin my ($i, $j); my $side = 0; # 0 = outside; 1 = inside for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) { if ( # If the y is between the (y-) borders... ($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i]) and # ...the (x,y) to infinity line crosses the edge # from the ith point to the jth point... ($x < ($x[$j] - $x[$i]) * ($y - $y[$i]) / ($y[$j] - $y[$i]) + $x[$i]) ) { $side = not $side; # Jump the fence } } # if point is not in polygon, let's check whether it belongs to the contour if (!$side && 0) { return 1 if polygon_segment_having_point($polygon, $point); } return $side; } sub point_in_segment { my ($point, $line) = @_; my ($x, $y) = @$point; my $line_p = $line->pp; my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X]; my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y]; # check whether the point is in the segment bounding box return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon) && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon); # if line is vertical, check whether point's X is the same as the line if ($line_p->[A][X] == $line_p->[B][X]) { return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0; } # calculate the Y in line at X of the point my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y]) * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]); return abs($y3 - $y) < epsilon ? 1 : 0; } sub segment_in_segment { my ($needle, $haystack) = @_; # a segment is contained in another segment if its endpoints are contained return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack); } sub polyline_lines { my ($polyline) = @_; my @points = @$polyline; return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1; } sub polygon_lines { my ($polygon) = @_; return polyline_lines([ @$polygon, $polygon->[0] ]); } # given a segment $p1-$p2, get the point at $distance from $p1 along segment sub point_along_segment { my ($p1, $p2, $distance) = @_; my $point = [ @$p1 ]; my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) ); for (X, Y) { if ($p1->[$_] != $p2->[$_]) { $point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length; } } return Slic3r::Point->new(@$point); } # given a $polygon, return the (first) segment having $point sub polygon_segment_having_point { my ($polygon, $point) = @_; foreach my $line (@{ $polygon->lines }) { return $line if point_in_segment($point, $line); } return undef; } # return true if the given segment is contained in any edge of the polygon sub polygon_has_subsegment { my ($polygon, $segment) = @_; foreach my $line (polygon_lines($polygon)) { return 1 if segment_in_segment($segment, $line); } return 0; } # polygon must be simple (non complex) and ccw sub polygon_is_convex { my ($points) = @_; for (my $i = 0; $i <= $#$points; $i++) { my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]); return 0 if $angle < PI; } return 1; } sub rotate_points { my ($radians, $center, @points) = @_; $center //= [0,0]; return map { [ $center->[X] + cos($radians) * ($_->[X] - $center->[X]) - sin($radians) * ($_->[Y] - $center->[Y]), $center->[Y] + cos($radians) * ($_->[Y] - $center->[Y]) + sin($radians) * ($_->[X] - $center->[X]), ] } @points; } sub move_points { my ($shift, @points) = @_; return map { my @p = @$_; Slic3r::Point->new($shift->[X] + $p[X], $shift->[Y] + $p[Y]); } @points; } sub move_points_3D { my ($shift, @points) = @_; return map [ $shift->[X] + $_->[X], $shift->[Y] + $_->[Y], $shift->[Z] + $_->[Z], ], @points; } sub normal { my ($line1, $line2) = @_; return [ ($line1->[Y] * $line2->[Z]) - ($line1->[Z] * $line2->[Y]), -($line2->[Z] * $line1->[X]) + ($line2->[X] * $line1->[Z]), ($line1->[X] * $line2->[Y]) - ($line1->[Y] * $line2->[X]), ]; } sub triangle_normal { my ($v1, $v2, $v3) = @_; my $u = [ map +($v2->[$_] - $v1->[$_]), (X,Y,Z) ]; my $v = [ map +($v3->[$_] - $v1->[$_]), (X,Y,Z) ]; return normal($u, $v); } sub normalize { my ($line) = @_; my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) ) or return [0, 0, 0]; # to avoid illegal division by zero return [ map $_ / $len, @$line ]; } # 2D dot product sub dot { my ($u, $v) = @_; return $u->[X] * $v->[X] + $u->[Y] * $v->[Y]; } # 2D perp product sub perp { my ($u, $v) = @_; return $u->[X] * $v->[Y] - $u->[Y] * $v->[X]; } sub line_intersects_any { my ($line, $lines) = @_; for (@$lines) { return 1 if line_intersection($line, $_, 1); } return 0; } sub line_intersection { my ($line1, $line2, $require_crossing) = @_; $require_crossing ||= 0; my $intersection = _line_intersection(map @$_, @$line1, @$line2); return (ref $intersection && $intersection->[1] == $require_crossing) ? $intersection->[0] : undef; } sub collinear { my ($line1, $line2, $require_overlapping) = @_; my $intersection = _line_intersection(map @$_, @$line1, @$line2); return 0 unless !ref($intersection) && ($intersection eq 'parallel collinear' || ($intersection eq 'parallel vertical' && abs($line1->[A][X] - $line2->[A][X]) < epsilon)); if ($require_overlapping) { my @box_a = bounding_box([ $line1->[0], $line1->[1] ]); my @box_b = bounding_box([ $line2->[0], $line2->[1] ]); return 0 unless bounding_box_intersect( 2, @box_a, @box_b ); } return 1; } sub _line_intersection { my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_; my ($x, $y); # The as-yet-undetermined intersection point. my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences my $dx10 = $x1 - $x0; # between the points P and Q. my $dy32 = $y3 - $y2; my $dx32 = $x3 - $x2; my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"? my $dx10z = abs( $dx10 ) < epsilon; my $dy32z = abs( $dy32 ) < epsilon; my $dx32z = abs( $dx32 ) < epsilon; my $dyx10; # The slopes. my $dyx32; $dyx10 = $dy10 / $dx10 unless $dx10z; $dyx32 = $dy32 / $dx32 unless $dx32z; # Now we know all differences and the slopes; # we can detect horizontal/vertical special cases. # E.g., slope = 0 means a horizontal line. unless ( defined $dyx10 or defined $dyx32 ) { return "parallel vertical"; } elsif ( $dy10z and not $dy32z ) { # First line horizontal. $y = $y0; $x = $x2 + ( $y - $y2 ) * $dx32 / $dy32; } elsif ( not $dy10z and $dy32z ) { # Second line horizontal. $y = $y2; $x = $x0 + ( $y - $y0 ) * $dx10 / $dy10; } elsif ( $dx10z and not $dx32z ) { # First line vertical. $x = $x0; $y = $y2 + $dyx32 * ( $x - $x2 ); } elsif ( not $dx10z and $dx32z ) { # Second line vertical. $x = $x2; $y = $y0 + $dyx10 * ( $x - $x0 ); } elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) { # The slopes are suspiciously close to each other. # Either we have parallel collinear or just parallel lines. # The bounding box checks have already weeded the cases # "parallel horizontal" and "parallel vertical" away. my $ya = $y0 - $dyx10 * $x0; my $yb = $y2 - $dyx32 * $x2; return "parallel collinear" if abs( $ya - $yb ) < epsilon; return "parallel"; } else { # None of the special cases matched. # We have a "honest" line intersection. $x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32); $y = $y0 + $dyx10 * ($x - $x0); } my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1); my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1); return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1]; } # http://paulbourke.net/geometry/lineline2d/ sub _line_intersection2 { my ($line1, $line2) = @_; my $denom = ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[B][X] - $line1->[A][X]) - ($line2->[B][X] - $line2->[A][X]) * ($line1->[B][Y] - $line1->[A][Y]); my $numerA = ($line2->[B][X] - $line2->[A][X]) * ($line1->[A][Y] - $line2->[A][Y]) - ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[A][X] - $line2->[A][X]); my $numerB = ($line1->[B][X] - $line1->[A][X]) * ($line1->[A][Y] - $line2->[A][Y]) - ($line1->[B][Y] - $line1->[A][Y]) * ($line1->[A][X] - $line2->[A][X]); # are the lines coincident? if (abs($numerA) < epsilon && abs($numerB) < epsilon && abs($denom) < epsilon) { return Slic3r::Point->new( ($line1->[A][X] + $line1->[B][X]) / 2, ($line1->[A][Y] + $line1->[B][Y]) / 2, ); } # are the lines parallel? if (abs($denom) < epsilon) { return undef; } # is the intersection along the segments? my $muA = $numerA / $denom; my $muB = $numerB / $denom; if ($muA < 0 || $muA > 1 || $muB < 0 || $muB > 1) { return undef; } return Slic3r::Point->new( $line1->[A][X] + $muA * ($line1->[B][X] - $line1->[A][X]), $line1->[A][Y] + $muA * ($line1->[B][Y] - $line1->[A][Y]), ); } # 2D sub bounding_box { my ($points) = @_; my @x = map $_->x, @$points; my @y = map $_->y, @$points; #,, my @bb = (undef, undef, undef, undef); for (0..$#x) { $bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1]; $bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2]; $bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1]; $bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2]; } return @bb[X1,Y1,X2,Y2]; } sub bounding_box_center { my ($bounding_box) = @_; return Slic3r::Point->new( ($bounding_box->[X2] + $bounding_box->[X1]) / 2, ($bounding_box->[Y2] + $bounding_box->[Y1]) / 2, ); } sub size_2D { my @bounding_box = bounding_box(@_); return ( ($bounding_box[X2] - $bounding_box[X1]), ($bounding_box[Y2] - $bounding_box[Y1]), ); } # bounding_box_intersect($d, @a, @b) # Return true if the given bounding boxes @a and @b intersect # in $d dimensions. Used by line_intersection(). sub bounding_box_intersect { my ( $d, @bb ) = @_; # Number of dimensions and box coordinates. my @aa = splice( @bb, 0, 2 * $d ); # The first box. # (@bb is the second one.) # Must intersect in all dimensions. for ( my $i_min = 0; $i_min < $d; $i_min++ ) { my $i_max = $i_min + $d; # The index for the maximum. return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ]; return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ]; } return 1; } # 3D sub bounding_box_3D { my ($points) = @_; my @extents = (map [undef, undef], X,Y,Z); foreach my $point (@$points) { for (X,Y,Z) { $extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN]; $extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX]; } } return @extents; } sub size_3D { my ($points) = @_; my @extents = bounding_box_3D($points); return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z); } # this assumes a CCW rotation from $p2 to $p3 around $p1 sub angle3points { my ($p1, $p2, $p3) = @_; # p1 is the center my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y]) - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]); # we only want to return only positive angles return $angle <= 0 ? $angle + 2*PI() : $angle; } sub polyline_remove_short_segments { my ($points, $min_length, $isPolygon) = @_; for (my $i = $isPolygon ? 0 : 1; $i < $#$points; $i++) { if (distance_between_points($points->[$i-1], $points->[$i]) < $min_length) { # we can remove $points->[$i] splice @$points, $i, 1; $i--; } } } sub douglas_peucker { my ($points, $tolerance) = @_; no warnings "recursion"; my $results = []; my $dmax = 0; my $index = 0; for my $i (1..$#$points) { my $d = $points->[$i]->distance_to(Slic3r::Line->new($points->[0], $points->[-1])); if ($d > $dmax) { $index = $i; $dmax = $d; } } if ($dmax >= $tolerance) { my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance); $results = [ @$dp1[0..($#$dp1-1)], @{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)}, ]; } else { $results = [ $points->[0], $points->[-1] ]; } return $results; } sub douglas_peucker2 { my ($points, $tolerance) = @_; my $anchor = 0; my $floater = $#$points; my @stack = (); my %keep = (); push @stack, [$anchor, $floater]; while (@stack) { ($anchor, $floater) = @{pop @stack}; # initialize line segment my ($anchor_x, $anchor_y, $seg_len); if (grep $points->[$floater][$_] != $points->[$anchor][$_], X, Y) { $anchor_x = $points->[$floater][X] - $points->[$anchor][X]; $anchor_y = $points->[$floater][Y] - $points->[$anchor][Y]; $seg_len = sqrt(($anchor_x ** 2) + ($anchor_y ** 2)); # get the unit vector $anchor_x /= $seg_len; $anchor_y /= $seg_len; } else { $anchor_x = $anchor_y = $seg_len = 0; } # inner loop: my $max_dist = 0; my $farthest = $anchor + 1; for my $i (($anchor + 1) .. $floater) { my $dist_to_seg = 0; # compare to anchor my $vecX = $points->[$i][X] - $points->[$anchor][X]; my $vecY = $points->[$i][Y] - $points->[$anchor][Y]; $seg_len = sqrt(($vecX ** 2) + ($vecY ** 2)); # dot product: my $proj = $vecX * $anchor_x + $vecY * $anchor_y; if ($proj < 0) { $dist_to_seg = $seg_len; } else { # compare to floater $vecX = $points->[$i][X] - $points->[$floater][X]; $vecY = $points->[$i][Y] - $points->[$floater][Y]; $seg_len = sqrt(($vecX ** 2) + ($vecY ** 2)); # dot product: $proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y); if ($proj < 0) { $dist_to_seg = $seg_len } else { # calculate perpendicular distance to line (pythagorean theorem): $dist_to_seg = sqrt(abs(($seg_len ** 2) - ($proj ** 2))); } if ($max_dist < $dist_to_seg) { $max_dist = $dist_to_seg; $farthest = $i; } } } if ($max_dist <= $tolerance) { # use line segment $keep{$_} = 1 for $anchor, $floater; } else { push @stack, [$anchor, $farthest]; push @stack, [$farthest, $floater]; } } return [ map $points->[$_], sort keys %keep ]; } 1; Slic3r-1.2.9/lib/Slic3r/Geometry/000077500000000000000000000000001254023100400163515ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Geometry/Clipper.pm000066400000000000000000000006111254023100400203030ustar00rootroot00000000000000package Slic3r::Geometry::Clipper; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); 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 diff_ppl intersection_ppl); 1; Slic3r-1.2.9/lib/Slic3r/Layer.pm000066400000000000000000000101321254023100400161650ustar00rootroot00000000000000package Slic3r::Layer; use strict; use warnings; use List::Util qw(first); use Slic3r::Geometry::Clipper qw(union_ex intersection_ex); # the following two were previously generated by Moo sub print { my $self = shift; return $self->object->print; } sub config { my $self = shift; return $self->object->config; } sub region { my $self = shift; my ($region_id) = @_; while ($self->region_count <= $region_id) { $self->add_region($self->object->print->get_region($self->region_count)); } return $self->get_region($region_id); } sub regions { my ($self) = @_; return [ map $self->get_region($_), 0..($self->region_count-1) ]; } sub make_perimeters { my $self = shift; Slic3r::debugf "Making perimeters for layer %d\n", $self->id; # keep track of regions whose perimeters we have already generated my %done = (); # region_id => 1 for my $region_id (0..$#{$self->regions}) { next if $done{$region_id}; my $layerm = $self->regions->[$region_id]; $done{$region_id} = 1; # find compatible regions my @layerms = ($layerm); for my $i (($region_id+1)..$#{$self->regions}) { my $config = $self->regions->[$i]->config; my $layerm_config = $layerm->config; if ($config->perimeter_extruder == $layerm_config->perimeter_extruder && $config->perimeters == $layerm_config->perimeters && $config->perimeter_speed == $layerm_config->perimeter_speed && $config->gap_fill_speed == $layerm_config->gap_fill_speed && $config->overhangs == $layerm_config->overhangs && $config->perimeter_extrusion_width == $layerm_config->perimeter_extrusion_width && $config->thin_walls == $layerm_config->thin_walls && $config->external_perimeters_first == $layerm_config->external_perimeters_first) { push @layerms, $self->regions->[$i]; $done{$i} = 1; } } if (@layerms == 1) { # optimization $layerm->fill_surfaces->clear; $layerm->make_perimeters($layerm->slices, $layerm->fill_surfaces); } else { # group slices (surfaces) according to number of extra perimeters my %slices = (); # extra_perimeters => [ surface, surface... ] foreach my $surface (map @{$_->slices}, @layerms) { my $extra = $surface->extra_perimeters; $slices{$extra} ||= []; push @{$slices{$extra}}, $surface; } # merge the surfaces assigned to each group my $new_slices = Slic3r::Surface::Collection->new; foreach my $surfaces (values %slices) { $new_slices->append(Slic3r::Surface->new( surface_type => $surfaces->[0]->surface_type, extra_perimeters => $surfaces->[0]->extra_perimeters, expolygon => $_, )) for @{union_ex([ map $_->p, @$surfaces ], 1)}; } # make perimeters my $fill_surfaces = Slic3r::Surface::Collection->new; $layerm->make_perimeters($new_slices, $fill_surfaces); # assign fill_surfaces to each layer if ($fill_surfaces->count > 0) { foreach my $lm (@layerms) { my $expolygons = intersection_ex( [ map $_->p, @$fill_surfaces ], [ map $_->p, @{$lm->slices} ], ); $lm->fill_surfaces->clear; $lm->fill_surfaces->append(Slic3r::Surface->new( surface_type => $fill_surfaces->[0]->surface_type, extra_perimeters => $fill_surfaces->[0]->extra_perimeters, expolygon => $_, )) for @$expolygons; } } } } } package Slic3r::Layer::Support; our @ISA = qw(Slic3r::Layer); 1; Slic3r-1.2.9/lib/Slic3r/Layer/000077500000000000000000000000001254023100400156325ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Layer/PerimeterGenerator.pm000066400000000000000000000555621254023100400220100ustar00rootroot00000000000000package Slic3r::Layer::PerimeterGenerator; use Moo; use Slic3r::ExtrusionLoop ':roles'; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale chained_path); use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2 offset_ex offset2_ex intersection_ppl diff_ppl); use Slic3r::Surface ':types'; has 'slices' => (is => 'ro', required => 1); # SurfaceCollection has 'lower_slices' => (is => 'ro', required => 0); has 'layer_height' => (is => 'ro', required => 1); has 'layer_id' => (is => 'ro', required => 0, default => sub { -1 }); has 'perimeter_flow' => (is => 'ro', required => 1); has 'ext_perimeter_flow' => (is => 'ro', required => 1); has 'overhang_flow' => (is => 'ro', required => 1); has 'solid_infill_flow' => (is => 'ro', required => 1); has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new }); has 'object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new }); has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); has '_lower_slices_p' => (is => 'rw', default => sub { [] }); has '_holes_pt' => (is => 'rw'); has '_ext_mm3_per_mm' => (is => 'rw'); has '_mm3_per_mm' => (is => 'rw'); has '_mm3_per_mm_overhang' => (is => 'rw'); # generated loops will be put here has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); # generated gap fills will be put here has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new }); # generated fill surfaces will be put here has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new }); sub BUILDARGS { my ($class, %args) = @_; if (my $flow = delete $args{flow}) { $args{perimeter_flow} //= $flow; $args{ext_perimeter_flow} //= $flow; $args{overhang_flow} //= $flow; $args{solid_infill_flow} //= $flow; } return { %args }; } sub process { my ($self) = @_; # other perimeters $self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm); my $pwidth = $self->perimeter_flow->scaled_width; my $pspacing = $self->perimeter_flow->scaled_spacing; # external perimeters $self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm); my $ext_pwidth = $self->ext_perimeter_flow->scaled_width; my $ext_pspacing = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow)); # overhang perimeters $self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm); # solid infill my $ispacing = $self->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); # prepare grown lower layer slices for overhang detection if ($self->lower_slices && $self->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->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1); $self->_lower_slices_p( offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2) ); } # we need to process each island separately because we might have different # extra perimeters for each one foreach my $surface (@{$self->slices}) { # detect how many perimeters must be generated for this island my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); $loop_number--; # 0-indexed loops my @gaps = (); # Polygons my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)}; if ($loop_number >= 0) { # no loops = -1 my @contours = (); # depth => [ Polygon, Polygon ... ] my @holes = (); # depth => [ Polygon, Polygon ... ] my @thin_walls = (); # Polylines # we loop one time more than needed in order to find gaps after the last perimeter was applied for my $i (0..($loop_number+1)) { # outer loop is 0 my @offsets = (); if ($i == 0) { # the minimum thickness of a single loop is: # ext_width/2 + ext_spacing/2 + spacing/2 + width/2 if ($self->config->thin_walls) { @offsets = @{offset2( \@last, -(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1), +(0.5*$ext_min_spacing - 1), )}; } else { @offsets = @{offset( \@last, -0.5*$ext_pwidth, )}; } # look for thin walls if ($self->config->thin_walls) { my $diff = diff( \@last, offset(\@offsets, +0.5*$ext_pwidth), 1, # medial axis requires non-overlapping geometry ); # the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width # (actually, something larger than that still may exist due to mitering or other causes) my $min_width = $ext_pwidth / 2; @thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)}; # the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop @thin_walls = grep $_->length > $ext_pwidth*2, map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls; Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "medial_axis.svg", no_arrows => 1, #expolygons => \@expp, polylines => \@thin_walls, ); } } } else { my $distance = ($i == 1) ? $ext_pspacing : $pspacing; if ($self->config->thin_walls) { @offsets = @{offset2( \@last, -($distance + 0.5*$min_spacing - 1), +(0.5*$min_spacing - 1), )}; } else { @offsets = @{offset( \@last, -$distance, )}; } # look for gaps if ($self->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*$distance), offset(\@offsets, +0.5*$distance + 10), # safety offset ); push @gaps, map $_->clone, map @$_, grep abs($_->area) >= $gap_area_threshold, @$diff; } } last if !@offsets; last if $i > $loop_number; # we were only looking for gaps this time @last = @offsets; $contours[$i] = []; $holes[$i] = []; foreach my $polygon (@offsets) { my $loop = Slic3r::Layer::PerimeterGenerator::Loop->new( polygon => $polygon, is_contour => $polygon->is_counter_clockwise, depth => $i, ); if ($loop->is_contour) { push @{$contours[$i]}, $loop; } else { push @{$holes[$i]}, $loop; } } } # nest loops: holes first for my $d (0..$loop_number) { # loop through all holes having depth $d LOOP: for (my $i = 0; $i <= $#{$holes[$d]}; ++$i) { my $loop = $holes[$d][$i]; # find the hole loop that contains this one, if any for my $t (($d+1)..$loop_number) { for (my $j = 0; $j <= $#{$holes[$t]}; ++$j) { my $candidate_parent = $holes[$t][$j]; if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { $candidate_parent->add_child($loop); splice @{$holes[$d]}, $i, 1; --$i; next LOOP; } } } # if no hole contains this hole, find the contour loop that contains it for my $t (reverse 0..$loop_number) { for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) { my $candidate_parent = $contours[$t][$j]; if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { $candidate_parent->add_child($loop); splice @{$holes[$d]}, $i, 1; --$i; next LOOP; } } } } } # nest contour loops for my $d (reverse 1..$loop_number) { # loop through all contours having depth $d LOOP: for (my $i = 0; $i <= $#{$contours[$d]}; ++$i) { my $loop = $contours[$d][$i]; # find the contour loop that contains it for my $t (reverse 0..($d-1)) { for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) { my $candidate_parent = $contours[$t][$j]; if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) { $candidate_parent->add_child($loop); splice @{$contours[$d]}, $i, 1; --$i; next LOOP; } } } } } # at this point, all loops should be in $contours[0] my @entities = $self->_traverse_loops($contours[0], \@thin_walls); # if brim will be printed, reverse the order of perimeters so that # we continue inwards after having finished the brim # TODO: add test for perimeter order @entities = reverse @entities if $self->config->external_perimeters_first || ($self->layer_id == 0 && $self->print_config->brim_width > 0); # append perimeters for this slice as a collection $self->loops->append(Slic3r::ExtrusionPath::Collection->new(@entities)) if @entities; } # fill gaps if (@gaps) { if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "gaps.svg", expolygons => union_ex(\@gaps), ); } # where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth # where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth my @gap_sizes = ( [ $pwidth, 2*$pspacing, unscale 2*$pwidth ], [ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ], ); foreach my $gap_size (@gap_sizes) { my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps); $self->gap_fill->append($_) for @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)}; @gaps = @{diff(\@gaps, \@filled)}; # prevent more gap fill here } } # 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 $inset = 0; if ($loop_number == 0) { # one loop $inset += $ext_pspacing/2; } elsif ($loop_number > 0) { # two or more loops $inset += $pspacing/2; } # only apply infill overlap if we actually have one perimeter $inset -= $self->config->get_abs_value_over('infill_overlap', $inset + $ispacing/2) if $inset > 0; my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); $self->fill_surfaces->append($_) for 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)} ], -$inset -$min_perimeter_infill_spacing/2, +$min_perimeter_infill_spacing/2, )}; } } sub _traverse_loops { my ($self, $loops, $thin_walls) = @_; # loops is an arrayref of ::Loop objects # turn each one into an ExtrusionLoop object my $coll = Slic3r::ExtrusionPath::Collection->new; foreach my $loop (@$loops) { my $is_external = $loop->is_external; my ($role, $loop_role); if ($is_external) { $role = EXTR_ROLE_EXTERNAL_PERIMETER; } else { $role = EXTR_ROLE_PERIMETER; } if ($loop->is_internal_contour) { # Note that we set loop role to ContourInternalPerimeter # also when loop is both internal and external (i.e. # there's only one contour loop). $loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER; } else { $loop_role = EXTR_ROLE_PERIMETER; } # detect overhanging/bridging perimeters my @paths = (); if ($self->config->overhangs && $self->layer_id > 0 && !($self->object_config->support_material && $self->object_config->support_material_contact_distance == 0)) { # get non-overhang paths by intersecting this loop with the grown lower slices foreach my $polyline (@{ intersection_ppl([ $loop->polygon ], $self->_lower_slices_p) }) { push @paths, Slic3r::ExtrusionPath->new( polyline => $polyline, role => $role, mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), height => $self->layer_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([ $loop->polygon ], $self->_lower_slices_p) }) { push @paths, Slic3r::ExtrusionPath->new( polyline => $polyline, role => EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => $self->_mm3_per_mm_overhang, width => $self->overhang_flow->width, height => $self->overhang_flow->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); # temporary collection @paths = map $_->clone, @{$collection->chained_path(0)}; } else { push @paths, Slic3r::ExtrusionPath->new( polyline => $loop->polygon->split_at_first_point, role => $role, mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm), width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width), height => $self->layer_height, ); } my $eloop = Slic3r::ExtrusionLoop->new_from_paths(@paths); $eloop->role($loop_role); $coll->append($eloop); } # append thin walls to the nearest-neighbor search (only for first iteration) if (@$thin_walls) { foreach my $polyline (@$thin_walls) { $coll->append(Slic3r::ExtrusionPath->new( polyline => $polyline, role => EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => $self->_mm3_per_mm, width => $self->perimeter_flow->width, height => $self->layer_height, )); } @$thin_walls = (); } # sort entities my $sorted_coll = $coll->chained_path_indices(0); my @indices = @{$sorted_coll->orig_indices}; # traverse children my @entities = (); for my $i (0..$#indices) { my $idx = $indices[$i]; if ($idx > $#$loops) { # this is a thin wall # let's get it from the sorted collection as it might have been reversed push @entities, $sorted_coll->[$i]->clone; } else { my $loop = $loops->[$idx]; my $eloop = $coll->[$idx]->clone; my @children = $self->_traverse_loops($loop->children, $thin_walls); if ($loop->is_contour) { $eloop->make_counter_clockwise; push @entities, @children, $eloop; } else { $eloop->make_clockwise; push @entities, $eloop, @children; } } } return @entities; } sub _fill_gaps { my ($self, $min, $max, $w, $gaps) = @_; $min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE); my $this = diff_ex( offset2($gaps, -$min/2, +$min/2), offset2($gaps, -$max/2, +$max/2), 1, ); my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this; return if !@polylines; Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w if @$this; #my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); my $flow = Slic3r::Flow->new( width => $w, height => $self->layer_height, nozzle_diameter => $self->solid_infill_flow->nozzle_diameter, ); my %path_args = ( role => EXTR_ROLE_GAPFILL, mm3_per_mm => $flow->mm3_per_mm, width => $flow->width, height => $self->layer_height, ); my @entities = (); foreach my $polyline (@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; if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) { # since medial_axis() now returns only Polyline objects, detect loops here push @entities, my $loop = Slic3r::ExtrusionLoop->new; $loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args)); } else { push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args); } } return @entities; } package Slic3r::Layer::PerimeterGenerator::Loop; use Moo; has 'polygon' => (is => 'ro', required => 1); has 'is_contour' => (is => 'ro', required => 1); has 'depth' => (is => 'ro', required => 1); has 'children' => (is => 'ro', default => sub { [] }); use List::Util qw(first); sub add_child { my ($self, $child) = @_; push @{$self->children}, $child; } sub is_external { my ($self) = @_; return $self->depth == 0; } sub is_internal_contour { my ($self) = @_; if ($self->is_contour) { # an internal contour is a contour containing no other contours return !defined first { $_->is_contour } @{$self->children}; } return 0; } 1; Slic3r-1.2.9/lib/Slic3r/Layer/Region.pm000066400000000000000000000116421254023100400174170ustar00rootroot00000000000000package Slic3r::Layer::Region; use strict; use warnings; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(scale); use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex ); use Slic3r::Surface ':types'; # 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 make_perimeters { my ($self, $slices, $fill_surfaces) = @_; $self->perimeters->clear; $self->thin_fills->clear; my $generator = Slic3r::Layer::PerimeterGenerator->new( # input: config => $self->config, object_config => $self->layer->object->config, print_config => $self->layer->print->config, layer_height => $self->height, layer_id => $self->layer->id, slices => $slices, lower_slices => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef, perimeter_flow => $self->flow(FLOW_ROLE_PERIMETER), ext_perimeter_flow => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER), overhang_flow => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object), solid_infill_flow => $self->flow(FLOW_ROLE_SOLID_INFILL), # output: loops => $self->perimeters, gap_fill => $self->thin_fills, fill_surfaces => $fill_surfaces, ); $generator->process; } sub process_external_surfaces { my ($self, $lower_layer) = @_; my @surfaces = @{$self->fill_surfaces}; my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN; my @bottom = (); foreach my $surface (grep $_->is_bottom, @surfaces) { my $grown = $surface->expolygon->offset_ex(+$margin); # detect bridge direction before merging grown surfaces otherwise adjacent bridges # 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; if ($lower_layer) { my $bridge_detector = Slic3r::BridgeDetector->new( $surface->expolygon, $lower_layer->slices, $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width, ); Slic3r::debugf "Processing bridge at layer %d:\n", $self->id; $bridge_detector->detect_angle; $angle = $bridge_detector->angle; if (defined $angle && $self->object->config->support_material) { $self->bridged->append(Slic3r::ExPolygon->new($_)) for @{ $bridge_detector->coverage_by_angle($angle) }; $self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges }; } } push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown; } my @top = (); foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @surfaces) { # give priority to bottom surfaces my $grown = diff_ex( $surface->expolygon->offset(+$margin), [ map $_->p, @bottom ], ); push @top, map $surface->clone(expolygon => $_), @$grown; } # if we're slicing with no infill, we can't extend external surfaces # over non-existent infill my @fill_boundaries = $self->config->fill_density > 0 ? @surfaces : grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; # intersect the grown surfaces with the actual fill boundaries my @new_surfaces = (); foreach my $group (@{Slic3r::Surface::Collection->new(@top, @bottom)->group}) { push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{intersection_ex( [ map $_->p, @$group ], [ map $_->p, @fill_boundaries ], 1, # to ensure adjacent expolygons are unified )}; } # subtract the new top surfaces from the other non-top surfaces and re-add them my @other = grep $_->surface_type != S_TYPE_TOP && !$_->is_bottom, @surfaces; foreach my $group (@{Slic3r::Surface::Collection->new(@other)->group}) { push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( [ map $_->p, @$group ], [ map $_->p, @new_surfaces ], )}; } $self->fill_surfaces->clear; $self->fill_surfaces->append($_) for @new_surfaces; } 1; Slic3r-1.2.9/lib/Slic3r/Line.pm000066400000000000000000000005571254023100400160120ustar00rootroot00000000000000package Slic3r::Line; use strict; use warnings; # a line is a two-points line use parent 'Slic3r::Polyline'; sub intersection { my $self = shift; my ($line, $require_crossing) = @_; return Slic3r::Geometry::line_intersection($self, $line, $require_crossing); } sub grow { my $self = shift; return Slic3r::Polyline->new(@$self)->grow(@_); } 1; Slic3r-1.2.9/lib/Slic3r/Model.pm000066400000000000000000000227441254023100400161650ustar00rootroot00000000000000package Slic3r::Model; use List::Util qw(first max); use Slic3r::Geometry qw(X Y Z move_points); sub read_from_file { my $class = shift; my ($input_file) = @_; my $model = $input_file =~ /\.stl$/i ? Slic3r::Format::STL->read_file($input_file) : $input_file =~ /\.obj$/i ? Slic3r::Format::OBJ->read_file($input_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"; die "The supplied file couldn't be read because it's empty.\n" if $model->objects_count == 0; $_->set_input_file($input_file) for @{$model->objects}; return $model; } sub merge { my $class = shift; my @models = @_; my $new_model = ref($class) ? $class : $class->new; $new_model->add_object($_) for map @{$_->objects}, @models; return $new_model; } sub add_object { my $self = shift; if (@_ == 1) { # we have a Model::Object my ($object) = @_; return $self->_add_object_clone($object); } else { my (%args) = @_; my $new_object = $self->_add_object; $new_object->set_name($args{name}) if defined $args{name}; $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; } } sub set_material { my $self = shift; my ($material_id, $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->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 => Slic3r::Pointf->new( ($size->[X] + $distance) * ($x_copy-1), ($size->[Y] + $distance) * ($y_copy-1), ), ); } } } # this will append more instances to each object # and then automatically rearrange everything sub duplicate_objects { my ($self, $copies_num, $distance, $bb) = @_; foreach my $object (@{$self->objects}) { my @instances = @{$object->instances}; foreach my $instance (@instances) { $object->add_instance($instance) for 2..$copies_num; } } $self->arrange_objects($distance, $bb); } # arrange objects preserving their instance count # but altering their instance positions sub arrange_objects { my ($self, $distance, $bb) = @_; # get the (transformed) size of each instance so that we take # into account their different transformations when packing my @instance_sizes = (); foreach my $object (@{$self->objects}) { push @instance_sizes, map $object->instance_bounding_box($_)->size, 0..$#{$object->instances}; } my @positions = $self->_arrange(\@instance_sizes, $distance, $bb); foreach my $object (@{$self->objects}) { $_->set_offset(Slic3r::Pointf->new(@{shift @positions})) for @{$object->instances}; $object->update_bounding_box; } } # duplicate the entire model preserving instance relative positions sub duplicate { my ($self, $copies_num, $distance, $bb) = @_; my $model_size = $self->bounding_box->size; my @positions = $self->_arrange([ map $model_size, 2..$copies_num ], $distance, $bb); # note that this will leave the object count unaltered foreach my $object (@{$self->objects}) { my @instances = @{$object->instances}; # store separately to avoid recursion from add_instance() below foreach my $instance (@instances) { foreach my $pos (@positions) { $object->add_instance( offset => Slic3r::Pointf->new($instance->offset->[X] + $pos->[X], $instance->offset->[Y] + $pos->[Y]), rotation => $instance->rotation, scaling_factor => $instance->scaling_factor, ); } } $object->update_bounding_box; } } sub _arrange { my ($self, $sizes, $distance, $bb) = @_; $bb //= Slic3r::Geometry::BoundingBoxf->new; # we supply unscaled data to arrange() return @{Slic3r::Geometry::arrange( scalar(@$sizes), # number of parts Slic3r::Pointf->new( max(map $_->x, @$sizes), # cell width max(map $_->y, @$sizes), # cell height , ), $distance, # distance between cells $bb, # bounding box of the area to fill (can be undef) )}; } sub print_info { my $self = shift; $_->print_info for @{$self->objects}; } sub get_material_name { my $self = shift; my ($material_id) = @_; my $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; sub apply { my ($self, $attributes) = @_; $self->set_attribute($_, $attributes{$_}) for keys %$attributes; } package Slic3r::Model::Object; use File::Basename qw(basename); use List::Util qw(first sum); 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 = $self->_add_volume_clone($volume); if ($volume->material_id ne '') { # merge material attributes and config (should we rename materials in case of duplicates?) if (my $material = $volume->object->model->get_material($volume->material_id)) { my %attributes = %{ $material->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); } } } else { my %args = @_; $new_volume = $self->_add_volume($args{mesh}); $new_volume->set_name($args{name}) if defined $args{name}; $new_volume->set_material_id($args{material_id}) if defined $args{material_id}; $new_volume->set_modifier($args{modifier}) if defined $args{modifier}; $new_volume->config->apply($args{config}) if defined $args{config}; } if ($new_volume->material_id ne '' && !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); } $self->invalidate_bounding_box; return $new_volume; } sub add_instance { my $self = shift; my %params = @_; 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 mesh_stats { my $self = shift; # TODO: sum values from all volumes return $self->volumes->[0]->mesh->stats; } sub print_info { my $self = shift; printf "Info about %s:\n", basename($self->input_file); printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->raw_mesh->bounding_box->size}; if (my $stats = $self->mesh_stats) { printf " number of facets: %d\n", $stats->{number_of_facets}; printf " number of shells: %d\n", $stats->{number_of_parts}; printf " volume: %.3f\n", $stats->{volume}; if ($self->needed_repair) { printf " needed repair: yes\n"; printf " degenerate facets: %d\n", $stats->{degenerate_facets}; printf " edges fixed: %d\n", $stats->{edges_fixed}; printf " facets removed: %d\n", $stats->{facets_removed}; printf " facets added: %d\n", $stats->{facets_added}; printf " facets reversed: %d\n", $stats->{facets_reversed}; printf " backwards edges: %d\n", $stats->{backwards_edges}; } else { printf " needed repair: no\n"; } } else { printf " number of facets: %d\n", scalar(map @{$_->facets}, grep !$_->modifier, @{$self->volumes}); } } 1; Slic3r-1.2.9/lib/Slic3r/Point.pm000066400000000000000000000010501254023100400162010ustar00rootroot00000000000000package Slic3r::Point; use strict; use warnings; sub new_scale { my $class = shift; return $class->new(map Slic3r::Geometry::scale($_), @_); } sub dump_perl { my $self = shift; return sprintf "[%s,%s]", @$self; } package Slic3r::Pointf; use strict; use warnings; sub new_unscale { my $class = shift; return $class->new(map Slic3r::Geometry::unscale($_), @_); } package Slic3r::Pointf3; use strict; use warnings; sub new_unscale { my $class = shift; return $class->new(map Slic3r::Geometry::unscale($_), @_); } 1; Slic3r-1.2.9/lib/Slic3r/Polygon.pm000066400000000000000000000017201254023100400165430ustar00rootroot00000000000000package Slic3r::Polygon; use strict; use warnings; # a polygon is a closed polyline. use parent 'Slic3r::Polyline'; use Slic3r::Geometry qw(PI); sub grow { my $self = shift; return $self->split_at_first_point->grow(@_); } # this method subdivides the polygon segments to that no one of them # is longer than the length provided sub subdivide { my $self = shift; my ($max_length) = @_; my @points = @$self; push @points, $points[0]; # append first point as this is a polygon my @new_points = shift @points; while (@points) { while ($new_points[-1]->distance_to($points[0]) > $max_length) { push @new_points, map Slic3r::Point->new(@$_), Slic3r::Geometry::point_along_segment($new_points[-1], $points[0], $max_length); } push @new_points, shift @points; } pop @new_points; # remove last point as it coincides with first one return Slic3r::Polygon->new(@new_points); } 1;Slic3r-1.2.9/lib/Slic3r/Polyline.pm000066400000000000000000000006431254023100400167120ustar00rootroot00000000000000package Slic3r::Polyline; use strict; use warnings; use Slic3r::Geometry qw(X Y); sub new_scale { my $class = shift; my @points = map { ref($_) eq 'Slic3r::Point' ? $_->pp : $_ } @_; return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points); } sub dump_perl { my $self = shift; return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self; } 1; Slic3r-1.2.9/lib/Slic3r/Print.pm000066400000000000000000000446751254023100400162300ustar00rootroot00000000000000package Slic3r::Print; use strict; use warnings; use File::Basename qw(basename fileparse); use File::Spec; 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 convex_hull); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset offset2 union union_pt_chained JT_ROUND JT_SQUARE); use Slic3r::Print::State ':steps'; our $status_cb; 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; } sub set_status_cb { my ($class, $cb) = @_; $status_cb = $cb; } sub status_cb { return $status_cb // sub {}; } # this value is not supposed to be compared with $layer->id # since they have different semantics sub total_layer_count { my $self = shift; return max(map $_->total_layer_count, @{$self->objects}); } sub size { my $self = shift; return $self->bounding_box->size; } sub process { my ($self) = @_; $_->make_perimeters for @{$self->objects}; $_->infill for @{$self->objects}; $_->generate_support_material for @{$self->objects}; $self->make_skirt; $self->make_brim; # must come after make_skirt # time to make some statistics if (0) { eval "use Devel::Size"; print "MEMORY USAGE:\n"; printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024; printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024; printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024; printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024; printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024; printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024; } if (0) { eval "use Slic3r::Test::SectionCut"; Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg"); } } sub export_gcode { my $self = shift; my %params = @_; # prerequisites $self->process; # output everything to a G-code file my $output_file = $self->expanded_output_filepath($params{output_file}); $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : "")); $self->write_gcode($params{output_fh} || $output_file); # run post-processing scripts if (@{$self->config->post_process}) { $self->status_cb->(95, "Running post-processing scripts"); $self->config->setenv; for my $script (@{$self->config->post_process}) { Slic3r::debugf " '%s' '%s'\n", $script, $output_file; # -x doesn't return true on Windows except for .exe files if (($^O eq 'MSWin32') ? !(-e $script) : !(-x $script)) { die "The configured post-processing script is not executable: check permissions. ($script)\n"; } system($script, $output_file); } } } sub export_svg { my $self = shift; my %params = @_; $_->slice for @{$self->objects}; my $fh = $params{output_fh}; if (!$fh) { my $output_file = $self->expanded_output_filepath($params{output_file}); $output_file =~ s/\.gcode$/.svg/i; Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; print "Exporting to $output_file..." unless $params{quiet}; } my $print_bb = $self->bounding_box; my $print_size = $print_bb->size; print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]); EOF my $print_polygon = sub { my ($polygon, $type) = @_; printf $fh qq{ \n}, $type, (join ' ', map { join ',', map unscale $_, @$_ } @$polygon), ($type eq 'contour' ? 'white' : 'black'); }; my @layers = sort { $a->print_z <=> $b->print_z } map { @{$_->layers}, @{$_->support_layers} } @{$self->objects}; my $layer_id = -1; my @previous_layer_slices = (); for my $layer (@layers) { $layer_id++; if ($layer->slice_z == -1) { printf $fh qq{ \n}, $layer_id; } else { printf $fh qq{ \n}, $layer_id, unscale($layer->slice_z); } my @current_layer_slices = (); # sort slices so that the outermost ones come first my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices}; foreach my $copy (@{$layer->object->_shifted_copies}) { foreach my $slice (@slices) { my $expolygon = $slice->clone; $expolygon->translate(@$copy); $expolygon->translate(-$print_bb->x_min, -$print_bb->y_min); $print_polygon->($expolygon->contour, 'contour'); $print_polygon->($_, 'hole') for @{$expolygon->holes}; push @current_layer_slices, $expolygon; } } # generate support material if ($self->has_support_material && $layer->id > 0) { my (@supported_slices, @unsupported_slices) = (); foreach my $expolygon (@current_layer_slices) { my $intersection = intersection_ex( [ map @$_, @previous_layer_slices ], [ @$expolygon ], ); @$intersection ? push @supported_slices, $expolygon : push @unsupported_slices, $expolygon; } my @supported_points = map @$_, @$_, @supported_slices; foreach my $expolygon (@unsupported_slices) { # look for the nearest point to this island among all # supported points my $contour = $expolygon->contour; my $support_point = $contour->first_point->nearest_point(\@supported_points) or next; my $anchor_point = $support_point->nearest_point([ @$contour ]); printf $fh qq{ \n}, map @$_, $support_point, $anchor_point; } } print $fh qq{ \n}; @previous_layer_slices = @current_layer_slices; } print $fh "\n"; close $fh; print "Done.\n" unless $params{quiet}; } sub make_skirt { my $self = shift; # prerequisites $_->make_perimeters for @{$self->objects}; $_->infill for @{$self->objects}; $_->generate_support_material for @{$self->objects}; return if $self->step_done(STEP_SKIRT); $self->set_step_started(STEP_SKIRT); # since this method must be idempotent, we clear skirt paths *before* # checking whether we need to generate them $self->skirt->clear; if (!$self->has_skirt) { $self->set_step_done(STEP_SKIRT); return; } $self->status_cb->(88, "Generating skirt"); # 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 # of the highest layer involved. # Note that unless has_infinite_skirt() == true # the actual skirt might not reach this $skirt_height_z value since the print # order of objects on each layer is not guaranteed and will not generally # include the thickest object first. It is just guaranteed that a skirt is # prepended to the first 'n' layers (with 'n' = skirt_height). # $skirt_height_z in this case is the highest possible skirt height for safety. my $skirt_height_z = -1; foreach my $object (@{$self->objects}) { my $skirt_height = $self->has_infinite_skirt ? $object->layer_count : min($self->config->skirt_height, $object->layer_count); my $highest_layer = $object->get_layer($skirt_height - 1); $skirt_height_z = max($skirt_height_z, $highest_layer->print_z); } # collect points from all layers contained in skirt height my @points = (); foreach my $object (@{$self->objects}) { my @object_points = (); # get object layers up to $skirt_height_z foreach my $layer (@{$object->layers}) { last if $layer->print_z > $skirt_height_z; push @object_points, map @$_, map @$_, @{$layer->slices}; } # get support layers up to $skirt_height_z foreach my $layer (@{$object->support_layers}) { last if $layer->print_z > $skirt_height_z; push @object_points, map @{$_->polyline}, @{$layer->support_fills} if $layer->support_fills; push @object_points, map @{$_->polyline}, @{$layer->support_interface_fills} if $layer->support_interface_fills; } # repeat points for each object copy foreach my $copy (@{$object->_shifted_copies}) { my @copy_points = map $_->clone, @object_points; $_->translate(@$copy) for @copy_points; push @points, @copy_points; } } return if @points < 3; # at least three points required for a convex hull # find out convex hull my $convex_hull = convex_hull(\@points); my @extruded_length = (); # for each extruder # skirt may be printed on several layers, having distinct layer heights, # but loops must be aligned so can't vary width/spacing # TODO: use each extruder's own flow my $first_layer_height = $self->skirt_first_layer_height; my $flow = $self->skirt_flow; my $spacing = $flow->spacing; my $mm3_per_mm = $flow->mm3_per_mm; my @extruders_e_per_mm = (); my $extruder_idx = 0; my $skirts = $self->config->skirts; $skirts ||= 1 if $self->has_infinite_skirt; # draw outlines from outside to inside # loop while we have less skirts than required or any extruder hasn't reached the min length if any my $distance = scale max($self->config->skirt_distance, $self->config->brim_width); for (my $i = $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_from_paths( Slic3r::ExtrusionPath->new( polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point, role => EXTR_ROLE_SKIRT, mm3_per_mm => $mm3_per_mm, # this will be overridden at G-code export time width => $flow->width, height => $first_layer_height, # this will be overridden at G-code export time ), )); if ($self->config->min_skirt_length > 0) { $extruded_length[$extruder_idx] ||= 0; if (!$extruders_e_per_mm[$extruder_idx]) { my $config = Slic3r::Config::GCode->new; $config->apply_print_config($self->config); my $extruder = Slic3r::Extruder->new($extruder_idx, $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]; $i++ if defined first { ($extruded_length[$_] // 0) < $self->config->min_skirt_length } 0 .. $#{$self->extruders}; if ($extruded_length[$extruder_idx] >= $self->config->min_skirt_length) { if ($extruder_idx < $#{$self->extruders}) { $extruder_idx++; next; } } } } $self->skirt->reverse; $self->set_step_done(STEP_SKIRT); } sub make_brim { my $self = shift; # prerequisites $_->make_perimeters for @{$self->objects}; $_->infill for @{$self->objects}; $_->generate_support_material for @{$self->objects}; $self->make_skirt; return if $self->step_done(STEP_BRIM); $self->set_step_started(STEP_BRIM); # since this method must be idempotent, we clear brim paths *before* # checking whether we need to generate them $self->brim->clear; if ($self->config->brim_width == 0) { $self->set_step_done(STEP_BRIM); return; } $self->status_cb->(88, "Generating brim"); # brim is only printed on first layer and uses perimeter extruder my $first_layer_height = $self->skirt_first_layer_height; my $flow = $self->brim_flow; 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->object_count - 1)) { my $object = $self->objects->[$obj_idx]; my $layer0 = $object->get_layer(0); my @object_islands = ( (map $_->contour, @{$layer0->slices}), ); if (@{ $object->support_layers }) { my $support_layer0 = $object->support_layers->[0]; push @object_islands, (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_fills}) if $support_layer0->support_fills; push @object_islands, (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_interface_fills}) if $support_layer0->support_interface_fills; } foreach my $copy (@{$object->_shifted_copies}) { push @islands, map { $_->translate(@$copy); $_ } map $_->clone, @object_islands; } } my @loops = (); my $num_loops = sprintf "%.0f", $self->config->brim_width / $flow->width; for my $i (reverse 1 .. $num_loops) { # JT_SQUARE ensures no vertex is outside the given offset distance # -0.5 because islands are not represented by their centerlines # (first offset more, then step back - reverse order than the one used for # perimeters because here we're offsetting outwards) 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_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)}); $self->set_step_done(STEP_BRIM); } sub write_gcode { my $self = shift; my ($file) = @_; # open output gcode file if we weren't supplied a file-handle my $fh; if (ref $file eq 'IO::Scalar') { $fh = $file; } else { Slic3r::open(\$fh, ">", $file) or die "Failed to open $file for writing\n"; # enable UTF-8 output since user might have entered Unicode characters in fields like notes binmode $fh, ':utf8'; } my $exporter = Slic3r::Print::GCode->new( print => $self, fh => $fh, ); $exporter->export; # close our gcode file close $fh; } # this method will return the supplied input file path after expanding its # format variables with their values sub expanded_output_filepath { my $self = shift; my ($path) = @_; return undef if !@{$self->objects}; my $input_file = first { defined $_ } map $_->model_object->input_file, @{$self->objects}; return undef if !defined $input_file; my $filename = my $filename_base = basename($input_file); $filename_base =~ s/\.[^.]+$//; # without suffix # set filename in placeholder parser so that it's available also in custom G-code $self->placeholder_parser->set(input_filename => $filename); $self->placeholder_parser->set(input_filename_base => $filename_base); # set other variables from model object $self->placeholder_parser->set_multiple( scale => [ map $_->model_object->instances->[0]->scaling_factor * 100 . "%", @{$self->objects} ], ); if ($path && -d $path) { # if output path is an existing directory, we take that and append # the specified filename format $path = File::Spec->join($path, $self->config->output_filename_format); } elsif (!$path) { # if no explicit output file was defined, we take the input # file directory and append the specified filename format $path = (fileparse($input_file))[1] . $self->config->output_filename_format; } else { # 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); } # This method assigns extruders to the volumes having a material # but not having extruders set in the volume config. sub auto_assign_extruders { my ($self, $model_object) = @_; # only assign extruders if object has more than one volume return if @{$model_object->volumes} == 1; my $extruders = scalar @{ $self->config->nozzle_diameter }; foreach my $i (0..$#{$model_object->volumes}) { my $volume = $model_object->volumes->[$i]; if ($volume->material_id ne '') { my $extruder_id = $i + 1; $volume->config->set_ifndef('extruder', $extruder_id); } } } 1; Slic3r-1.2.9/lib/Slic3r/Print/000077500000000000000000000000001254023100400156525ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Print/GCode.pm000066400000000000000000000752501254023100400172020ustar00rootroot00000000000000package Slic3r::Print::GCode; use Moo; has 'print' => (is => 'ro', required => 1, handles => [qw(objects placeholder_parser config)]); has 'fh' => (is => 'ro', required => 1); has '_gcodegen' => (is => 'rw'); has '_cooling_buffer' => (is => 'rw'); has '_spiral_vase' => (is => 'rw'); has '_vibration_limit' => (is => 'rw'); has '_arc_fitting' => (is => 'rw'); has '_pressure_regulator' => (is => 'rw'); has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 has '_brim_done' => (is => 'rw'); has '_second_layer_things_done' => (is => 'rw'); has '_last_obj_copy' => (is => 'rw'); use List::Util qw(first sum min max); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull); use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset); sub BUILD { my ($self) = @_; { # 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( placeholder_parser => $self->placeholder_parser, layer_count => $layer_count, enable_cooling_markers => 1, ); $self->_gcodegen($gcodegen); $gcodegen->apply_print_config($self->config); $gcodegen->set_extruders($self->print->extruders); # initialize autospeed { # get the minimum cross-section used in the print my @mm3_per_mm = (); foreach my $object (@{$self->print->objects}) { foreach my $region_id (0..$#{$self->print->regions}) { my $region = $self->print->get_region($region_id); foreach my $layer (@{$object->layers}) { my $layerm = $layer->get_region($region_id); if ($region->config->get_abs_value('perimeter_speed') == 0 || $region->config->get_abs_value('small_perimeter_speed') == 0 || $region->config->get_abs_value('external_perimeter_speed') == 0 || $region->config->get_abs_value('bridge_speed') == 0) { push @mm3_per_mm, $layerm->perimeters->min_mm3_per_mm; } if ($region->config->get_abs_value('infill_speed') == 0 || $region->config->get_abs_value('solid_infill_speed') == 0 || $region->config->get_abs_value('top_solid_infill_speed') == 0 || $region->config->get_abs_value('bridge_speed') == 0) { push @mm3_per_mm, $layerm->fills->min_mm3_per_mm; } } } if ($object->config->get_abs_value('support_material_speed') == 0 || $object->config->get_abs_value('support_material_interface_speed') == 0) { foreach my $layer (@{$object->support_layers}) { push @mm3_per_mm, $layer->support_fills->min_mm3_per_mm; push @mm3_per_mm, $layer->support_interface_fills->min_mm3_per_mm; } } } @mm3_per_mm = grep $_ != 0, @mm3_per_mm; if (@mm3_per_mm) { my $min_mm3_per_mm = min(@mm3_per_mm); # In order to honor max_print_speed we need to find a target volumetric # speed that we can use throughout the print. So we define this target # volumetric speed as the volumetric speed produced by printing the # smallest cross-section at the maximum speed: any larger cross-section # will need slower feedrates. my $volumetric_speed = $min_mm3_per_mm * $self->config->max_print_speed; # limit such volumetric speed with max_volumetric_speed if set if ($self->config->max_volumetric_speed > 0) { $volumetric_speed = min( $volumetric_speed, $self->config->max_volumetric_speed, ); } $gcodegen->volumetric_speed($volumetric_speed); } } } $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new( config => $self->config, gcodegen => $self->_gcodegen, )); $self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config)) if $self->config->spiral_vase; $self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config)) if $self->config->vibration_limit != 0; $self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config)) if $self->config->gcode_arcs; $self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config)) if $self->config->pressure_advance > 0; } sub export { my ($self) = @_; my $fh = $self->fh; my $gcodegen = $self->_gcodegen; # write some information my @lt = localtime; printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n", $lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0]; print $fh "; $_\n" foreach split /\R/, $self->config->notes; print $fh "\n" if $self->config->notes; my $first_object = $self->objects->[0]; my $layer_height = $first_object->config->layer_height; for my $region_id (0..$#{$self->print->regions}) { my $region = $self->print->regions->[$region_id]; printf $fh "; external perimeters extrusion width = %.2fmm\n", $region->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width; printf $fh "; perimeters extrusion width = %.2fmm\n", $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width; printf $fh "; infill extrusion width = %.2fmm\n", $region->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object)->width; printf $fh "; solid infill extrusion width = %.2fmm\n", $region->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width; printf $fh "; top infill extrusion width = %.2fmm\n", $region->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width; printf $fh "; support material extrusion width = %.2fmm\n", $self->objects->[0]->support_material_flow->width if $self->print->has_support_material; printf $fh "; first layer extrusion width = %.2fmm\n", $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width if $region->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; print $fh $gcodegen->writer->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers; # set bed temperature if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) { printf $fh $gcodegen->writer->set_bed_temperature($temp, 1); } # set extruder(s) temperature before and after start G-code $self->_print_first_layer_temperature(0); printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode); $self->_print_first_layer_temperature(1); # set other general things print $fh $gcodegen->preamble; # initialize a motion planner for object-to-object travel moves if ($self->config->avoid_crossing_perimeters) { my $distance_from_objects = scale 1; # compute the offsetted convex hull for each object and repeat it for each copy. my @islands_p = (); foreach my $object (@{$self->objects}) { # compute the convex hull of the entire object my $convex_hull = convex_hull([ map @{$_->contour}, map @{$_->slices}, @{$object->layers}, ]); # discard objects only containing thin walls (offset would fail on an empty polygon) next if !@$convex_hull; # grow convex hull by the wanted clearance my @obj_islands_p = @{offset([$convex_hull], $distance_from_objects, 1, JT_SQUARE)}; # translate convex hull for each object copy and append it to the islands array foreach my $copy (@{ $object->_shifted_copies }) { my @copy_islands_p = map $_->clone, @obj_islands_p; $_->translate(@$copy) for @copy_islands_p; push @islands_p, @copy_islands_p; } } $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p)); } # calculate wiping points if needed if ($self->config->ooze_prevention) { my @skirt_points = map @$_, map @$_, @{$self->print->skirt}; if (@skirt_points) { my $outer_skirt = convex_hull(\@skirt_points); my @skirts = (); foreach my $extruder_id (@{$self->print->extruders}) { my $extruder_offset = $self->config->get_at('extruder_offset', $extruder_id); push @skirts, my $s = $outer_skirt->clone; $s->translate(-scale($extruder_offset->x), -scale($extruder_offset->y)); #) } my $convex_hull = convex_hull([ map @$_, @skirts ]); $gcodegen->ooze_prevention->enable(1); $gcodegen->ooze_prevention->standby_points( [ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ] ); if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "ooze_prevention.svg", red_polygons => \@skirts, polygons => [$outer_skirt], points => $gcodegen->ooze_prevention->standby_points, ); } } } # set initial extruder only after custom start G-code print $fh $gcodegen->set_extruder($self->print->extruders->[0]); # do all objects for each layer 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->print->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 # no collision happens hopefully. if ($finished_objects > 0) { $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); $gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer $gcodegen->avoid_crossing_perimeters->use_external_mp_once(1); print $fh $gcodegen->retract; print $fh $gcodegen->travel_to( Slic3r::Point->new(0,0), undef, 'move to origin position for next object', ); $gcodegen->enable_cooling_markers(1); # disable motion planner when traveling to first object point $gcodegen->avoid_crossing_perimeters->disable_once(1); } 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 # another one, set first layer temperatures. this happens before the Z move # is triggered, so machine has more time to reach such temperatures if ($layer->id == 0 && $finished_objects > 0) { printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature), if $self->config->first_layer_bed_temperature; $self->_print_first_layer_temperature(0); } $self->process_layer($layer, [$copy]); } $self->flush_filters; $finished_objects++; $self->_second_layer_things_done(0); } } } else { # order objects using a nearest neighbor search my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])}; # sort layers by Z my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx foreach my $obj_idx (0 .. ($self->print->object_count - 1)) { my $object = $self->objects->[$obj_idx]; foreach my $layer (@{$object->layers}, @{$object->support_layers}) { $layers{ $layer->print_z } ||= []; $layers{ $layer->print_z }[$obj_idx] ||= []; push @{$layers{ $layer->print_z }[$obj_idx]}, $layer; } } foreach my $print_z (sort { $a <=> $b } keys %layers) { foreach my $obj_idx (@obj_idx) { foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) { $self->process_layer($layer, $layer->object->_shifted_copies); } } } $self->flush_filters; } # write end commands to file print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully print $fh $gcodegen->writer->set_fan(0); printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode); print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100% print $fh $gcodegen->writer->postamble; $self->print->total_used_filament(0); $self->print->total_extruded_volume(0); foreach my $extruder (@{$gcodegen->writer->extruders}) { my $used_filament = $extruder->used_filament; my $extruded_volume = $extruder->extruded_volume; printf $fh "; filament used = %.1fmm (%.1fcm3)\n", $used_filament, $extruded_volume/1000; $self->print->total_used_filament($self->print->total_used_filament + $used_filament); $self->print->total_extruded_volume($self->print->total_extruded_volume + $extruded_volume); } # append full config print $fh "\n"; foreach my $config ($self->print->config, $self->print->default_object_config, $self->print->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); } } } sub _print_first_layer_temperature { my ($self, $wait) = @_; return if $self->config->start_gcode =~ /M(?:109|104)/i; for my $t (@{$self->print->extruders}) { my $temp = $self->config->get_at('first_layer_temperature', $t); $temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention; printf {$self->fh} $self->_gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0; } } sub process_layer { my $self = shift; my ($layer, $object_copies) = @_; 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->_spiral_vase) { $self->_spiral_vase->enable( ($layer->id > 0 || $self->print->config->brim_width == 0) && ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt) && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}) && !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions}) && !defined(first { $_->fills->items_count > 0 } @{$layer->regions}) ); } # if we're going to apply spiralvase to this layer, disable loop clipping $self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable); if (!$self->_second_layer_things_done && $layer->id == 1) { for my $extruder (@{$self->_gcodegen->writer->extruders}) { my $temperature = $self->config->get_at('temperature', $extruder->id); $gcode .= $self->_gcodegen->writer->set_temperature($temperature, 0, $extruder->id) if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id); } $gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature) if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature; $self->_second_layer_things_done(1); } # set new layer - this will change Z and force a retraction if retract_layer_change is enabled $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, { layer_num => $self->_gcodegen->layer_index + 1, layer_z => $layer->print_z, }) . "\n" if $self->print->config->before_layer_gcode; $gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, { layer_num => $self->_gcodegen->layer_index, layer_z => $layer->print_z, }) . "\n" if $self->print->config->layer_gcode; # extrude skirt along raft layers and normal object layers # (not along interlaced support material layers) if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt) && !$self->_skirt_done->{$layer->print_z} && (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) { $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders}; $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]); # skip skirt if we have a large brim if ($layer->id < $self->print->config->skirt_height || $self->print->has_infinite_skirt) { my $skirt_flow = $self->print->skirt_flow; # distribute skirt loops across all extruders my @skirt_loops = @{$self->print->skirt}; for my $i (0 .. $#skirt_loops) { # 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); my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids]; $gcode .= $self->_gcodegen->set_extruder($extruder_id) if $layer->id == 0; # adjust flow according to this layer's layer height my $loop = $skirt_loops[$i]->clone; { my $layer_skirt_flow = $skirt_flow->clone; $layer_skirt_flow->set_height($layer->height); my $mm3_per_mm = $layer_skirt_flow->mm3_per_mm; foreach my $path (@$loop) { $path->height($layer->height); $path->mm3_per_mm($mm3_per_mm); } } $gcode .= $self->_gcodegen->extrude_loop($loop, 'skirt', $object->config->support_material_speed); } } $self->_skirt_done->{$layer->print_z} = 1; $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); # allow a straight travel move to the first object point if this is the first layer # (but don't in next layers) if ($layer->id == 0) { $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); } } # extrude brim if (!$self->_brim_done) { $gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1); $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) for @{$self->print->brim}; $self->_brim_done(1); $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0); # allow a straight travel move to the first object point $self->_gcodegen->avoid_crossing_perimeters->disable_once(1); } for my $copy (@$object_copies) { # when starting a new object, use the external motion planner for the first travel move $self->_gcodegen->avoid_crossing_perimeters->use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy"; $self->_last_obj_copy("$copy"); $self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y)); # extrude support material before other things because it might use a lower Z # and also because we avoid travelling on other things when printing it 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); $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); $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)}; } } # We now define a strategy for building perimeters and fills. The separation # between regions doesn't matter in terms of printing order, as we follow # another logic instead: # - we group all extrusions by extruder so that we minimize toolchanges # - we start from the last used extruder # - for each extruder, we group extrusions by island # - for each island, we extrude perimeters first, unless user set the infill_first # option # group extrusions by extruder and then by island my %by_extruder = (); # extruder_id => [ { perimeters => \@perimeters, infill => \@infill } ] foreach my $region_id (0..($self->print->region_count-1)) { my $layerm = $layer->regions->[$region_id] or next; my $region = $self->print->get_region($region_id); # process perimeters { my $extruder_id = $region->config->perimeter_extruder-1; foreach my $perimeter_coll (@{$layerm->perimeters}) { next if $perimeter_coll->empty; # this shouldn't happen but first_point() would fail # init by_extruder item only if we actually use the extruder $by_extruder{$extruder_id} //= []; # $perimeter_coll is an ExtrusionPath::Collection object representing a single slice for my $i (0 .. $#{$layer->slices}) { if ($i == $#{$layer->slices} || $layer->slices->[$i]->contour->contains_point($perimeter_coll->first_point)) { $by_extruder{$extruder_id}[$i] //= { perimeters => {} }; $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= []; push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll; last; } } } } # process infill # $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing # the ExtrusionPath objects of a certain infill "group" (also called "surface" # throughout the code). We can redefine the order of such Collections but we have to # do each one completely at once. foreach my $fill (@{$layerm->fills}) { next if $fill->empty; # this shouldn't happen but first_point() would fail # init by_extruder item only if we actually use the extruder my $extruder_id = $fill->[0]->is_solid_infill ? $region->config->solid_infill_extruder-1 : $region->config->infill_extruder-1; $by_extruder{$extruder_id} //= []; # $fill is an ExtrusionPath::Collection object for my $i (0 .. $#{$layer->slices}) { if ($i == $#{$layer->slices} || $layer->slices->[$i]->contour->contains_point($fill->first_point)) { $by_extruder{$extruder_id}[$i] //= { infill => {} }; $by_extruder{$extruder_id}[$i]{infill}{$region_id} //= []; push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill; last; } } } } # tweak extruder ordering to save toolchanges my @extruders = sort keys %by_extruder; if (@extruders > 1) { my $last_extruder_id = $self->_gcodegen->writer->extruder->id; if (exists $by_extruder{$last_extruder_id}) { @extruders = ( $last_extruder_id, grep $_ != $last_extruder_id, @extruders, ); } } foreach my $extruder_id (@extruders) { $gcode .= $self->_gcodegen->set_extruder($extruder_id); foreach my $island (@{ $by_extruder{$extruder_id} }) { if ($self->print->config->infill_first) { $gcode .= $self->_extrude_infill($island->{infill} // {}); $gcode .= $self->_extrude_perimeters($island->{perimeters} // {}); } else { $gcode .= $self->_extrude_perimeters($island->{perimeters} // {}); $gcode .= $self->_extrude_infill($island->{infill} // {}); } } } } # apply spiral vase post-processing if this layer contains suitable geometry # (we must feed all the G-code into the post-processor, including the first # bottom non-spiral layers otherwise it will mess with positions) # we apply spiral vase at this stage because it requires a full layer $gcode = $self->_spiral_vase->process_layer($gcode) if defined $self->_spiral_vase; # apply cooling logic; this may alter speeds $gcode = $self->_cooling_buffer->append( $gcode, $layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers $layer->id, $layer->print_z, ) if defined $self->_cooling_buffer; print {$self->fh} $self->filter($gcode); } sub _extrude_perimeters { my ($self, $entities_by_region) = @_; my $gcode = ""; foreach my $region_id (sort keys %$entities_by_region) { $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); $gcode .= $self->_gcodegen->extrude($_, 'perimeter') for @{ $entities_by_region->{$region_id} }; } return $gcode; } sub _extrude_infill { my ($self, $entities_by_region) = @_; my $gcode = ""; foreach my $region_id (sort keys %$entities_by_region) { $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} }); for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) { if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { $gcode .= $self->_gcodegen->extrude($_, 'infill') for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; } else { $gcode .= $self->_gcodegen->extrude($fill, 'infill') ; } } } return $gcode; } sub flush_filters { my ($self) = @_; print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1); } sub filter { my ($self, $gcode, $flush) = @_; # apply vibration limit if enabled; # this injects pauses according to time (thus depends on actual speeds) $gcode = $self->_vibration_limit->process($gcode) if defined $self->_vibration_limit; # apply pressure regulation if enabled; # this depends on actual speeds $gcode = $self->_pressure_regulator->process($gcode, $flush) if defined $self->_pressure_regulator; # apply arc fitting if enabled; # this does not depend on speeds but changes G1 XY commands into G2/G2 IJ $gcode = $self->_arc_fitting->process($gcode) if defined $self->_arc_fitting; return $gcode; } 1; Slic3r-1.2.9/lib/Slic3r/Print/Object.pm000066400000000000000000001457361254023100400174360ustar00rootroot00000000000000package Slic3r::Print::Object; use strict; use warnings; use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path); use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex offset offset_ex offset2 offset2_ex intersection_ppl CLIPPER_OFFSET_SCALE JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; # 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) ]; } # this should be idempotent sub slice { my $self = shift; return if $self->step_done(STEP_SLICE); $self->set_step_started(STEP_SLICE); $self->print->status_cb->(10, "Processing triangulated mesh"); # init layers { $self->clear_layers; # make layers taking custom heights into account my $id = 0; my $print_z = 0; my $first_object_layer_height = -1; my $first_object_layer_distance = -1; # add raft layers if ($self->config->raft_layers > 0) { $id += $self->config->raft_layers; # raise first object layer Z by the thickness of the raft itself # plus the extra distance required by the support material logic my $first_layer_height = $self->config->get_value('first_layer_height'); $print_z += $first_layer_height; # use a large height my $support_material_layer_height; { my @nozzle_diameters = ( map $self->print->config->get_at('nozzle_diameter', $_), $self->config->support_material_extruder-1, $self->config->support_material_interface_extruder-1, ); $support_material_layer_height = 0.75 * min(@nozzle_diameters); } $print_z += $support_material_layer_height * ($self->config->raft_layers - 1); # compute the average of all nozzles used for printing the object my $nozzle_diameter; { my @nozzle_diameters = ( map $self->print->config->get_at('nozzle_diameter', $_), @{$self->print->object_extruders} ); $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; } $first_object_layer_distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter); # force first layer print_z according to the contact distance # (the loop below will raise print_z by such height) $first_object_layer_height = $first_object_layer_distance - $self->config->support_material_contact_distance; } # loop until we have at least one layer and the max slice_z reaches the object height my $slice_z = 0; my $height = 0; my $max_z = unscale($self->size->z); while (($slice_z - $height) <= $max_z) { # assign the default height to the layer according to the general settings $height = ($id == 0) ? $self->config->get_value('first_layer_height') : $self->config->layer_height; # look for an applicable custom range if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { $height = $range->[2]; # if user set custom height to zero we should just skip the range and resume slicing over it if ($height == 0) { $slice_z += $range->[1] - $range->[0]; next; } } if ($first_object_layer_height != -1 && !@{$self->layers}) { $height = $first_object_layer_height; $print_z += ($first_object_layer_distance - $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; $self->add_layer($id, $height, $print_z, $slice_z); if ($self->layer_count >= 2) { my $lc = $self->layer_count; $self->get_layer($lc - 2)->set_upper_layer($self->get_layer($lc - 1)); $self->get_layer($lc - 1)->set_lower_layer($self->get_layer($lc - 2)); } $id++; $slice_z += $height/2; # add the other half layer } } # make sure all layers contain layer region objects for all regions my $regions_count = $self->print->region_count; foreach my $layer (@{ $self->layers }) { $layer->region($_) for 0 .. ($regions_count-1); } # get array of Z coordinates for slicing my @z = map $_->slice_z, @{$self->layers}; # slice all non-modifier 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->get_layer($layer_id)->regions->[$region_id]; $layerm->slices->clear; foreach my $expolygon (@{ $expolygons_by_layer->[$layer_id] }) { $layerm->slices->append(Slic3r::Surface->new( expolygon => $expolygon, surface_type => S_TYPE_INTERNAL, )); } } } # then slice all modifier 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_count) { next if $other_region_id == $region_id; for my $layer_id (0..$#$expolygons_by_layer) { my $layerm = $self->get_layer($layer_id)->regions->[$region_id]; my $other_layerm = $self->get_layer($layer_id)->regions->[$other_region_id]; next if !defined $other_layerm; my $other_slices = [ map $_->p, @{$other_layerm->slices} ]; # Polygons my $my_parts = intersection_ex( $other_slices, [ map @$_, @{ $expolygons_by_layer->[$layer_id] } ], ); next if !@$my_parts; # append new parts to our region foreach my $expolygon (@$my_parts) { $layerm->slices->append(Slic3r::Surface->new( expolygon => $expolygon, surface_type => S_TYPE_INTERNAL, )); } # remove such parts from original region $other_layerm->slices->clear; $other_layerm->slices->append(Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNAL, )) for @{ diff_ex($other_slices, [ map @$_, @$my_parts ]) }; } } } } # remove last layer(s) if empty $self->delete_layer($self->layer_count - 1) while $self->layer_count && (!map @{$_->slices}, @{$self->get_layer($self->layer_count - 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->layer_count - 1)) { my $layer = $self->get_layer($i); next unless $layer->slicing_errors; if (!$warning_thrown) { warn "The model has overlapping or self-intersecting facets. I tried to repair it, " . "however you might want to check the results or repair the input file and retry.\n"; $warning_thrown = 1; } # try to repair the layer surfaces by merging all contours and all holes from # neighbor layers Slic3r::debugf "Attempting to repair layer %d\n", $i; 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->layer_count; $j++) { if (!$self->get_layer($j)->slicing_errors) { @upper_surfaces = @{$self->get_layer($j)->region($region_id)->slices}; last; } } for (my $j = $i-1; $j >= 0; $j--) { if (!$self->get_layer($j)->slicing_errors) { @lower_surfaces = @{$self->get_layer($j)->region($region_id)->slices}; last; } } my $union = union_ex([ map $_->expolygon->contour, @upper_surfaces, @lower_surfaces, ]); my $diff = diff_ex( [ map @$_, @$union ], [ map @{$_->expolygon->holes}, @upper_surfaces, @lower_surfaces, ], ); $layerm->slices->clear; $layerm->slices->append($_) for map Slic3r::Surface->new (expolygon => $_, surface_type => S_TYPE_INTERNAL), @$diff; } # update layer slices after repairing the single regions $layer->make_slices; } # remove empty layers from bottom while (@{$self->layers} && !@{$self->get_layer(0)->slices}) { shift @{$self->layers}; for (my $i = 0; $i <= $#{$self->layers}; $i++) { $self->get_layer($i)->set_id( $self->get_layer($i)->id-1 ); } } # simplify slices if required if ($self->print->config->resolution) { $self->_simplify_slices(scale($self->print->config->resolution)); } die "No layers were detected. You might want to repair your STL file(s) or check their size and retry.\n" if !@{$self->layers}; $self->set_typed_slices(0); $self->set_step_done(STEP_SLICE); } sub _slice_region { my ($self, $region_id, $z, $modifier) = @_; return [] if !@{$self->get_region_volumes($region_id)}; # compose mesh my $mesh; 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; if (defined $mesh) { $mesh->merge($volume->mesh); } else { $mesh = $volume->mesh->clone; } } return if !defined $mesh; # transform mesh # we ignore the per-instance transformations currently and only # consider the first one $self->model_object->instances->[0]->transform_mesh($mesh, 1); # align mesh to Z = 0 (it should be already aligned actually) and apply XY shift $mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min); # perform actual slicing return $mesh->slice($z); } sub make_perimeters { my $self = shift; # prerequisites $self->slice; return if $self->step_done(STEP_PERIMETERS); $self->set_step_started(STEP_PERIMETERS); $self->print->status_cb->(20, "Generating perimeters"); # merge slices if they were split into types if ($self->typed_slices) { $_->merge_slices for @{$self->layers}; $self->set_typed_slices(0); $self->invalidate_step(STEP_PREPARE_INFILL); } # compare each layer to the one below, and mark those slices needing # one additional inner perimeter, like the top of domed objects- # this algorithm makes sure that at least one perimeter is overlapping # but we don't generate any extra perimeter if fill density is zero, as they would be floating # inside the object - infill_only_where_needed should be the method of choice for printing # hollow objects for my $region_id (0 .. ($self->print->region_count-1)) { my $region = $self->print->regions->[$region_id]; my $region_perimeters = $region->config->perimeters; next if !$region->config->extra_perimeters; next if $region_perimeters == 0; next if $region->config->fill_density == 0; for my $i (0 .. ($self->layer_count - 2)) { my $layerm = $self->get_layer($i)->get_region($region_id); my $upper_layerm = $self->get_layer($i+1)->get_region($region_id); my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER); my $ext_perimeter_width = $ext_perimeter_flow->scaled_width; my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing; foreach my $slice (@{$layerm->slices}) { while (1) { # compute the total thickness of perimeters my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2 + ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing; # define a critical area where we don't want the upper slice to fall into # (it should either lay over our perimeters or outside this area) my $critical_area_depth = $perimeter_spacing*1.5; my $critical_area = diff( offset($slice->expolygon->arrayref, -$perimeters_thickness), offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)), ); # check whether a portion of the upper slices falls inside the critical area my $intersection = intersection_ppl( [ map $_->p, @{$upper_layerm->slices} ], $critical_area, ); # only add an additional loop if at least 30% of the slice loop would benefit from it my $total_loop_length = sum(map $_->length, map $_->p, @{$upper_layerm->slices}) // 0; my $total_intersection_length = sum(map $_->length, @$intersection) // 0; last unless $total_intersection_length > $total_loop_length*0.3; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "extra.svg", no_arrows => 1, expolygons => union_ex($critical_area), polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], ); } $slice->extra_perimeters($slice->extra_perimeters + 1); } Slic3r::debugf " adding %d more perimeter(s) at layer %d\n", $slice->extra_perimeters, $layerm->id if $slice->extra_perimeters > 0; } } } Slic3r::parallelize( threads => $self->print->config->threads, items => sub { 0 .. ($self->layer_count - 1) }, thread_cb => sub { my $q = shift; while (defined (my $i = $q->dequeue)) { $self->get_layer($i)->make_perimeters; } }, no_threads_cb => sub { $_->make_perimeters for @{$self->layers}; }, ); # simplify slices (both layer and region slices), # we only need the max resolution for perimeters ### This makes this method not-idempotent, so we keep it disabled for now. ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); $self->set_step_done(STEP_PERIMETERS); } sub prepare_infill { my ($self) = @_; # prerequisites $self->make_perimeters; return if $self->step_done(STEP_PREPARE_INFILL); $self->set_step_started(STEP_PREPARE_INFILL); $self->print->status_cb->(30, "Preparing infill"); # this will assign a type (top/bottom/internal) to $layerm->slices # and transform $layerm->fill_surfaces from expolygon # to typed top/bottom/internal surfaces; $self->detect_surfaces_type; $self->set_typed_slices(1); # decide what surfaces are to be filled $_->prepare_fill_surfaces for map @{$_->regions}, @{$self->layers}; # this will detect bridges and reverse bridges # and rearrange top/bottom/internal surfaces $self->process_external_surfaces; # detect which fill surfaces are near external layers # they will be split in internal and internal-solid surfaces $self->discover_horizontal_shells; $self->clip_fill_surfaces; # the following step needs to be done before combination because it may need # to remove only half of the combined infill $self->bridge_over_infill; # combine fill surfaces to honor the "infill every N layers" option $self->combine_infill; $self->set_step_done(STEP_PREPARE_INFILL); } sub infill { my ($self) = @_; # prerequisites $self->prepare_infill; return if $self->step_done(STEP_INFILL); $self->set_step_started(STEP_INFILL); $self->print->status_cb->(70, "Infilling layers"); Slic3r::parallelize( threads => $self->print->config->threads, items => sub { my @items = (); # [layer_id, region_id] for my $region_id (0 .. ($self->print->region_count-1)) { push @items, map [$_, $region_id], 0..($self->layer_count - 1); } @items; }, thread_cb => sub { my $q = shift; while (defined (my $obj_layer = $q->dequeue)) { my ($i, $region_id) = @$obj_layer; my $layerm = $self->get_layer($i)->regions->[$region_id]; $layerm->fills->clear; $layerm->fills->append($_) for $self->fill_maker->make_fill($layerm); } }, no_threads_cb => sub { foreach my $layerm (map @{$_->regions}, @{$self->layers}) { $layerm->fills->clear; $layerm->fills->append($_) for $self->fill_maker->make_fill($layerm); } }, ); ### we could free memory now, but this would make this step not idempotent ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; $self->set_step_done(STEP_INFILL); } sub generate_support_material { my $self = shift; # prerequisites $self->slice; return if $self->step_done(STEP_SUPPORTMATERIAL); $self->set_step_started(STEP_SUPPORTMATERIAL); $self->clear_support_layers; if ((!$self->config->support_material && $self->config->raft_layers == 0) || scalar(@{$self->layers}) < 2) { $self->set_step_done(STEP_SUPPORTMATERIAL); return; } $self->print->status_cb->(85, "Generating support material"); $self->_support_material->generate($self); $self->set_step_done(STEP_SUPPORTMATERIAL); } sub _support_material { my ($self) = @_; my $first_layer_flow = Slic3r::Flow->new_from_width( width => ($self->print->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width), role => FLOW_ROLE_SUPPORT_MATERIAL, nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ] // $self->print->config->nozzle_diameter->[0], layer_height => $self->config->get_abs_value('first_layer_height'), bridge_flow_ratio => 0, ); return Slic3r::Print::SupportMaterial->new( print_config => $self->print->config, object_config => $self->config, first_layer_flow => $first_layer_flow, flow => $self->support_material_flow, interface_flow => $self->support_material_flow(FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE), ); } sub detect_surfaces_type { my $self = shift; Slic3r::debugf "Detecting solid surfaces...\n"; for my $region_id (0 .. ($self->print->region_count-1)) { for my $i (0 .. ($self->layer_count - 1)) { my $layerm = $self->get_layer($i)->regions->[$region_id]; # prepare a reusable subroutine to make surface differences my $difference = sub { my ($subject, $clip, $result_type) = @_; my $diff = diff( [ map @$_, @$subject ], [ map @$_, @$clip ], 1, ); # collapse very narrow parts (using the safety offset in the diff is not enough) 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) }; }; # comparison happens against the *full* slices (considering all regions) # unless internal shells are requested my $upper_layer = $i < $self->layer_count - 1 ? $self->get_layer($i+1) : undef; my $lower_layer = $i > 0 ? $self->get_layer($i-1) : undef; # find top surfaces (difference between current surfaces # of current layer and upper one) my @top = (); if ($upper_layer) { my $upper_slices = $self->config->interface_shells ? [ map $_->expolygon, @{$upper_layer->regions->[$region_id]->slices} ] : $upper_layer->slices; @top = $difference->( [ map $_->expolygon, @{$layerm->slices} ], $upper_slices, S_TYPE_TOP, ); } else { # if no upper layer, all surfaces of this one are solid # we clone surfaces because we're going to clear the slices collection @top = map $_->clone, @{$layerm->slices}; $_->surface_type(S_TYPE_TOP) for @top; } # find bottom surfaces (difference between current surfaces # of current layer and lower one) my @bottom = (); if ($lower_layer) { # any surface lying on the void is a true bottom bridge push @bottom, $difference->( [ map $_->expolygon, @{$layerm->slices} ], $lower_layer->slices, S_TYPE_BOTTOMBRIDGE, ); # if we have soluble support material, don't bridge if ($self->config->support_material && $self->config->support_material_contact_distance == 0) { $_->surface_type(S_TYPE_BOTTOM) for @bottom; } # if user requested internal shells, we need to identify surfaces # lying on other slices not belonging to this region if ($self->config->interface_shells) { # non-bridging bottom surfaces: any part of this layer lying # on something else, excluding those lying on our own region my $supported = intersection_ex( [ map @{$_->expolygon}, @{$layerm->slices} ], [ map @$_, @{$lower_layer->slices} ], ); push @bottom, $difference->( $supported, [ map $_->expolygon, @{$lower_layer->regions->[$region_id]->slices} ], S_TYPE_BOTTOM, ); } } else { # if no lower layer, all surfaces of this one are solid # we clone surfaces because we're going to clear the slices collection @bottom = map $_->clone, @{$layerm->slices}; # if we have raft layers, consider bottom layer as a bridge # just like any other bottom surface lying on the void if ($self->config->raft_layers > 0 && $self->config->support_material_contact_distance > 0) { $_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom; } else { $_->surface_type(S_TYPE_BOTTOM) for @bottom; } } # now, if the object contained a thin membrane, we could have overlapping bottom # and top surfaces; let's do an intersection to discover them and consider them # as bottom surfaces (to allow for bridge detection) if (@top && @bottom) { my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]); Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->id, scalar(@$overlapping) if $Slic3r::debug; @top = $difference->([map $_->expolygon, @top], $overlapping, S_TYPE_TOP); } # find internal surfaces (difference between top/bottom surfaces and others) my @internal = $difference->( [ map $_->expolygon, @{$layerm->slices} ], [ map $_->expolygon, @top, @bottom ], S_TYPE_INTERNAL, ); # save surfaces to layer $layerm->slices->clear; $layerm->slices->append($_) for (@bottom, @top, @internal); Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", $layerm->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; } # 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. This guarantees idempotence of prepare_infill() also in case # that combine_infill() turns some fill_surface into VOID surfaces. my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ]; $layerm->fill_surfaces->clear; foreach my $surface (@{$layerm->slices}) { my $intersection = intersection_ex( [ $surface->p ], $fill_boundaries, ); $layerm->fill_surfaces->append($_) for map Slic3r::Surface->new(expolygon => $_, surface_type => $surface->surface_type), @$intersection; } } } } # Idempotence of this method is guaranteed by the fact that we don't remove things from # fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub clip_fill_surfaces { my $self = shift; return unless $self->config->infill_only_where_needed; # We only want infill under ceilings; this is almost like an # internal support material. # proceed top-down skipping bottom layer my $upper_internal = []; for my $layer_id (reverse 1..($self->layer_count - 1)) { my $layer = $self->get_layer($layer_id); my $lower_layer = $self->get_layer($layer_id-1); # detect things that we need to support my $overhangs = []; # Polygons # we need to support any solid surface push @$overhangs, map $_->p, grep $_->is_solid, map @{$_->fill_surfaces}, @{$layer->regions}; # we also need to support perimeters when there's at least one full # unsupported loop { # get perimeters area as the difference between slices and fill_surfaces my $perimeters = diff( [ map @$_, @{$layer->slices} ], [ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ], ); # only consider the area that is not supported by lower perimeters $perimeters = intersection( $perimeters, [ map $_->p, map @{$_->fill_surfaces}, @{$lower_layer->regions} ], 1, ); # only consider perimeter areas that are at least one extrusion width thick my $pw = min(map $_->flow(FLOW_ROLE_PERIMETER)->scaled_width, @{$layer->regions}); $perimeters = offset2($perimeters, -$pw, +$pw); # append such thick perimeters to the areas that need support push @$overhangs, @$perimeters; } # find new internal infill $upper_internal = my $new_internal = intersection( [ @$overhangs, @$upper_internal, ], [ # our current internal fill boundaries map $_->p, grep $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALVOID, map @{$_->fill_surfaces}, @{$lower_layer->regions} ], ); # apply new internal infill to regions foreach my $layerm (@{$lower_layer->regions}) { my (@internal, @other) = (); foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) { if ($surface->surface_type == S_TYPE_INTERNAL || $surface->surface_type == S_TYPE_INTERNALVOID) { push @internal, $surface; } else { push @other, $surface; } } my @new = map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNAL, ), @{intersection_ex( [ map $_->p, @internal ], $new_internal, 1, )}; push @other, map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNALVOID, ), @{diff_ex( [ map $_->p, @internal ], $new_internal, 1, )}; # If there are voids it means that our internal infill is not adjacent to # perimeters. In this case it would be nice to add a loop around infill to # make it more robust and nicer. TODO. $layerm->fill_surfaces->clear; $layerm->fill_surfaces->append($_) for (@new, @other); } } } sub process_external_surfaces { my ($self) = @_; for my $region_id (0 .. ($self->print->region_count-1)) { $self->get_layer(0)->regions->[$region_id]->process_external_surfaces(undef); for my $i (1 .. ($self->layer_count - 1)) { $self->get_layer($i)->regions->[$region_id]->process_external_surfaces($self->get_layer($i-1)); } } } sub discover_horizontal_shells { my $self = shift; Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n"; for my $region_id (0 .. ($self->print->region_count-1)) { for (my $i = 0; $i < $self->layer_count; $i++) { my $layerm = $self->get_layer($i)->regions->[$region_id]; if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0 && ($i % $layerm->config->solid_infill_every_layers) == 0) { $_->surface_type(S_TYPE_INTERNALSOLID) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}; } EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE) { # find slices of current type for current layer # use slices instead of fill_surfaces because they also include the perimeter area # which needs to be propagated in shells; we need to grow slices like we did for # fill_surfaces though. Using both ungrown slices and grown fill_surfaces will # not work in some situations, as there won't be any grown region in the perimeter # area (this was seen in a model where the top layer had one extra perimeter, thus # its fill_surfaces were thinner than the lower layer's infill), however it's the best # solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put # too much solid infill inside nearly-vertical slopes. my $solid = [ (map $_->p, @{$layerm->slices->filter_by_type($type)}), (map $_->p, @{$layerm->fill_surfaces->filter_by_type($type)}), ]; 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; NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1; abs($n - $i) <= $solid_layers-1; ($type == S_TYPE_TOP) ? $n-- : $n++) { next if $n < 0 || $n >= $self->layer_count; Slic3r::debugf " looking for neighbors on layer %d...\n", $n; my $neighbor_layerm = $self->get_layer($n)->regions->[$region_id]; my $neighbor_fill_surfaces = $neighbor_layerm->fill_surfaces; my @neighbor_fill_surfaces = map $_->clone, @$neighbor_fill_surfaces; # clone because we will use these surfaces even after clearing the collection # find intersection between neighbor and current layer's surfaces # intersections have contours and holes # we update $solid so that we limit the next neighbor layer to the areas that were # found on this one - in other words, solid shells on one layer (for a given external surface) # are always a subset of the shells found on the previous shell layer # this approach allows for DWIM in hollow sloping vases, where we want bottom # shells to be generated in the base but not in the walls (where there are many # narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the # 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, ); next EXTERNAL if !@$new_internal_solid; if ($layerm->config->fill_density == 0) { # if we're printing a hollow object we discard any solid shell thinner # 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_EXTERNAL_PERIMETER)->scaled_width; my $too_narrow = diff( $new_internal_solid, offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5), 1, ); $new_internal_solid = $solid = diff( $new_internal_solid, $too_narrow, ) if @$too_narrow; } # make sure the new internal solid is wide enough, as it might get collapsed # when spacing is added in Fill.pm { my $margin = 3 * $layerm->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width; # require at least this size # we use a higher miterLimit here to handle areas with acute angles # in those cases, the default miterLimit would cut the corner and we'd # get a triangle in $too_narrow; if we grow it below then the shell # would have a different shape from the external surface and we'd still # have the same angle, so the next shell would be grown even more and so on. my $too_narrow = diff( $new_internal_solid, offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5), 1, ); if (@$too_narrow) { # grow the collapsing parts and add the extra area to the neighbor layer # as well as to our original surfaces so that we support this # additional area in the next shell too # make sure our grown surfaces don't exceed the fill area my @grown = @{intersection( offset($too_narrow, +$margin), [ map $_->p, @neighbor_fill_surfaces ], )}; $new_internal_solid = $solid = [ @grown, @$new_internal_solid ]; } } # internal-solid are the union of the existing internal-solid surfaces # and new ones my $internal_solid = union_ex([ ( 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( [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], [ map @$_, @$internal_solid ], 1, ); Slic3r::debugf " %d internal-solid and %d internal surfaces found\n", scalar(@$internal_solid), scalar(@$internal); # assign resulting internal surfaces to layer $neighbor_fill_surfaces->clear; $neighbor_fill_surfaces->append($_) for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$internal; # assign new internal-solid surfaces to layer $neighbor_fill_surfaces->append($_) for map Slic3r::Surface->new(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($_) for map $s->[0]->clone(expolygon => $_), @$solid_surfaces; } } } } } } # combine fill surfaces across layers # Idempotence of this method is guaranteed by the fact that we don't remove things from # fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub combine_infill { my $self = shift; # define the type used for voids my %voidtype = ( &S_TYPE_INTERNAL() => S_TYPE_INTERNALVOID, ); # work on each region separately for my $region_id (0 .. ($self->print->region_count-1)) { my $region = $self->print->get_region($region_id); my $every = $region->config->infill_every_layers; next unless $every > 1 && $region->config->fill_density > 0; # limit the number of combined layers to the maximum height allowed by this regions' nozzle my $nozzle_diameter = min( $self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1), $self->print->config->get_at('nozzle_diameter', $region->config->solid_infill_extruder-1), ); # define the combinations my %combine = (); # layer_idx => number of additional combined lower layers { my $current_height = my $layers = 0; for my $layer_idx (0 .. ($self->layer_count-1)) { my $layer = $self->get_layer($layer_idx); next if $layer->id == 0; # skip first print layer (which may not be first layer in array because of raft) my $height = $layer->height; # check whether the combination of this layer with the lower layers' buffer # would exceed max layer height or max combined layer count if ($current_height + $height >= $nozzle_diameter || $layers >= $every) { # append combination to lower layer $combine{$layer_idx-1} = $layers; $current_height = $layers = 0; } $current_height += $height; $layers++; } # append lower layers (if any) to uppermost layer $combine{$self->layer_count-1} = $layers; } # loop through layers to which we have assigned layers to combine for my $layer_idx (sort keys %combine) { next unless $combine{$layer_idx} > 1; # get all the LayerRegion objects to be combined my @layerms = map $self->get_layer($_)->get_region($region_id), ($layer_idx - ($combine{$layer_idx}-1) .. $layer_idx); # only combine internal infill for my $type (S_TYPE_INTERNAL) { # we need to perform a multi-layer intersection, so let's split it in pairs # initialize the intersection with the candidates of the lowest layer my $intersection = [ map $_->expolygon, @{$layerms[0]->fill_surfaces->filter_by_type($type)} ]; # start looping from the second layer and intersect the current intersection with it for my $layerm (@layerms[1 .. $#layerms]) { $intersection = intersection_ex( [ map @$_, @$intersection ], [ map @{$_->expolygon}, @{$layerm->fill_surfaces->filter_by_type($type)} ], ); } my $area_threshold = $layerms[0]->infill_area_threshold; @$intersection = grep $_->area > $area_threshold, @$intersection; next if !@$intersection; Slic3r::debugf " combining %d %s regions from layers %d-%d\n", scalar(@$intersection), ($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'), $layer_idx-($every-1), $layer_idx; # $intersection now contains the regions that can be combined across the full amount of layers # so let's remove those areas from all layers my @intersection_with_clearance = map @{$_->offset( $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width / 2 + $layerms[-1]->flow(FLOW_ROLE_PERIMETER)->scaled_width / 2 # Because fill areas for rectilinear and honeycomb are grown # later to overlap perimeters, we need to counteract that too. + (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|honeycomb)/) ? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width : 0) )}, @$intersection; foreach my $layerm (@layerms) { my @this_type = @{$layerm->fill_surfaces->filter_by_type($type)}; my @other_types = map $_->clone, grep $_->surface_type != $type, @{$layerm->fill_surfaces}; my @new_this_type = map Slic3r::Surface->new(expolygon => $_, surface_type => $type), @{diff_ex( [ map $_->p, @this_type ], [ @intersection_with_clearance ], )}; # apply surfaces back with adjusted depth to the uppermost layer if ($layerm->id == $self->get_layer($layer_idx)->id) { push @new_this_type, map Slic3r::Surface->new( expolygon => $_, surface_type => $type, thickness => sum(map $_->height, @layerms), thickness_layers => scalar(@layerms), ), @$intersection; } else { # save void surfaces push @new_this_type, map Slic3r::Surface->new(expolygon => $_, surface_type => $voidtype{$type}), @{intersection_ex( [ map @{$_->expolygon}, @this_type ], [ @intersection_with_clearance ], )}; } $layerm->fill_surfaces->clear; $layerm->fill_surfaces->append($_) for (@new_this_type, @other_types); } } } } } sub _simplify_slices { my ($self, $distance) = @_; foreach my $layer (@{$self->layers}) { $layer->slices->simplify($distance); $_->slices->simplify($distance) for @{$layer->regions}; } } sub support_material_flow { my ($self, $role) = @_; $role //= FLOW_ROLE_SUPPORT_MATERIAL; my $extruder = ($role == FLOW_ROLE_SUPPORT_MATERIAL) ? $self->config->support_material_extruder : $self->config->support_material_interface_extruder; # 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 || $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, bridge_flow_ratio => 0, ); } 1; Slic3r-1.2.9/lib/Slic3r/Print/Simple.pm000066400000000000000000000054351254023100400174500ustar00rootroot00000000000000package Slic3r::Print::Simple; use Moo; use Slic3r::Geometry qw(X Y); has '_print' => ( is => 'ro', default => sub { Slic3r::Print->new }, handles => [qw(apply_config extruders expanded_output_filepath total_used_filament total_extruded_volume placeholder_parser process)], ); has 'duplicate' => ( is => 'rw', default => sub { 1 }, ); has 'scale' => ( is => 'rw', default => sub { 1 }, ); has 'rotate' => ( is => 'rw', default => sub { 0 }, ); has 'duplicate_grid' => ( is => 'rw', default => sub { [1,1] }, ); has 'status_cb' => ( is => 'rw', default => sub { sub {} }, ); has 'print_center' => ( is => 'rw', default => sub { Slic3r::Pointf->new(100,100) }, ); has 'output_file' => ( is => 'rw', ); sub set_model { my ($self, $model) = @_; # make method idempotent so that the object is reusable $self->_print->clear_objects; # 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->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) { $model->duplicate_objects_grid($self->duplicate_grid, $self->_print->config->duplicate_distance); } elsif ($need_arrange) { $model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance); } elsif ($self->duplicate > 1) { # if all input objects have defined position(s) apply duplication to the whole model $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); } $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; $model->center_instances_around_point($self->print_center); foreach my $model_object (@{$model->objects}) { $self->_print->auto_assign_extruders($model_object); $self->_print->add_model_object($model_object); } } sub _before_export { my ($self) = @_; $self->_print->set_status_cb($self->status_cb); $self->_print->validate; } sub _after_export { my ($self) = @_; $self->_print->set_status_cb(undef); } sub export_gcode { my ($self) = @_; $self->_before_export; $self->_print->export_gcode(output_file => $self->output_file); $self->_after_export; } sub export_svg { my ($self) = @_; $self->_before_export; $self->_print->export_svg(output_file => $self->output_file); $self->_after_export; } 1; Slic3r-1.2.9/lib/Slic3r/Print/State.pm000066400000000000000000000004451254023100400172730ustar00rootroot00000000000000package Slic3r::Print::State; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM); our %EXPORT_TAGS = (steps => \@EXPORT_OK); 1; Slic3r-1.2.9/lib/Slic3r/Print/SupportMaterial.pm000066400000000000000000001165141254023100400213530ustar00rootroot00000000000000package Slic3r::Print::SupportMaterial; use Moo; use List::Util qw(sum min max); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull); use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 intersection_pl offset2_ex diff_pl); use Slic3r::Surface ':types'; has 'print_config' => (is => 'rw', required => 1); has 'object_config' => (is => 'rw', required => 1); has 'flow' => (is => 'rw', required => 1); has 'first_layer_flow' => (is => 'rw', required => 1); has 'interface_flow' => (is => 'rw', required => 1); use constant DEBUG_CONTACT_ONLY => 0; # increment used to reach MARGIN in steps to avoid trespassing thin objects use constant MARGIN_STEP => MARGIN/3; # generate a tree-like structure to save material use constant PILLAR_SIZE => 2.5; use constant PILLAR_SPACING => 10; sub generate { my ($self, $object) = @_; # Determine the top surfaces of the support, defined as: # contact = overhangs - clearance + margin # This method is responsible for identifying what contact surfaces # should the support material expose to the object in order to guarantee # that it will be effective, regardless of how it's built below. my ($contact, $overhang) = $self->contact_area($object); # Determine the top surfaces of the object. We need these to determine # the layer heights of support material and to clip support to the object # silhouette. my ($top) = $self->object_top($object, $contact); # We now know the upper and lower boundaries for our support material object # (@$contact_z and @$top_z), so we can generate intermediate layers. my $support_z = $self->support_layers_z( [ sort keys %$contact ], [ sort keys %$top ], max(map $_->height, @{$object->layers}) ); # If we wanted to apply some special logic to the first support layers lying on # object's top surfaces this is the place to detect them my $shape = []; if ($self->object_config->support_material_pattern eq 'pillars') { $self->generate_pillars_shape($contact, $support_z, $shape); } # Propagate contact layers downwards to generate interface layers my ($interface) = $self->generate_interface_layers($support_z, $contact, $top); $self->clip_with_object($interface, $support_z, $object); $self->clip_with_shape($interface, $shape) if @$shape; # Propagate contact layers and interface layers downwards to generate # the main support layers. my ($base) = $self->generate_base_layers($support_z, $contact, $interface, $top); $self->clip_with_object($base, $support_z, $object); $self->clip_with_shape($base, $shape) if @$shape; # Detect what part of base support layers are "reverse interfaces" because they # lie above object's top surfaces. $self->generate_bottom_interface_layers($support_z, $base, $top, $interface); # Install support layers into object. 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 ); 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); } sub contact_area { my ($self, $object) = @_; # if user specified a custom angle threshold, convert it to radians my $threshold_rad; if ($self->object_config->support_material_threshold) { $threshold_rad = deg2rad($self->object_config->support_material_threshold + 1); # +1 makes the threshold inclusive Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); } # determine contact areas my %contact = (); # contact_z => [ polygons ] my %overhang = (); # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer for my $layer_id (0 .. $#{$object->layers}) { # note $layer_id might != $layer->id when raft_layers > 0 # so $layer_id == 0 means first object layer # and $layer->id == 0 means first print layer (including raft) if ($self->object_config->raft_layers == 0) { next if $layer_id == 0; } elsif (!$self->object_config->support_material) { # if we are only going to generate raft just check # the 'overhangs' of the first object layer last if $layer_id > 0; } my $layer = $object->get_layer($layer_id); # detect overhangs and contact areas needed to support them my (@overhang, @contact) = (); if ($layer_id == 0) { # this is the first object layer, so we're here just to get the object # footprint for the raft # 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->get_layer($layer_id-1); foreach my $layerm (@{$layer->regions}) { 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 && $layer_id == 0)) { my $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( [ map $_->p, @{$layerm->slices} ], offset([ map @$_, @{$lower_layer->slices} ], +$fw*2), ); # 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 } map @$_, @{$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! # Let's define the required contact area by using a max gap of half the upper # extrusion width and extending the area according to the configured margin. # 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)}; for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) { $diff = diff( offset($diff, $_), \@slices_margin, ); } } push @contact, @$diff; } } next if !@contact; # now apply the contact areas to the layer were they need to be made { # get the average nozzle diameter used on this layer my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_), map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 } @{$layer->regions}; my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter); # ignore this contact area if it's too low next if $contact_z < $self->object_config->get_value('first_layer_height') - epsilon; $contact{$contact_z} = [ @contact ]; $overhang{$contact_z} = [ @overhang ]; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("contact_" . $contact_z . ".svg", expolygons => union_ex(\@contact), red_expolygons => union_ex(\@overhang), ); } } } return (\%contact, \%overhang); } sub object_top { my ($self, $object, $contact) = @_; # find object top surfaces # we'll use them to clip our support and detect where does it stick my %top = (); # print_z => [ expolygons ] my $projection = []; foreach my $layer (reverse @{$object->layers}) { if (my @top = map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions}) { # compute projection of the contact areas above this top layer # first add all the 'new' contact areas to the current projection # ('new' means all the areas that are lower than the last top layer # we considered) my $min_top = min(keys %top) // max(keys %$contact); # use <= instead of just < because otherwise we'd ignore any contact regions # having the same Z of top layers push @$projection, map @{$contact->{$_}}, grep { $_ > $layer->print_z && $_ <= $min_top } keys %$contact; # now find whether any projection falls onto this top surface my $touching = intersection($projection, [ map $_->p, @top ]); if (@$touching) { # grow top surfaces so that interface and support generation are generated # with some spacing from object - it looks we don't need the actual # top shapes so this can be done here $top{ $layer->print_z } = offset($touching, $self->flow->scaled_width); } # remove the areas that touched from the projection that will continue on # next, lower, top surfaces $projection = diff($projection, $touching); } } return \%top; } sub support_layers_z { my ($self, $contact_z, $top_z, $max_object_layer_height) = @_; # quick table to check whether a given Z is a top surface my %top = map { $_ => 1 } @$top_z; # determine layer height for any non-contact layer # 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 $contact_distance = $self->contact_distance($support_material_height, $nozzle_diameter); # initialize known, fixed, support layers my @z = sort { $a <=> $b } @$contact_z, @$top_z, # TODO: why we have this? (map $_ + $contact_distance, @$top_z); # enforce first layer height my $first_layer_height = $self->object_config->get_value('first_layer_height'); shift @z while @z && $z[0] <= $first_layer_height; unshift @z, $first_layer_height; # add raft layers by dividing the space between first layer and # first contact layer evenly if ($self->object_config->raft_layers > 1 && @z >= 2) { # $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); # since we already have two raft layers ($z[0] and $z[1]) we need to insert # raft_layers-2 more splice @z, 1, 0, map { sprintf "%.2f", $_ } map { $z[0] + $height * $_ } 1..($self->object_config->raft_layers - 2); } # create other layers (skip raft layers as they're already done and use thicker layers) for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) { my $target_height = $support_material_height; if ($i > 0 && $top{ $z[$i-1] }) { $target_height = $nozzle_diameter; } # enforce first layer height if (($i == 0 && $z[$i] > $target_height + $first_layer_height) || ($z[$i] - $z[$i-1] > $target_height + Slic3r::Geometry::epsilon)) { splice @z, $i, 0, ($z[$i] - $target_height); $i++; } } # remove duplicates and make sure all 0.x values have the leading 0 { my %sl = map { 1 * $_ => 1 } @z; @z = sort { $a <=> $b } keys %sl; } return \@z; } sub generate_interface_layers { my ($self, $support_z, $contact, $top) = @_; # let's now generate interface layers below contact areas my %interface = (); # layer_id => [ polygons ] 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_num; $i--) { $z = $support_z->[$i]; my @overlapping_layers = $self->overlapping_layers($i, $support_z); my @overlapping_z = map $support_z->[$_], @overlapping_layers; # Compute interface area on this layer as diff of upper contact area # (or upper interface area) and layer slices. # This diff is responsible of the contact between support material and # the top surfaces of the object. We should probably offset the top # surfaces vertically before performing the diff, but this needs # investigation. $this = $interface{$i} = diff( [ @$this, # clipped projection of the current contact regions @{ $interface{$i} || [] }, # interface regions already applied to this layer ], [ (map @$_, map $top->{$_}, grep exists $top->{$_}, @overlapping_z), # top slices on this layer (map @$_, map $contact->{$_}, grep exists $contact->{$_}, @overlapping_z), # contact regions on this layer ], 1, ); } } return \%interface; } sub generate_bottom_interface_layers { my ($self, $support_z, $base, $top, $interface) = @_; my $area_threshold = $self->interface_flow->scaled_spacing ** 2; # loop through object's top surfaces foreach my $top_z (sort keys %$top) { my $this = $top->{$top_z}; # keep a count of the interface layers we generated for this top surface my $interface_layers = 0; # loop through support layers until we find the one(s) right above the top # surface foreach my $layer_id (0 .. $#$support_z) { my $z = $support_z->[$layer_id]; next unless $z > $top_z; if ($base->{$layer_id}) { # get the support material area that should be considered interface my $interface_area = intersection( $base->{$layer_id}, $this, ); # discard too small areas $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; # subtract new interface area from base $base->{$layer_id} = diff( $base->{$layer_id}, $interface_area, ); # add new interface area to interface push @{$interface->{$layer_id}}, @$interface_area; } $interface_layers++; last if $interface_layers == $self->object_config->support_material_interface_layers; } } } sub generate_base_layers { my ($self, $support_z, $contact, $interface, $top) = @_; # let's now generate support layers under interface layers my $base = {}; # layer_id => [ polygons ] { for my $i (reverse 0 .. $#$support_z-1) { my $z = $support_z->[$i]; my @overlapping_layers = $self->overlapping_layers($i, $support_z); my @overlapping_z = map $support_z->[$_], @overlapping_layers; # in case we have no interface layers, look at upper contact # (1 interface layer means we only have contact layer, so $interface->{$i+1} is empty) my @upper_contact = (); if ($self->object_config->support_material_interface_layers <= 1) { @upper_contact = @{ $contact->{$support_z->[$i+1]} || [] }; } $base->{$i} = diff( [ @{ $base->{$i+1} || [] }, # support regions on upper layer @{ $interface->{$i+1} || [] }, # interface regions on upper layer @upper_contact, # contact regions on upper layer ], [ (map @$_, map $top->{$_}, grep exists $top->{$_}, @overlapping_z), # top slices on this layer (map @$_, map $interface->{$_}, grep exists $interface->{$_}, @overlapping_layers), # interface regions on this layer (map @$_, map $contact->{$_}, grep exists $contact->{$_}, @overlapping_z), # contact regions on this layer ], 1, ); } } 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) = @_; foreach my $i (keys %$support) { next if !@{$support->{$i}}; my $zmax = $support_z->[$i]; my $zmin = ($i == 0) ? 0 : $support_z->[$i-1]; 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), ); } } sub generate_toolpaths { my ($self, $object, $overhang, $contact, $interface, $base) = @_; my $flow = $self->flow; my $interface_flow = $self->interface_flow; # shape of contact area my $contact_loops = 1; my $circle_radius = 1.5 * $interface_flow->scaled_width; my $circle_distance = 3 * $circle_radius; my $circle = Slic3r::Polygon->new(map [ $circle_radius * cos $_, $circle_radius * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); Slic3r::debugf "Generating patterns\n"; # prepare fillers my $pattern = $self->object_config->support_material_pattern; my @angles = ($self->object_config->support_material_angle); if ($pattern eq 'rectilinear-grid') { $pattern = 'rectilinear'; push @angles, $angles[0] + 90; } elsif ($pattern eq 'pillars') { $pattern = 'honeycomb'; } my %fillers = ( interface => $object->fill_maker->filler('rectilinear'), support => $object->fill_maker->filler($pattern), ); my $interface_angle = $self->object_config->support_material_angle + 90; my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing; my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow->spacing / $interface_spacing; my $support_spacing = $self->object_config->support_material_spacing + $flow->spacing; my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing; my $process_layer = sub { my ($layer_id) = @_; my $layer = $object->support_layers->[$layer_id]; my $z = $layer->print_z; # we redefine flows locally by applying this layer's height my $_flow = $flow->clone; my $_interface_flow = $interface_flow->clone; $_flow->set_height($layer->height); $_interface_flow->set_height($layer->height); my $overhang = $overhang->{$z} || []; my $contact = $contact->{$z} || []; my $interface = $interface->{$layer_id} || []; my $base = $base->{$layer_id} || []; if (DEBUG_CONTACT_ONLY) { $interface = []; $base = []; } if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("layer_" . $z . ".svg", red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), ); } # islands $layer->support_islands->append(@{union_ex([ @$interface, @$base, @$contact ])}); # contact my $contact_infill = []; if ($self->object_config->support_material_interface_layers == 0) { # 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) { # 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 = @$contact; # only consider the loops facing the overhang { my $overhang_with_margin = offset($overhang, +$_interface_flow->scaled_width/2); @external_loops = grep { @{intersection_pl( [ $_->split_at_first_point ], $overhang_with_margin, )} } @external_loops; } # apply a pattern to the loop my @positions = map @{Slic3r::Polygon->new(@$_)->equally_spaced_points($circle_distance)}, @external_loops; @loops0 = @{diff( [ @external_loops ], [ map { my $c = $circle->clone; $c->translate(@$_); $c } @positions ], )}; } # make more loops my @loops = @loops0; for my $i (2..$contact_loops) { my $d = ($i-1) * $_interface_flow->scaled_spacing; push @loops, @{offset2(\@loops0, -$d -0.5*$_interface_flow->scaled_spacing, +0.5*$_interface_flow->scaled_spacing)}; } # clip such loops to the side oriented towards the object @loops = @{intersection_pl( [ map $_->split_at_first_point, @loops ], offset($overhang, +scale MARGIN), )}; # add the contact infill area to the interface area # note that growing loops by $circle_radius ensures no tiny # extrusions are left inside the circles; however it creates # a very large gap between loops and contact_infill, so maybe another # solution should be found to achieve both goals $contact_infill = diff( $contact, [ map @{$_->grow($circle_radius*1.1)}, @loops ], ); # transform loops into ExtrusionPath objects my $mm3_per_mm = $_interface_flow->mm3_per_mm; @loops = map Slic3r::ExtrusionPath->new( polyline => $_, 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); } # interface and contact infill if (@$interface || @$contact_infill) { $fillers{interface}->angle($interface_angle); $fillers{interface}->spacing($_interface_flow->spacing); # 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); # turn base support into interface when it's contained in our holes # (this way we get wider interface anchoring) { my @p = @$interface; @$interface = (); foreach my $p (@p) { if ($p->is_clockwise) { my $p2 = $p->clone; $p2->make_counter_clockwise; next if !@{diff([$p2], $base, 1)}; } push @$interface, $p; } } $base = diff($base, $interface); my @paths = (); foreach my $expolygon (@{union_ex($interface)}) { my @p = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $interface_density, layer_height => $layer->height, complete => 1, ); my $mm3_per_mm = $_interface_flow->mm3_per_mm; push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE, mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, ), @p; } $layer->support_interface_fills->append(@paths); } # support or flange if (@$base) { my $filler = $fillers{support}; $filler->angle($angles[ ($layer_id) % @angles ]); # We don't use $base_flow->spacing because we need a constant spacing # value that guarantees that all layers are correctly aligned. $filler->spacing($flow->spacing); my $density = $support_density; my $base_flow = $_flow; # 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 if ($layer_id == 0) { $filler = $fillers{interface}; $filler->angle($self->object_config->support_material_angle + 90); $density = 0.5; $base_flow = $self->first_layer_flow; # use the proper spacing for first layer as we don't need to align # its pattern to the other layers $filler->spacing($base_flow->spacing); } else { # 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); } my $mm3_per_mm = $base_flow->mm3_per_mm; foreach my $expolygon (@$to_infill) { my @p = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $density, layer_height => $layer->height, complete => 1, ); push @paths, map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_SUPPORTMATERIAL, mm3_per_mm => $mm3_per_mm, width => $base_flow->width, height => $layer->height, ), @p; } $layer->support_fills->append(@paths); } if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("islands_" . $z . ".svg", red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ], polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ], ); } }; Slic3r::parallelize( threads => $self->print_config->threads, items => [ 0 .. $#{$object->support_layers} ], thread_cb => sub { my $q = shift; while (defined (my $layer_id = $q->dequeue)) { $process_layer->($layer_id); } }, no_threads_cb => sub { $process_layer->($_) for 0 .. $#{$object->support_layers}; }, ); } 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; my $grid; # arrayref of polygons { my $pillar = Slic3r::Polygon->new( [0,0], [$pillar_size, 0], [$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) { for (my $y = $bb->y_min; $y <= $bb->y_max-$pillar_size; $y += $pillar_spacing) { push @pillars, my $p = $pillar->clone; $p->translate($x, $y); } } $grid = union(\@pillars); } # add pillars to every layer for my $i (0..$#$support_z) { $shape->[$i] = [ @$grid ]; } # build capitals for my $i (0..$#$support_z) { my $z = $support_z->[$i]; my $capitals = intersection( $grid, $contact->{$z} // [], ); # work on one pillar at time (if any) to prevent the capitals from being merged # but store the contact area supported by the capital because we need to make # sure nothing is left my $contact_supported_by_capitals = []; foreach my $capital (@$capitals) { # enlarge capital tops $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); push @$contact_supported_by_capitals, @$capital; for (my $j = $i-1; $j >= 0; $j--) { my $jz = $support_z->[$j]; $capital = offset($capital, -$self->interface_flow->scaled_width/2); last if !@$capitals; push @{ $shape->[$j] }, @$capital; } } # Capitals will not generally cover the whole contact area because there will be # remainders. For now we handle this situation by projecting such unsupported # areas to the ground, just like we would do with a normal support. my $contact_not_supported_by_capitals = diff( $contact->{$z} // [], $contact_supported_by_capitals, ); if (@$contact_not_supported_by_capitals) { for (my $j = $i-1; $j >= 0; $j--) { push @{ $shape->[$j] }, @$contact_not_supported_by_capitals; } } } } 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], ); } } # this method returns the indices of the layers overlapping with the given one sub overlapping_layers { my ($self, $i, $support_z) = @_; my $zmax = $support_z->[$i]; my $zmin = ($i == 0) ? 0 : $support_z->[$i-1]; return grep { my $zmax2 = $support_z->[$_]; my $zmin2 = ($_ == 0) ? 0 : $support_z->[$_-1]; $zmax > $zmin2 && $zmin < $zmax2; } 0..$#$support_z; } sub contact_distance { my ($self, $layer_height, $nozzle_diameter) = @_; my $extra = $self->object_config->support_material_contact_distance; if ($extra == 0) { return $layer_height; } else { return $nozzle_diameter + $extra; } } 1; Slic3r-1.2.9/lib/Slic3r/SVG.pm000066400000000000000000000101601254023100400155510ustar00rootroot00000000000000package Slic3r::SVG; use strict; use warnings; use SVG; use constant X => 0; use constant Y => 1; our $filltype = 'evenodd'; sub factor { return &Slic3r::SCALING_FACTOR * 10; } sub svg { my $svg = SVG->new(width => 200 * 10, height => 200 * 10); my $marker_end = $svg->marker( id => "endArrow", viewBox => "0 0 10 10", refX => "1", refY => "5", markerUnits => "strokeWidth", orient => "auto", markerWidth => "10", markerHeight => "8", ); $marker_end->polyline( points => "0,0 10,5 0,10 1,5", fill => "darkblue", ); return $svg; } sub output { my ($filename, @things) = @_; my $svg = svg(); my $arrows = 1; while (my $type = shift @things) { my $value = shift @things; if ($type eq 'no_arrows') { $arrows = 0; } elsif ($type =~ /^(?:(.+?)_)?expolygons$/) { my $colour = $1; $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { 'stroke-width' => 0, 'stroke' => $colour || 'black', 'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')), 'fill-type' => $filltype, }, ); foreach my $expolygon (@$value) { my $points = join ' ', map "M $_ z", map join(" ", reverse map $_->[0]*factor() . " " . $_->[1]*factor(), @$_), @$expolygon; $g->path( d => $points, ); } } elsif ($type =~ /^(?:(.+?)_)?(polygon|polyline)s$/) { my ($colour, $method) = ($1, $2); $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { 'stroke-width' => ($method eq 'polyline') ? 1 : 0, 'stroke' => $colour || 'black', 'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')), }, ); foreach my $polygon (@$value) { my $path = $svg->get_path( 'x' => [ map($_->[X] * factor(), @$polygon) ], 'y' => [ map($_->[Y] * factor(), @$polygon) ], -type => 'polygon', ); $g->$method( %$path, 'marker-end' => !$arrows ? "" : "url(#endArrow)", ); } } elsif ($type =~ /^(?:(.+?)_)?points$/) { my $colour = $1 // 'black'; my $r = $colour eq 'black' ? 1 : 3; $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { 'stroke-width' => 2, 'stroke' => $colour, 'fill' => $colour, }, ); foreach my $point (@$value) { $g->circle( cx => $point->[X] * factor(), cy => $point->[Y] * factor(), r => $r, ); } } elsif ($type =~ /^(?:(.+?)_)?lines$/) { my $colour = $1; $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { 'stroke-width' => 2, }, ); foreach my $line (@$value) { $g->line( x1 => $line->[0][X] * factor(), y1 => $line->[0][Y] * factor(), x2 => $line->[1][X] * factor(), y2 => $line->[1][Y] * factor(), style => { 'stroke' => $colour || 'black', }, 'marker-end' => !$arrows ? "" : "url(#endArrow)", ); } } } write_svg($svg, $filename); } sub write_svg { my ($svg, $filename) = @_; Slic3r::open(\my $fh, '>', $filename); print $fh $svg->xmlify; close $fh; printf "SVG written to %s\n", $filename; } 1; Slic3r-1.2.9/lib/Slic3r/Surface.pm000066400000000000000000000005421254023100400165050ustar00rootroot00000000000000package Slic3r::Surface; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_BOTTOMBRIDGE S_TYPE_INTERNAL S_TYPE_INTERNALSOLID S_TYPE_INTERNALBRIDGE S_TYPE_INTERNALVOID); our %EXPORT_TAGS = (types => \@EXPORT_OK); sub p { my $self = shift; return @{$self->polygons}; } 1; Slic3r-1.2.9/lib/Slic3r/Test.pm000066400000000000000000002727621254023100400160530ustar00rootroot00000000000000package Slic3r::Test; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(_eq); use IO::Scalar; use List::Util qw(first); use Slic3r::Geometry qw(epsilon X Y Z); my %cuboids = ( '20mm_cube' => [20,20,20], '2x20x10' => [2, 20,10], ); sub mesh { my ($name, %params) = @_; my ($vertices, $facets); if ($cuboids{$name}) { my ($x, $y, $z) = @{ $cuboids{$name} }; $vertices = [ [$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z], ]; $facets = [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], ], } elsif ($name eq 'cube_with_hole') { $vertices = [ [0,0,0],[0,0,10],[0,20,0],[0,20,10],[20,0,0],[20,0,10],[5,5,0],[15,5,0],[5,15,0],[20,20,0],[15,15,0],[20,20,10],[5,5,10],[5,15,10],[15,5,10],[15,15,10] ]; $facets = [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[0,2,8],[10,8,9],[0,8,6],[0,6,4],[4,7,9],[7,10,9],[2,3,9],[9,3,11],[12,1,5],[13,3,12],[14,12,5],[3,1,12],[11,3,13],[11,15,5],[11,13,15],[15,14,5],[5,4,9],[11,5,9],[8,13,12],[6,8,12],[10,15,13],[8,10,13],[15,10,14],[14,10,7],[14,7,12],[12,7,6] ], } elsif ($name eq 'cube_with_concave_hole') { $vertices = [ [-10,-10,-5],[-10,-10,5],[-10,10,-5],[-10,10,5],[10,-10,-5],[10,-10,5],[-5,-5,-5],[5,-5,-5],[5,5,-5],[5,10,-5],[-5,5,-5],[3.06161699911402e-16,5,-5],[5,0,-5],[0,0,-5],[10,5,-5],[5,10,5],[-5,-5,5],[5,0,5],[5,-5,5],[-5,5,5],[10,5,5],[5,5,5],[3.06161699911402e-16,5,5],[0,0,5] ]; $facets = [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[10,2,11],[11,12,13],[0,2,10],[0,10,6],[0,6,4],[11,2,8],[4,7,12],[4,12,8],[12,11,8],[14,4,8],[2,3,9],[9,3,15],[16,1,5],[17,18,5],[19,3,16],[20,21,5],[18,16,5],[3,1,16],[22,3,19],[21,3,22],[21,17,5],[21,22,17],[21,15,3],[23,17,22],[5,4,14],[20,5,14],[20,14,21],[21,14,8],[9,15,21],[8,9,21],[10,19,16],[6,10,16],[11,22,19],[10,11,19],[13,23,11],[11,23,22],[23,13,12],[17,23,12],[17,12,18],[18,12,7],[18,7,16],[16,7,6] ], } elsif ($name eq 'V') { $vertices = [ [-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20] ]; $facets = [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[4,0,2],[6,4,2],[7,6,2],[8,9,7],[9,6,7],[2,3,7],[7,3,10],[1,5,3],[3,5,11],[11,12,13],[11,13,3],[3,13,10],[5,4,6],[11,5,6],[6,9,11],[11,9,12],[12,9,8],[13,12,8],[8,7,10],[13,8,10] ], } elsif ($name eq 'L') { $vertices = [ [0,10,0],[0,10,10],[0,20,0],[0,20,10],[10,10,0],[10,10,10],[20,20,0],[20,0,0],[10,0,0],[20,20,10],[10,0,10],[20,0,10] ]; $facets = [ [0,1,2],[2,1,3],[4,5,1],[0,4,1],[0,2,4],[4,2,6],[4,6,7],[4,7,8],[2,3,6],[6,3,9],[3,1,5],[9,3,5],[10,11,5],[11,9,5],[5,4,10],[10,4,8],[10,8,7],[11,10,7],[11,7,6],[9,11,6] ], } elsif ($name eq 'overhang') { $vertices = [ [1364.68505859375,614.398010253906,20.002498626709],[1389.68505859375,614.398010253906,20.002498626709],[1377.18505859375,589.398986816406,20.002498626709],[1389.68505859375,589.398986816406,20.002498626709],[1389.68505859375,564.398986816406,20.0014991760254],[1364.68505859375,589.398986816406,20.002498626709],[1364.68505859375,564.398986816406,20.0014991760254],[1360.93505859375,589.398986816406,17.0014991760254],[1360.93505859375,585.64697265625,17.0014991760254],[1357.18505859375,564.398986816406,17.0014991760254],[1364.68505859375,589.398986816406,17.0014991760254],[1364.68505859375,571.899963378906,17.0014991760254],[1364.68505859375,564.398986816406,17.0014991760254],[1348.43603515625,564.398986816406,17.0014991760254],[1352.80908203125,589.398986816406,17.0014991760254],[1357.18408203125,589.398986816406,17.0014991760254],[1357.18310546875,614.398010253906,17.0014991760254],[1364.68505859375,606.89599609375,17.0014991760254],[1364.68505859375,614.398010253906,17.0014991760254],[1352.18603515625,564.398986816406,20.0014991760254],[1363.65405273438,589.398986816406,23.3004989624023],[1359.46704101562,589.398986816406,23.3004989624023],[1358.37109375,564.398986816406,23.3004989624023],[1385.56103515625,564.398986816406,23.3004989624023],[1373.06311035156,589.398986816406,23.3004989624023],[1368.80810546875,564.398986816406,23.3004989624023],[1387.623046875,589.398986816406,23.3004989624023],[1387.623046875,585.276000976562,23.3004989624023],[1389.68505859375,589.398986816406,23.3004989624023],[1389.68505859375,572.64599609375,23.3004989624023],[1389.68505859375,564.398986816406,23.3004989624023],[1367.77709960938,589.398986816406,23.3004989624023],[1366.7470703125,564.398986816406,23.3004989624023],[1354.31201171875,589.398986816406,23.3004989624023],[1352.18603515625,564.398986816406,23.3004989624023],[1389.68505859375,614.398010253906,23.3004989624023],[1377.31701660156,614.398010253906,23.3004989624023],[1381.43908691406,589.398986816406,23.3004989624023],[1368.80700683594,614.398010253906,23.3004989624023],[1368.80810546875,589.398986816406,23.3004989624023],[1356.43908691406,614.398010253906,23.3004989624023],[1357.40502929688,589.398986816406,23.3004989624023],[1360.56201171875,614.398010253906,23.3004989624023],[1348.705078125,614.398010253906,23.3004989624023],[1350.44506835938,589.398986816406,23.3004989624023],[1389.68505859375,606.153015136719,23.3004989624023],[1347.35205078125,589.398986816406,23.3004989624023],[1346.56005859375,589.398986816406,23.3004989624023],[1346.56005859375,594.159912109375,17.0014991760254],[1346.56005859375,589.398986816406,17.0014991760254],[1346.56005859375,605.250427246094,23.3004989624023],[1346.56005859375,614.398010253906,23.3004989624023],[1346.56005859375,614.398010253906,20.8258285522461],[1346.56005859375,614.398010253906,17.0014991760254],[1346.56005859375,564.398986816406,19.10133934021],[1346.56005859375,567.548583984375,23.3004989624023],[1346.56005859375,564.398986816406,17.0020332336426],[1346.56005859375,564.398986816406,23.0018501281738],[1346.56005859375,564.398986816406,23.3004989624023],[1346.56005859375,575.118957519531,17.0014991760254],[1346.56005859375,574.754028320312,23.3004989624023] ]; $facets = [ [0,1,2],[2,3,4],[2,5,0],[4,6,2],[2,6,5],[2,1,3],[7,8,9],[10,9,8],[11,9,10],[12,9,11],[9,13,14],[7,15,16],[10,17,0],[10,0,5],[12,11,6],[18,16,0],[6,19,13],[6,13,9],[9,12,6],[17,18,0],[11,10,5],[11,5,6],[14,16,15],[17,7,18],[16,18,7],[14,15,9],[7,9,15],[7,17,8],[10,8,17],[20,21,22],[23,24,25],[26,23,27],[28,27,23],[29,28,23],[30,29,23],[25,31,32],[22,33,34],[35,36,37],[24,38,39],[21,40,41],[38,42,20],[33,43,44],[6,4,23],[6,23,25],[36,35,1],[1,0,38],[1,38,36],[29,30,4],[25,32,6],[40,42,0],[35,45,1],[4,3,28],[4,28,29],[3,1,45],[3,45,28],[22,34,19],[19,6,32],[19,32,22],[42,38,0],[30,23,4],[0,16,43],[0,43,40],[24,37,36],[38,24,36],[24,23,37],[37,23,26],[22,32,20],[20,32,31],[33,41,40],[43,33,40],[45,35,26],[37,26,35],[33,44,34],[44,43,46],[20,42,21],[40,21,42],[31,39,38],[20,31,38],[33,22,41],[21,41,22],[31,25,39],[24,39,25],[26,27,45],[28,45,27],[47,48,49],[47,50,48],[51,48,50],[52,48,51],[53,48,52],[54,55,56],[57,55,54],[58,55,57],[49,59,47],[60,56,55],[59,56,60],[60,47,59],[48,53,16],[56,13,19],[54,56,19],[56,59,13],[59,49,14],[59,14,13],[49,48,16],[49,16,14],[44,46,60],[44,60,55],[51,50,43],[19,34,58],[19,58,57],[53,52,16],[43,16,52],[43,52,51],[57,54,19],[47,60,46],[55,58,34],[55,34,44],[50,47,46],[50,46,43] ], } elsif ($name eq '40x10') { $vertices = [ [12.8680295944214,29.5799007415771,12],[11.7364797592163,29.8480796813965,12],[11.1571502685547,29.5300102233887,12],[10.5814504623413,29.9830799102783,12],[10,29.6000003814697,12],[9.41855144500732,29.9830799102783,12],[8.84284687042236,29.5300102233887,12],[8.26351833343506,29.8480796813965,12],[7.70256900787354,29.3210391998291,12],[7.13196802139282,29.5799007415771,12],[6.59579277038574,28.9761600494385,12],[6.03920221328735,29.1821594238281,12],[5.53865718841553,28.5003795623779,12],[5,28.6602592468262,12],[4.54657793045044,27.9006500244141,12],[4.02841377258301,28.0212306976318,12],[3.63402199745178,27.1856994628906,12],[3.13758301734924,27.2737407684326,12],[2.81429696083069,26.3659801483154,12],[2.33955597877502,26.4278793334961,12],[2.0993549823761,25.4534206390381,12],[1.64512205123901,25.4950904846191,12],[1.49962198734283,24.4613399505615,12],[1.0636739730835,24.4879894256592,12],[1.02384400367737,23.4042091369629,12],[0.603073298931122,23.4202003479004,12],[0.678958415985107,22.2974300384521,12],[0.269550800323486,22.3061599731445,12],[0.469994693994522,21.1571502685547,12],[0.067615881562233,21.1609306335449,12],[0.399999290704727,20,12],[0,20,12],[0.399999290704727,5,12],[0,5,12],[0.456633001565933,4.2804012298584,12],[0.0615576282143593,4.21782684326172,12],[0.625140011310577,3.5785219669342,12],[0.244717106223106,3.45491504669189,12],[0.901369392871857,2.91164398193359,12],[0.544967114925385,2.73004698753357,12],[1.27852201461792,2.29618692398071,12],[0.954914808273315,2.06107401847839,12],[1.74730801582336,1.74730801582336,12],[1.46446597576141,1.46446597576141,12],[2.29618692398071,1.27852201461792,12],[2.06107401847839,0.954914808273315,12],[2.91164398193359,0.901369392871857,12],[2.73004698753357,0.544967114925385,12],[3.5785219669342,0.625140011310577,12],[3.45491504669189,0.244717106223106,12],[4.2804012298584,0.456633001565933,12],[4.21782684326172,0.0615576282143593,12],[5,0.399999290704727,12],[5,0,12],[19.6000003814697,0.399999290704727,12],[20,0,12],[19.6000003814697,20,12],[20,20,12],[19.5300102233887,21.1571502685547,12],[19.9323806762695,21.1609306335449,12],[19.3210391998291,22.2974300384521,12],[19.7304496765137,22.3061599731445,12],[18.9761600494385,23.4042091369629,12],[19.3969306945801,23.4202003479004,12],[18.5003795623779,24.4613399505615,12],[18.9363307952881,24.4879894256592,12],[17.9006500244141,25.4534206390381,12],[18.3548793792725,25.4950904846191,12],[17.1856994628906,26.3659801483154,12],[17.6604404449463,26.4278793334961,12],[16.3659801483154,27.1856994628906,12],[16.862419128418,27.2737407684326,12],[15.4534196853638,27.9006500244141,12],[15.9715900421143,28.0212306976318,12],[14.4613399505615,28.5003795623779,12],[15,28.6602592468262,12],[13.4042100906372,28.9761600494385,12],[13.9608001708984,29.1821594238281,12],[12.2974300384521,29.3210391998291,12],[7.13196802139282,29.5799007415771,0],[8.26351833343506,29.8480796813965,0],[8.84284687042236,29.5300102233887,0],[9.41855144500732,29.9830799102783,0],[10,29.6000003814697,0],[10.5814504623413,29.9830799102783,0],[11.1571502685547,29.5300102233887,0],[11.7364797592163,29.8480796813965,0],[12.2974300384521,29.3210391998291,0],[12.8680295944214,29.5799007415771,0],[13.4042100906372,28.9761600494385,0],[13.9608001708984,29.1821594238281,0],[14.4613399505615,28.5003795623779,0],[15,28.6602592468262,0],[15.4534196853638,27.9006500244141,0],[15.9715900421143,28.0212306976318,0],[16.3659801483154,27.1856994628906,0],[16.862419128418,27.2737407684326,0],[17.1856994628906,26.3659801483154,0],[17.6604404449463,26.4278793334961,0],[17.9006500244141,25.4534206390381,0],[18.3548793792725,25.4950904846191,0],[18.5003795623779,24.4613399505615,0],[18.9363307952881,24.4879894256592,0],[18.9761600494385,23.4042091369629,0],[19.3969306945801,23.4202003479004,0],[19.3210391998291,22.2974300384521,0],[19.7304496765137,22.3061599731445,0],[19.5300102233887,21.1571502685547,0],[19.9323806762695,21.1609306335449,0],[19.6000003814697,20,0],[20,20,0],[19.6000003814697,0.399999290704727,0],[20,0,0],[5,0.399999290704727,0],[5,0,0],[4.2804012298584,0.456633001565933,0],[4.21782684326172,0.0615576282143593,0],[3.5785219669342,0.625140011310577,0],[3.45491504669189,0.244717106223106,0],[2.91164398193359,0.901369392871857,0],[2.73004698753357,0.544967114925385,0],[2.29618692398071,1.27852201461792,0],[2.06107401847839,0.954914808273315,0],[1.74730801582336,1.74730801582336,0],[1.46446597576141,1.46446597576141,0],[1.27852201461792,2.29618692398071,0],[0.954914808273315,2.06107401847839,0],[0.901369392871857,2.91164398193359,0],[0.544967114925385,2.73004698753357,0],[0.625140011310577,3.5785219669342,0],[0.244717106223106,3.45491504669189,0],[0.456633001565933,4.2804012298584,0],[0.0615576282143593,4.21782684326172,0],[0.399999290704727,5,0],[0,5,0],[0.399999290704727,20,0],[0,20,0],[0.469994693994522,21.1571502685547,0],[0.067615881562233,21.1609306335449,0],[0.678958415985107,22.2974300384521,0],[0.269550800323486,22.3061599731445,0],[1.02384400367737,23.4042091369629,0],[0.603073298931122,23.4202003479004,0],[1.49962198734283,24.4613399505615,0],[1.0636739730835,24.4879894256592,0],[2.0993549823761,25.4534206390381,0],[1.64512205123901,25.4950904846191,0],[2.81429696083069,26.3659801483154,0],[2.33955597877502,26.4278793334961,0],[3.63402199745178,27.1856994628906,0],[3.13758301734924,27.2737407684326,0],[4.54657793045044,27.9006500244141,0],[4.02841377258301,28.0212306976318,0],[5.53865718841553,28.5003795623779,0],[5,28.6602592468262,0],[6.59579277038574,28.9761600494385,0],[6.03920221328735,29.1821594238281,0],[7.70256900787354,29.3210391998291,0] ]; $facets = [ [0,1,2],[2,1,3],[2,3,4],[4,3,5],[4,5,6],[6,5,7],[6,7,8],[8,7,9],[8,9,10],[10,9,11],[10,11,12],[12,11,13],[12,13,14],[14,13,15],[14,15,16],[16,15,17],[16,17,18],[18,17,19],[18,19,20],[20,19,21],[20,21,22],[22,21,23],[22,23,24],[24,23,25],[24,25,26],[26,25,27],[26,27,28],[28,27,29],[28,29,30],[30,29,31],[30,31,32],[32,31,33],[32,33,34],[34,33,35],[34,35,36],[36,35,37],[36,37,38],[38,37,39],[38,39,40],[40,39,41],[40,41,42],[42,41,43],[42,43,44],[44,43,45],[44,45,46],[46,45,47],[46,47,48],[48,47,49],[48,49,50],[50,49,51],[50,51,52],[52,51,53],[52,53,54],[54,53,55],[54,55,56],[56,55,57],[56,57,58],[58,57,59],[58,59,60],[60,59,61],[60,61,62],[62,61,63],[62,63,64],[64,63,65],[64,65,66],[66,65,67],[66,67,68],[68,67,69],[68,69,70],[70,69,71],[70,71,72],[72,71,73],[72,73,74],[74,73,75],[74,75,76],[76,75,77],[76,77,78],[78,77,0],[78,0,2],[79,80,81],[81,80,82],[81,82,83],[83,82,84],[83,84,85],[85,84,86],[85,86,87],[87,86,88],[87,88,89],[89,88,90],[89,90,91],[91,90,92],[91,92,93],[93,92,94],[93,94,95],[95,94,96],[95,96,97],[97,96,98],[97,98,99],[99,98,100],[99,100,101],[101,100,102],[101,102,103],[103,102,104],[103,104,105],[105,104,106],[105,106,107],[107,106,108],[107,108,109],[109,108,110],[109,110,111],[111,110,112],[111,112,113],[113,112,114],[113,114,115],[115,114,116],[115,116,117],[117,116,118],[117,118,119],[119,118,120],[119,120,121],[121,120,122],[121,122,123],[123,122,124],[123,124,125],[125,124,126],[125,126,127],[127,126,128],[127,128,129],[129,128,130],[129,130,131],[131,130,132],[131,132,133],[133,132,134],[133,134,135],[135,134,136],[135,136,137],[137,136,138],[137,138,139],[139,138,140],[139,140,141],[141,140,142],[141,142,143],[143,142,144],[143,144,145],[145,144,146],[145,146,147],[147,146,148],[147,148,149],[149,148,150],[149,150,151],[151,150,152],[151,152,153],[153,152,154],[153,154,155],[155,154,156],[155,156,157],[157,156,79],[157,79,81],[57,110,108],[57,108,59],[59,108,106],[59,106,61],[61,106,104],[61,104,63],[63,104,102],[63,102,65],[65,102,100],[65,100,67],[67,100,98],[67,98,69],[69,98,96],[69,96,71],[71,96,94],[71,94,73],[73,94,92],[73,92,75],[75,92,90],[75,90,77],[77,90,88],[77,88,0],[0,88,86],[0,86,1],[1,86,84],[1,84,3],[3,84,82],[3,82,5],[5,82,80],[5,80,7],[7,80,79],[7,79,9],[9,79,156],[9,156,11],[11,156,154],[11,154,13],[13,154,152],[13,152,15],[15,152,150],[15,150,17],[17,150,148],[17,148,19],[19,148,146],[19,146,21],[21,146,144],[21,144,23],[23,144,142],[23,142,25],[25,142,140],[25,140,27],[27,140,138],[27,138,29],[29,138,136],[29,136,31],[33,31,134],[134,31,136],[33,134,132],[33,132,35],[35,132,130],[35,130,37],[37,130,128],[37,128,39],[39,128,126],[39,126,41],[41,126,124],[41,124,43],[43,124,122],[43,122,45],[45,122,120],[45,120,47],[47,120,118],[47,118,49],[49,118,116],[49,116,51],[51,116,114],[51,114,53],[55,53,112],[112,53,114],[57,55,110],[110,55,112],[30,135,137],[30,137,28],[28,137,139],[28,139,26],[26,139,141],[26,141,24],[24,141,143],[24,143,22],[22,143,145],[22,145,20],[20,145,147],[20,147,18],[18,147,149],[18,149,16],[16,149,151],[16,151,14],[14,151,153],[14,153,12],[12,153,155],[12,155,10],[10,155,157],[10,157,8],[8,157,81],[8,81,6],[6,81,83],[6,83,4],[4,83,85],[4,85,2],[2,85,87],[2,87,78],[78,87,89],[78,89,76],[76,89,91],[76,91,74],[74,91,93],[74,93,72],[72,93,95],[72,95,70],[70,95,97],[70,97,68],[68,97,99],[68,99,66],[66,99,101],[66,101,64],[64,101,103],[64,103,62],[62,103,105],[62,105,60],[60,105,107],[60,107,58],[58,107,109],[58,109,56],[30,32,135],[135,32,133],[52,113,115],[52,115,50],[50,115,117],[50,117,48],[48,117,119],[48,119,46],[46,119,121],[46,121,44],[44,121,123],[44,123,42],[42,123,125],[42,125,40],[40,125,127],[40,127,38],[38,127,129],[38,129,36],[36,129,131],[36,131,34],[34,131,133],[34,133,32],[52,54,113],[113,54,111],[54,56,111],[111,56,109] ], } elsif ($name eq 'sloping_hole') { $vertices = [ [-20,-20,-5],[-20,-20,5],[-20,20,-5],[-20,20,5],[20,-20,-5],[20,-20,5],[4.46294021606445,7.43144989013672,-5],[20,20,-5],[-19.1420993804932,0,-5],[-18.8330993652344,-2.07911992073059,-5],[-17.9195003509521,-4.06736993789673,-5],[-16.4412002563477,-5.87785005569458,-5],[-14.4629001617432,-7.43144989013672,-5],[-12.0711002349854,-8.66024971008301,-5],[-9.37016010284424,-9.51056003570557,-5],[-3.5217399597168,-9.94521999359131,-5],[-6.4782600402832,-9.94521999359131,-5],[-0.629840016365051,-9.51056003570557,-5],[2.07106995582581,-8.66024971008301,-5],[6.44122982025146,-5.87785005569458,-5],[4.46294021606445,-7.43144989013672,-5],[-12.0711002349854,8.66024971008301,-5],[-9.37016010284424,9.51056003570557,-5],[7.91947984695435,-4.06736993789673,-5],[8.83310031890869,-2.07911992073059,-5],[-6.4782600402832,9.94521999359131,-5],[-0.629840016365051,9.51056003570557,-5],[2.07106995582581,8.66024971008301,-5],[9.14214038848877,0,-5],[8.83310031890869,2.07911992073059,-5],[-3.5217399597168,9.94521999359131,-5],[7.91947984695435,4.06736993789673,-5],[6.44122982025146,5.87785005569458,-5],[-14.4629001617432,7.43144989013672,-5],[-16.4412002563477,5.87785005569458,-5],[-17.9195003509521,4.06736993789673,-5],[-18.8330993652344,2.07911992073059,-5],[20,20,5],[3.5217399597168,-9.94521999359131,5],[-8.83310031890869,-2.07911992073059,5],[-9.14214038848877,0,5],[-8.83310031890869,2.07911992073059,5],[6.4782600402832,-9.94521999359131,5],[-7.91947984695435,4.06736993789673,5],[-6.44122982025146,5.87785005569458,5],[-4.46294021606445,7.43144989013672,5],[-2.07106995582581,8.66024971008301,5],[0.629840016365051,9.51056003570557,5],[12.0711002349854,-8.66024971008301,5],[9.37016010284424,-9.51056003570557,5],[3.5217399597168,9.94521999359131,5],[6.4782600402832,9.94521999359131,5],[9.37016010284424,9.51056003570557,5],[12.0711002349854,8.66024971008301,5],[14.4629001617432,7.43144989013672,5],[16.4412002563477,-5.87785005569458,5],[14.4629001617432,-7.43144989013672,5],[16.4412002563477,5.87785005569458,5],[17.9195003509521,4.06736993789673,5],[18.8330993652344,-2.07911992073059,5],[17.9195003509521,-4.06736993789673,5],[18.8330993652344,2.07911992073059,5],[19.1420993804932,0,5],[0.629840016365051,-9.51056003570557,5],[-2.07106995582581,-8.66024971008301,5],[-4.46294021606445,-7.43144989013672,5],[-6.44122982025146,-5.87785005569458,5],[-7.91947984695435,-4.06736993789673,5] ]; $facets = [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,2,7],[0,2,8],[0,8,9],[0,9,10],[0,10,11],[0,11,12],[0,12,13],[0,13,4],[13,14,4],[15,4,16],[17,4,15],[18,4,17],[19,4,20],[18,20,4],[21,2,22],[4,19,23],[4,23,7],[23,24,7],[22,2,25],[26,2,27],[28,29,7],[25,2,30],[29,31,7],[30,2,26],[31,32,7],[27,2,6],[32,6,7],[28,7,24],[33,2,21],[34,2,33],[35,2,34],[36,2,35],[8,2,36],[16,4,14],[2,3,7],[7,3,37],[38,1,5],[3,1,39],[3,39,40],[3,40,41],[42,38,5],[3,41,43],[3,43,44],[37,3,45],[37,45,46],[37,46,47],[48,49,5],[37,47,50],[49,42,5],[37,50,51],[37,51,52],[37,52,53],[37,53,54],[55,56,5],[37,54,57],[37,57,58],[59,60,5],[37,58,61],[37,62,5],[37,61,62],[62,59,5],[60,55,5],[63,1,38],[64,1,63],[65,1,64],[66,1,65],[67,1,66],[39,1,67],[44,45,3],[56,48,5],[5,4,7],[37,5,7],[41,40,36],[36,40,8],[39,9,40],[40,9,8],[43,41,35],[35,41,36],[44,43,34],[34,43,35],[33,45,44],[34,33,44],[21,46,45],[33,21,45],[22,47,46],[21,22,46],[25,50,47],[22,25,47],[30,51,50],[25,30,50],[26,52,51],[30,26,51],[27,53,52],[26,27,52],[6,54,53],[27,6,53],[32,57,54],[6,32,54],[31,58,57],[32,31,57],[29,61,58],[31,29,58],[28,62,61],[29,28,61],[59,62,28],[24,59,28],[60,59,24],[23,60,24],[55,60,23],[19,55,23],[55,19,56],[56,19,20],[56,20,48],[48,20,18],[48,18,49],[49,18,17],[49,17,42],[42,17,15],[42,15,38],[38,15,16],[38,16,63],[63,16,14],[63,14,64],[64,14,13],[64,13,65],[65,13,12],[65,12,66],[66,12,11],[66,11,67],[67,11,10],[67,10,39],[39,10,9] ], } elsif ($name eq 'ipadstand') { $vertices = [ [17.4344673156738,-2.69879599481136e-16,9.5],[14.2814798355103,10,9.5],[0,0,9.5],[31.7159481048584,10,9.5],[62.2344741821289,2.06667568800577e-16,20],[31.7159481048584,10,20],[17.4344673156738,-2.69879599481136e-16,20],[62.2344741821289,10,20],[98.2079696655273,10,0],[98.2079696655273,8.56525380796383e-16,10],[98.2079696655273,0,0],[98.2079696655273,10,20],[98.2079696655273,0,20],[81.6609649658203,-4.39753856997999e-16,10],[90.0549850463867,10,10],[78.5079803466797,10,10],[93.2079696655273,8.56525380796383e-16,10],[14.2814798355103,10,20],[0,0,20],[87.4344711303711,2.81343962782118e-15,20],[84.2814788818359,10,20],[0,10,20],[0,0,0],[0,10,0],[62.2344741821289,2.06667568800577e-16,30],[66.9609756469727,10,30],[62.2344741821289,10,30],[70.1139602661133,8.5525763717214e-16,30],[67.7053375244141,10,28.7107200622559],[71.6787109375,1.24046736339707e-15,27.2897701263428] ]; $facets = [ [0,1,2],[1,0,3],[4,5,6],[5,4,7],[8,9,10],[9,11,12],[11,9,8],[13,14,15],[14,13,16],[17,2,1],[2,17,18],[19,11,20],[11,19,12],[17,21,18],[21,2,18],[2,21,22],[22,21,23],[8,22,23],[22,8,10],[24,25,26],[25,24,27],[23,1,8],[1,23,21],[1,21,17],[5,15,3],[15,5,7],[15,7,28],[28,7,26],[28,26,25],[8,14,11],[14,8,3],[3,8,1],[14,3,15],[11,14,20],[26,4,24],[4,26,7],[12,16,9],[16,12,19],[29,4,13],[4,29,24],[24,29,27],[9,22,10],[22,9,0],[0,9,16],[0,16,13],[0,13,6],[6,13,4],[2,22,0],[19,14,16],[14,19,20],[15,29,13],[29,25,27],[25,29,15],[25,15,28],[6,3,0],[3,6,5] ]; } elsif ($name eq 'A') { $vertices = [ [513.075988769531,51.6074333190918,36.0009002685547],[516.648803710938,51.7324333190918,36.0009002685547],[513.495178222656,51.7324333190918,36.0009002685547],[489.391204833984,51.4824333190918,24.0011005401611],[488.928588867188,51.7324333190918,24.0011005401611],[492.06201171875,51.7324333190918,24.0011005401611],[496.840393066406,51.2324333190918,24.0011005401611],[495.195404052734,51.7324333190918,24.0011005401611],[498.981994628906,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,24.0011005401611],[510.342010498047,51.7324333190918,24.0011005401611],[507.163818359375,51.6074333190918,24.0011005401611],[512.515380859375,54.7190322875977,36.0009002685547],[514.161987304688,54.5058326721191,36.0009002685547],[493.06201171875,54.7190322875977,36.0009002685547],[495.195404052734,51.7324333190918,36.0009002685547],[496.195404052734,54.7190322875977,36.0009002685547],[497.195404052734,57.7058334350586,36.0009002685547],[500.851989746094,60.2658309936523,36.0009002685547],[498.915405273438,62.8258323669434,36.0009002685547],[506.701995849609,62.8258323669434,36.0009002685547],[503.648590087891,60.2658309936523,36.0009002685547],[508.381805419922,57.7058334350586,36.0009002685547],[496.418792724609,60.052433013916,36.0009002685547],[506.515197753906,72.2124328613281,36.0009002685547],[502.808807373047,74.5324325561523,36.0009002685547],[503.781982421875,71.6058349609375,36.0009002685547],[515.358764648438,55.4658317565918,36.0009002685547],[499.375183105469,76.9058380126953,36.0009002685547],[501.168792724609,78.0658340454102,36.0009002685547],[504.568786621094,78.0658340454102,36.0009002685547],[506.32861328125,81.599235534668,36.0009002685547],[502.928588867188,81.599235534668,36.0009002685547],[499.528594970703,81.599235534668,36.0009002685547],[498.20361328125,77.8658294677734,36.0009002685547],[495.195404052734,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,27.0011005401611],[506.555206298828,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,36.0009002685547],[510.342010498047,51.7324333190918,36.0009002685547],[512.515380859375,54.7190322875977,24.0011005401611],[509.361999511719,54.7190322875977,24.0011005401611],[508.381805419922,57.7058334350586,24.0011005401611],[506.701995849609,62.8258323669434,24.0011005401611],[509.188812255859,60.052433013916,24.0011005401611],[493.06201171875,54.7190322875977,24.0011005401611],[503.648590087891,60.2658309936523,24.0011005401611],[500.851989746094,60.2658309936523,24.0011005401611],[498.915405273438,62.8258323669434,24.0011005401611],[502.808807373047,62.8258323669434,24.0011005401611],[491.425201416016,54.5058326721191,24.0011005401611],[506.421813964844,76.9058380126953,24.0011005401611],[502.808807373047,74.5324325561523,24.0011005401611],[504.568786621094,78.0658340454102,24.0011005401611],[506.32861328125,81.599235534668,24.0011005401611],[507.618804931641,77.8658294677734,24.0011005401611],[499.221801757812,72.2124328613281,24.0011005401611],[501.835388183594,71.6058349609375,24.0011005401611],[501.168792724609,78.0658340454102,24.0011005401611],[499.528594970703,81.599235534668,24.0011005401611],[502.048583984375,79.8324356079102,24.0011005401611],[490.253601074219,55.4658317565918,24.0011005401611],[488.928588867188,51.7324333190918,30.0011005401611],[488.928588867188,51.7324333190918,36.0009002685547],[490.253601074219,55.4658317565918,31.5009002685547],[498.20361328125,77.8658294677734,34.5009002685547],[508.381805419922,57.7058334350586,30.0011005401611],[505.585388183594,57.7058334350586,27.0011005401611],[502.788818359375,57.7058334350586,36.0009002685547],[499.992004394531,57.7058334350586,33.0009002685547],[509.851989746094,53.2258338928223,33.0009002685547],[509.361999511719,54.7190322875977,36.0009002685547],[508.871795654297,56.2124328613281,27.0011005401611],[496.695404052734,56.2124328613281,33.0009002685547],[495.695404052734,53.2258338928223,27.0011005401611],[506.32861328125,81.599235534668,30.0011005401611],[507.618804931641,77.8658294677734,25.5011005401611],[515.358764648438,55.4658317565918,34.5009002685547],[501.228607177734,81.599235534668,33.0009002685547],[504.628601074219,81.599235534668,27.0011005401611],[503.781982421875,71.6058349609375,33.0009002685547],[502.808807373047,74.5324325561523,30.0011005401611],[498.915405273438,62.8258323669434,30.0011005401611],[500.861999511719,62.8258323669434,27.0011005401611],[502.808807373047,62.8258323669434,36.0009002685547],[504.755187988281,62.8258323669434,33.0009002685547],[501.835388183594,71.6058349609375,33.0009002685547],[499.888793945312,65.7524337768555,33.0009002685547],[499.888793945312,65.7524337768555,36.0009002685547],[513.128601074219,51.4824333190918,36.0009002685547],[513.075988769531,51.6074333190918,24.0011005401611],[516.648803710938,51.7324333190918,24.0011005401611],[513.128601074219,51.4824333190918,24.0011005401611],[513.495178222656,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,36.0009002685547],[507.163818359375,51.6074333190918,36.0009002685547],[490.337799072266,51.4824333190918,24.0011005401611],[489.391204833984,51.4824333190918,36.0009002685547],[492.06201171875,51.7324333190918,36.0009002685547],[490.337799072266,51.4824333190918,36.0009002685547],[513.233764648438,51.2324333190918,24.0011005401611],[513.233764648438,51.2324333190918,36.0009002685547],[504.773803710938,51.4824333190918,36.0009002685547],[504.773803710938,51.4824333190918,24.0011005401611],[489.266998291016,51.2324333190918,24.0011005401611],[489.266998291016,51.2324333190918,36.0009002685547],[490.253601074219,55.4658317565918,25.5011005401611],[499.528594970703,81.599235534668,30.0011005401611],[498.20361328125,77.8658294677734,31.5009002685547],[515.358764648438,55.4658317565918,28.5011005401611],[515.358764648438,55.4658317565918,25.5011005401611],[495.246795654297,61.0124320983887,36.0009002685547],[490.253601074219,55.4658317565918,34.5009002685547],[490.253601074219,55.4658317565918,36.0009002685547],[494.228607177734,66.6658325195312,24.0011005401611],[499.068786621094,67.5192337036133,24.0011005401611],[498.20361328125,77.8658294677734,25.5011005401611],[498.20361328125,77.8658294677734,24.0011005401611],[506.608795166016,67.5192337036133,36.0009002685547],[509.09521484375,64.7458343505859,36.0009002685547],[507.618804931641,77.8658294677734,34.5009002685547],[507.618804931641,77.8658294677734,36.0009002685547],[510.385406494141,61.0124320983887,24.0011005401611],[515.358764648438,55.4658317565918,24.0011005401611],[489.32861328125,47.7324333190918,31.5009002685547],[492.95361328125,47.7324333190918,33.5634994506836],[489.32861328125,47.7324333190918,34.5009002685547],[489.32861328125,47.7324333190918,28.5011005401611],[489.32861328125,47.7324333190918,25.5011005401611],[492.95361328125,47.7324333190918,26.4385013580322],[492.95361328125,47.7324333190918,30.5635013580322],[492.95361328125,47.7324333190918,32.0634994506836],[492.95361328125,47.7324333190918,31.3135013580322],[492.95361328125,47.7324333190918,35.4384994506836],[489.32861328125,47.7324333190918,36.0009002685547],[492.95361328125,47.7324333190918,34.3134994506836],[492.95361328125,47.7324333190918,34.6884994506836],[492.95361328125,47.7324333190918,27.9385013580322],[492.95361328125,47.7324333190918,28.6885013580322],[492.95361328125,47.7324333190918,29.0635013580322],[489.32861328125,47.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,24.5635013580322],[492.95361328125,47.7324333190918,25.6885013580322],[492.95361328125,47.7324333190918,25.3135013580322],[492.95361328125,47.7324333190918,24.1885013580322],[492.95361328125,47.7324333190918,24.0011005401611],[513.443786621094,50.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,35.8134994506836],[492.95361328125,47.7324333190918,36.0009002685547],[513.443786621094,50.7324333190918,36.0009002685547],[506.350402832031,51.4824333190918,36.0009002685547],[506.350402832031,51.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,24.0011005401611],[492.638793945312,48.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,36.0009002685547],[492.638793945312,48.4824333190918,36.0009002685547],[490.089599609375,50.9824333190918,36.0009002685547],[490.089599609375,50.9824333190918,24.0011005401611],[510.342010498047,51.7324333190918,30.0011005401611],[499.068786621094,67.5192337036133,36.0009002685547],[494.228607177734,66.6658325195312,36.0009002685547],[499.375183105469,76.9058380126953,24.0011005401611],[506.421813964844,76.9058380126953,36.0009002685547],[506.608795166016,67.5192337036133,24.0011005401611],[505.728607177734,65.7524337768555,24.0011005401611],[509.09521484375,64.7458343505859,24.0011005401611],[506.701995849609,62.8258323669434,30.0011005401611],[505.728607177734,65.7524337768555,27.0011005401611],[501.835388183594,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,27.0011005401611],[494.228607177734,66.6658325195312,30.0011005401611],[495.553588867188,70.3992309570312,28.5011005401611],[492.903594970703,62.9324340820312,28.5011005401611],[495.553588867188,70.3992309570312,31.5009002685547],[492.903594970703,62.9324340820312,31.5009002685547],[511.488800048828,66.6658325195312,24.0011005401611],[511.488800048828,66.6658325195312,30.0011005401611],[512.778564453125,62.9324340820312,28.5011005401611],[515.358764648438,55.4658317565918,31.5009002685547],[507.618804931641,77.8658294677734,31.5009002685547],[510.198791503906,70.3992309570312,28.5011005401611],[511.488800048828,66.6658325195312,36.0009002685547],[512.778564453125,62.9324340820312,31.5009002685547],[510.198791503906,70.3992309570312,31.5009002685547],[502.788818359375,57.7058334350586,24.0011005401611],[497.195404052734,57.7058334350586,30.0011005401611],[492.903594970703,62.9324340820312,34.5009002685547],[492.903594970703,62.9324340820312,36.0009002685547],[495.553588867188,70.3992309570312,24.0011005401611],[496.725189208984,69.4392318725586,24.0011005401611],[495.553588867188,70.3992309570312,25.5011005401611],[495.246795654297,61.0124320983887,24.0011005401611],[492.903594970703,62.9324340820312,25.5011005401611],[492.903594970703,62.9324340820312,24.0011005401611],[495.553588867188,70.3992309570312,36.0009002685547],[496.725189208984,69.4392318725586,36.0009002685547],[495.553588867188,70.3992309570312,34.5009002685547],[510.198791503906,70.3992309570312,36.0009002685547],[509.002014160156,69.4392318725586,36.0009002685547],[510.198791503906,70.3992309570312,34.5009002685547],[512.778564453125,62.9324340820312,25.5011005401611],[512.778564453125,62.9324340820312,24.0011005401611],[510.198791503906,70.3992309570312,24.0011005401611],[509.002014160156,69.4392318725586,24.0011005401611],[510.198791503906,70.3992309570312,25.5011005401611],[510.385406494141,61.0124320983887,36.0009002685547],[512.778564453125,62.9324340820312,34.5009002685547],[512.778564453125,62.9324340820312,36.0009002685547],[496.840393066406,51.2324333190918,36.0009002685547],[498.981994628906,51.7324333190918,36.0009002685547],[498.981994628906,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,24.0011005401611],[506.555206298828,51.7324333190918,27.0011005401611],[503.82861328125,47.7324333190918,30.7509002685547],[507.45361328125,47.7324333190918,32.8134994506836],[503.82861328125,47.7324333190918,33.7509002685547],[503.82861328125,47.7324333190918,29.2511005401611],[503.82861328125,47.7324333190918,26.2511005401611],[507.45361328125,47.7324333190918,27.1885013580322],[493.921813964844,57.2792320251465,36.0009002685547],[491.425201416016,54.5058326721191,36.0009002685547],[497.195404052734,57.7058334350586,24.0011005401611],[496.418792724609,60.052433013916,24.0011005401611],[509.188812255859,60.052433013916,36.0009002685547],[511.675415039062,57.2792320251465,24.0011005401611],[514.161987304688,54.5058326721191,24.0011005401611],[507.45361328125,47.7324333190918,34.3134994506836],[503.82861328125,47.7324333190918,35.2509002685547],[507.45361328125,47.7324333190918,25.6885013580322],[503.82861328125,47.7324333190918,24.7511005401611],[500.20361328125,47.7324333190918,31.6885013580322],[500.20361328125,47.7324333190918,28.3135013580322],[500.20361328125,47.7324333190918,30.1885013580322],[507.45361328125,47.7324333190918,29.8135013580322],[507.45361328125,47.7324333190918,31.3135013580322],[507.45361328125,47.7324333190918,30.5635013580322],[503.82861328125,47.7324333190918,36.0009002685547],[507.45361328125,47.7324333190918,35.4384994506836],[507.45361328125,47.7324333190918,35.0634994506836],[507.45361328125,47.7324333190918,28.6885013580322],[507.45361328125,47.7324333190918,29.4385013580322],[503.82861328125,47.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,34.6884994506836],[500.20361328125,47.7324333190918,33.1884994506836],[500.20361328125,47.7324333190918,33.9384994506836],[500.20361328125,47.7324333190918,25.3135013580322],[500.20361328125,47.7324333190918,26.8135013580322],[500.20361328125,47.7324333190918,26.0635013580322],[500.20361328125,47.7324333190918,30.9385013580322],[500.20361328125,47.7324333190918,35.0634994506836],[500.20361328125,47.7324333190918,35.4384994506836],[500.20361328125,47.7324333190918,29.0635013580322],[500.20361328125,47.7324333190918,29.4385013580322],[500.20361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.1885013580322],[507.45361328125,47.7324333190918,24.0011005401611],[513.86376953125,49.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,35.8134994506836],[507.45361328125,47.7324333190918,36.0009002685547],[513.86376953125,49.7324333190918,36.0009002685547],[500.20361328125,47.7324333190918,24.1885013580322],[500.20361328125,47.7324333190918,24.0011005401611],[502.988800048828,49.7324333190918,24.0011005401611],[500.20361328125,47.7324333190918,35.8134994506836],[500.20361328125,47.7324333190918,36.0009002685547],[502.988800048828,49.7324333190918,36.0009002685547],[504.755187988281,62.8258323669434,27.0011005401611],[499.205383300781,51.2324333190918,36.0009002685547],[498.786193847656,51.1074333190918,36.0009002685547],[502.358795166016,51.2324333190918,36.0009002685547],[499.205383300781,51.2324333190918,24.0011005401611],[502.358795166016,51.2324333190918,24.0011005401611],[498.786193847656,51.1074333190918,24.0011005401611],[502.568786621094,50.7324333190918,24.0011005401611],[505.931213378906,51.3574333190918,24.0011005401611],[509.503601074219,51.4824333190918,24.0011005401611],[502.568786621094,50.7324333190918,36.0009002685547],[505.931213378906,51.3574333190918,36.0009002685547],[509.503601074219,51.4824333190918,36.0009002685547],[499.048583984375,50.4824333190918,36.0009002685547],[492.428588867188,48.9824333190918,36.0009002685547],[499.048583984375,50.4824333190918,24.0011005401611],[492.428588867188,48.9824333190918,24.0011005401611],[506.088806152344,50.9824333190918,24.0011005401611],[506.036010742188,51.1074333190918,24.0011005401611],[506.088806152344,50.9824333190918,36.0009002685547],[506.036010742188,51.1074333190918,36.0009002685547],[498.891204833984,50.8574333190918,36.0009002685547],[498.943786621094,50.7324333190918,36.0009002685547],[498.891204833984,50.8574333190918,24.0011005401611],[498.943786621094,50.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,24.0011005401611],[499.783813476562,48.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,36.0009002685547],[499.783813476562,48.7324333190918,36.0009002685547],[506.403594970703,50.2324333190918,24.0011005401611],[506.298797607422,50.4824333190918,24.0011005401611],[506.403594970703,50.2324333190918,36.0009002685547],[506.298797607422,50.4824333190918,36.0009002685547],[501.228607177734,81.599235534668,27.0011005401611],[502.928588867188,81.599235534668,24.0011005401611],[499.2587890625,49.9824333190918,36.0009002685547],[499.363800048828,49.7324333190918,36.0009002685547],[499.2587890625,49.9824333190918,24.0011005401611],[499.363800048828,49.7324333190918,24.0011005401611],[496.695404052734,56.2124328613281,27.0011005401611],[496.195404052734,54.7190322875977,24.0011005401611],[509.851989746094,53.2258338928223,27.0011005401611],[493.464782714844,51.1074333190918,36.0009002685547],[493.464782714844,51.1074333190918,24.0011005401611],[502.768798828125,51.7324333190918,24.0011005401611],[500.215789794922,51.3574333190918,24.0011005401611],[497.628601074219,51.2324333190918,24.0011005401611],[502.768798828125,51.7324333190918,36.0009002685547],[500.215789794922,51.3574333190918,36.0009002685547],[497.628601074219,51.2324333190918,36.0009002685547],[507.033813476562,48.7324333190918,24.0011005401611],[506.823791503906,49.2324333190918,24.0011005401611],[507.033813476562,48.7324333190918,36.0009002685547],[506.823791503906,49.2324333190918,36.0009002685547],[494.4501953125,51.1074333190918,24.0011005401611],[494.4501953125,51.1074333190918,36.0009002685547],[500.807006835938,51.3574333190918,36.0009002685547],[503.591186523438,51.4824333190918,36.0009002685547],[503.591186523438,51.4824333190918,24.0011005401611],[500.807006835938,51.3574333190918,24.0011005401611],[505.728607177734,65.7524337768555,36.0009002685547],[505.728607177734,65.7524337768555,33.0009002685547],[499.221801757812,72.2124328613281,36.0009002685547],[501.835388183594,71.6058349609375,36.0009002685547],[506.515197753906,72.2124328613281,24.0011005401611],[503.781982421875,71.6058349609375,24.0011005401611],[503.781982421875,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,24.0011005401611],[495.695404052734,53.2258338928223,33.0009002685547],[516.648803710938,51.7324333190918,30.0011005401611],[498.20361328125,77.8658294677734,28.5011005401611],[505.585388183594,57.7058334350586,33.0009002685547],[508.871795654297,56.2124328613281,33.0009002685547],[499.992004394531,57.7058334350586,27.0011005401611],[504.628601074219,81.599235534668,33.0009002685547],[500.861999511719,62.8258323669434,33.0009002685547],[496.878601074219,74.1324310302734,27.0011005401611],[496.878601074219,74.1324310302734,33.0009002685547],[491.57861328125,59.199031829834,27.0011005401611],[490.253601074219,55.4658317565918,28.5011005401611],[491.57861328125,59.199031829834,33.0009002685547],[514.068786621094,59.199031829834,27.0011005401611],[514.068786621094,59.199031829834,33.0009002685547],[508.908813476562,74.1324310302734,27.0011005401611],[507.618804931641,77.8658294677734,28.5011005401611],[508.908813476562,74.1324310302734,33.0009002685547],[491.271789550781,50.9824333190918,36.0009002685547],[490.877807617188,50.9824333190918,36.0009002685547],[491.271789550781,50.9824333190918,24.0011005401611],[490.877807617188,50.9824333190918,24.0011005401611],[495.213806152344,50.9824333190918,36.0009002685547],[493.636993408203,50.9824333190918,36.0009002685547],[495.213806152344,50.9824333190918,24.0011005401611],[493.636993408203,50.9824333190918,24.0011005401611],[503.985412597656,51.4824333190918,36.0009002685547],[503.985412597656,51.4824333190918,24.0011005401611],[511.675415039062,57.2792320251465,36.0009002685547],[493.921813964844,57.2792320251465,24.0011005401611],[502.768798828125,51.7324333190918,30.0011005401611],[506.555206298828,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,30.0011005401611],[492.848815917969,50.9824333190918,24.0011005401611],[492.848815917969,50.9824333190918,36.0009002685547],[500.861999511719,68.6792297363281,36.0009002685547],[500.861999511719,68.6792297363281,24.0011005401611],[496.878601074219,74.1324310302734,24.0011005401611],[496.878601074219,74.1324310302734,36.0009002685547],[504.755187988281,68.6792297363281,24.0011005401611],[504.755187988281,68.6792297363281,36.0009002685547],[508.908813476562,74.1324310302734,36.0009002685547],[508.908813476562,74.1324310302734,24.0011005401611],[505.728607177734,65.7524337768555,30.0011005401611],[504.755187988281,68.6792297363281,30.0011005401611],[503.781982421875,71.6058349609375,30.0011005401611],[500.861999511719,68.6792297363281,30.0011005401611],[499.888793945312,65.7524337768555,30.0011005401611],[501.835388183594,71.6058349609375,30.0011005401611],[491.57861328125,59.199031829834,24.0011005401611],[491.57861328125,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,24.0011005401611],[511.07861328125,47.7324333190918,34.8759002685547],[511.07861328125,47.7324333190918,31.8759002685547],[514.70361328125,47.7324333190918,33.9384994506836],[511.07861328125,47.7324333190918,25.1261005401611],[514.70361328125,47.7324333190918,26.0635013580322],[511.07861328125,47.7324333190918,28.1261005401611],[502.788818359375,57.7058334350586,30.0011005401611],[502.048583984375,79.8324356079102,36.0009002685547],[514.70361328125,47.7324333190918,30.9385013580322],[511.07861328125,47.7324333190918,30.3759002685547],[514.70361328125,47.7324333190918,29.0635013580322],[511.07861328125,47.7324333190918,29.6261005401611],[496.57861328125,47.7324333190918,31.1259002685547],[496.57861328125,47.7324333190918,32.6259002685547],[496.57861328125,47.7324333190918,34.1259002685547],[496.57861328125,47.7324333190918,28.8761005401611],[496.57861328125,47.7324333190918,27.3761005401611],[496.57861328125,47.7324333190918,25.8761005401611],[496.57861328125,47.7324333190918,29.6261005401611],[514.70361328125,47.7324333190918,35.4384994506836],[511.07861328125,47.7324333190918,35.6259002685547],[514.70361328125,47.7324333190918,24.5635013580322],[511.07861328125,47.7324333190918,24.3761005401611],[496.57861328125,47.7324333190918,34.8759002685547],[496.57861328125,47.7324333190918,25.1261005401611],[496.57861328125,47.7324333190918,35.6259002685547],[496.57861328125,47.7324333190918,24.3761005401611],[511.07861328125,47.7324333190918,36.0009002685547],[511.07861328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,30.1885013580322],[514.70361328125,47.7324333190918,35.8134994506836],[514.70361328125,47.7324333190918,29.8135013580322],[514.70361328125,47.7324333190918,24.1885013580322],[496.57861328125,47.7324333190918,36.0009002685547],[496.57861328125,47.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,36.0009002685547],[514.70361328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,24.0011005401611],[502.808807373047,62.8258323669434,30.0011005401611],[509.608795166016,51.2324333190918,24.0011005401611],[509.608795166016,51.2324333190918,36.0009002685547],[491.641204833984,50.8574333190918,24.0011005401611],[495.423797607422,50.4824333190918,36.0009002685547],[495.423797607422,50.4824333190918,24.0011005401611],[491.641204833984,50.8574333190918,36.0009002685547],[495.528594970703,50.2324333190918,24.0011005401611],[492.0087890625,49.9824333190918,24.0011005401611],[509.818786621094,50.7324333190918,24.0011005401611],[495.948608398438,49.2324333190918,36.0009002685547],[495.528594970703,50.2324333190918,36.0009002685547],[495.948608398438,49.2324333190918,24.0011005401611],[509.818786621094,50.7324333190918,36.0009002685547],[492.0087890625,49.9824333190918,36.0009002685547],[491.956207275391,50.1074333190918,24.0011005401611],[491.956207275391,50.1074333190918,36.0009002685547],[502.928588867188,81.599235534668,30.0011005401611],[491.851013183594,50.3574333190918,36.0009002685547],[491.851013183594,50.3574333190918,24.0011005401611],[496.195404052734,54.7190322875977,30.0011005401611],[509.361999511719,54.7190322875977,30.0011005401611],[488.632598876953,51.7256317138672,30.0011005401611],[488.632598876953,51.7256317138672,29.5091018676758],[488.632598876953,51.7188339233398,24.0011005401611],[488.632598876953,51.7256317138672,27.4929008483887],[488.632598876953,51.7324333190918,30.0011005401611],[488.632598876953,51.7324333190918,29.0175018310547],[488.632598876953,51.7324333190918,24.9847011566162],[488.632598876953,51.7324333190918,24.0011005401611],[488.632598876953,51.7188339233398,30.0011005401611],[488.632598876953,51.7176322937012,24.0011005401611],[488.632598876953,51.7182312011719,30.0011005401611],[488.632598876953,51.7176322937012,30.0011005401611],[488.632598876953,51.715030670166,24.0011005401611],[488.632598876953,51.7162322998047,30.0011005401611],[488.632598876953,50.761833190918,24.0011005401611],[488.632598876953,50.7578315734863,24.0011005401611],[488.632598876953,50.7598342895508,30.0011005401611],[488.632598876953,50.7522315979004,24.0011005401611],[488.632598876953,49.7838325500488,24.0011005401611],[488.632598876953,50.2680320739746,30.0011005401611],[488.632598876953,51.7046318054199,24.0011005401611],[488.632598876953,51.709831237793,30.0011005401611],[488.632598876953,50.9120330810547,24.0011005401611],[488.632598876953,50.8882331848145,24.0011005401611],[488.632598876953,50.9002304077148,30.0011005401611],[488.632598876953,47.7324333190918,24.0370998382568],[488.632598876953,48.5612335205078,30.0011005401611],[488.632598876953,47.7324333190918,24.0011005401611],[488.632598876953,47.7324333190918,24.1091003417969],[488.632598876953,48.5612335205078,30.0189018249512],[488.632598876953,47.7324333190918,25.3211002349854],[488.632598876953,48.5612335205078,30.0551013946533],[488.632598876953,47.7324333190918,25.4651012420654],[488.632598876953,48.5612335205078,30.6609001159668],[488.632598876953,47.7324333190918,25.5371017456055],[488.632598876953,48.5612335205078,30.7329006195068],[488.632598876953,47.7324333190918,25.6091003417969],[488.632598876953,48.5612335205078,30.7689018249512],[488.632598876953,47.7324333190918,25.8971004486084],[488.632598876953,48.5612335205078,30.8051013946533],[488.632598876953,47.7324333190918,28.321102142334],[488.632598876953,48.5612335205078,30.9491004943848],[488.632598876953,47.7324333190918,28.4651012420654],[488.632598876953,48.5612335205078,32.1609001159668],[488.632598876953,47.7324333190918,28.5371017456055],[488.632598876953,48.5612335205078,32.2329025268555],[488.632598876953,47.7324333190918,28.6811008453369],[488.632598876953,48.5612335205078,32.2689018249512],[488.632598876953,47.7324333190918,31.1049003601074],[488.632598876953,48.5612335205078,32.3411026000977],[488.632598876953,47.7324333190918,31.3929004669189],[488.632598876953,49.3900299072266,36.0009002685547],[488.632598876953,47.7324333190918,31.536901473999],[488.632598876953,47.7324333190918,31.6809005737305],[488.632598876953,47.7324333190918,34.1049003601074],[488.632598876953,47.7324333190918,34.3929023742676],[488.632598876953,47.7324333190918,34.464900970459],[488.632598876953,47.7324333190918,34.5369033813477],[488.632598876953,47.7324333190918,34.6809005737305],[488.632598876953,47.7324333190918,35.8929023742676],[488.632598876953,47.7324333190918,35.964900970459],[488.632598876953,47.7324333190918,36.0009002685547],[488.632598876953,50.8816299438477,24.0011005401611],[488.632598876953,50.8850326538086,30.0011005401611],[488.632598876953,49.7480316162109,24.0011005401611],[488.632598876953,49.7426300048828,24.0011005401611],[488.632598876953,49.745231628418,30.0011005401611],[488.632598876953,49.7592315673828,24.0011005401611],[488.632598876953,49.7536315917969,30.0011005401611],[488.632598876953,49.3900299072266,24.0011005401611],[488.632598876953,49.5664329528809,30.0011005401611],[488.632598876953,50.8786315917969,24.0011005401611],[488.632598876953,50.7764320373535,24.0011005401611],[488.632598876953,50.8274307250977,30.0011005401611],[488.632598876953,50.7550315856934,30.0011005401611],[488.632598876953,50.7692337036133,30.0011005401611],[488.632598876953,50.9284324645996,24.0011005401611],[488.632598876953,50.9202308654785,30.0011005401611],[488.632598876953,51.1788330078125,24.0011005401611],[488.632598876953,51.139232635498,24.0011005401611],[488.632598876953,51.1590309143066,30.0011005401611],[488.632598876953,51.2324333190918,24.0011005401611],[488.632598876953,51.2056312561035,30.0011005401611],[488.632598876953,51.4340324401855,24.0011005401611],[488.632598876953,51.3946304321289,24.0011005401611],[488.632598876953,51.4142303466797,30.0011005401611],[488.632598876953,51.4498329162598,24.0011005401611],[488.632598876953,51.5772323608398,30.0011005401611],[488.632598876953,51.4418334960938,30.0011005401611],[488.632598876953,51.3136329650879,30.0011005401611],[488.632598876953,49.7714309692383,30.0011005401611],[488.632598876953,51.0338325500488,30.0011005401611],[488.632598876953,50.8816299438477,30.0011005401611],[488.632598876953,50.8800315856934,30.0011005401611],[488.632598876953,51.7188339233398,36.0009002685547],[488.632598876953,51.7176322937012,36.0009002685547],[488.632598876953,49.3900299072266,30.0011005401611],[488.632598876953,50.7522315979004,30.0011005401611],[488.632598876953,50.7522315979004,36.0009002685547],[488.632598876953,49.7426300048828,30.0011005401611],[488.632598876953,49.7426300048828,36.0009002685547],[488.632598876953,49.7480316162109,30.0011005401611],[488.632598876953,49.7480316162109,36.0009002685547],[488.632598876953,51.715030670166,30.0011005401611],[488.632598876953,51.715030670166,36.0009002685547],[488.632598876953,50.7578315734863,30.0011005401611],[488.632598876953,50.7578315734863,36.0009002685547],[488.632598876953,50.761833190918,30.0011005401611],[488.632598876953,50.761833190918,36.0009002685547],[488.632598876953,50.8882331848145,30.0011005401611],[488.632598876953,50.8882331848145,36.0009002685547],[488.632598876953,49.7592315673828,30.0011005401611],[488.632598876953,49.7592315673828,36.0009002685547],[488.632598876953,51.1788330078125,30.0011005401611],[488.632598876953,51.1788330078125,36.0009002685547],[488.632598876953,50.9120330810547,30.0011005401611],[488.632598876953,50.9120330810547,36.0009002685547],[488.632598876953,51.4498329162598,30.0011005401611],[488.632598876953,51.4498329162598,36.0009002685547],[488.632598876953,51.7046318054199,30.0011005401611],[488.632598876953,51.7046318054199,36.0009002685547],[488.632598876953,51.2324333190918,30.0011005401611],[488.632598876953,51.2324333190918,36.0009002685547],[488.632598876953,51.3946304321289,30.0011005401611],[488.632598876953,51.3946304321289,36.0009002685547],[488.632598876953,51.4340324401855,30.0011005401611],[488.632598876953,51.4340324401855,36.0009002685547],[488.632598876953,49.7838325500488,30.0011005401611],[488.632598876953,49.7838325500488,36.0009002685547],[488.632598876953,50.7764320373535,30.0011005401611],[488.632598876953,50.7764320373535,36.0009002685547],[488.632598876953,51.139232635498,30.0011005401611],[488.632598876953,51.139232635498,36.0009002685547],[488.632598876953,50.9284324645996,30.0011005401611],[488.632598876953,50.9284324645996,36.0009002685547],[488.632598876953,50.8816299438477,36.0009002685547],[488.632598876953,50.8786315917969,30.0011005401611],[488.632598876953,50.8786315917969,36.0009002685547],[488.632598876953,51.7324333190918,35.0173034667969],[488.632598876953,51.7324333190918,36.0009002685547],[488.632598876953,51.7324333190918,30.9847011566162],[517.188415527344,51.7140884399414,24.0011005401611],[517.188415527344,51.7140884399414,36.0009002685547],[517.188415527344,50.4475173950195,24.0011005401611],[517.188415527344,51.7324333190918,35.3734130859375],[517.188415527344,51.7324333190918,36.0009002685547],[517.188415527344,51.7324333190918,34.1185760498047],[517.188415527344,51.7324333190918,31.88330078125],[517.188415527344,51.7324333190918,30.0011005401611],[517.188415527344,51.7324333190918,28.1187744140625],[517.188415527344,51.7324333190918,25.8834266662598],[517.188415527344,51.7324333190918,24.6285915374756],[517.188415527344,51.7324333190918,24.0011005401611],[517.188415527344,47.7324333190918,24.0600452423096],[517.188415527344,47.7324333190918,24.0011005401611],[517.188415527344,50.4475173950195,36.0009002685547],[517.188415527344,47.7324333190918,24.1779975891113],[517.188415527344,47.7324333190918,24.6498031616211],[517.188415527344,47.7324333190918,28.7625770568848],[517.188415527344,47.7324333190918,29.7061901092529],[517.188415527344,47.7324333190918,29.9420928955078],[517.188415527344,47.7324333190918,30.0600452423096],[517.188415527344,47.7324333190918,30.2959480285645],[517.188415527344,47.7324333190918,31.2395629882812],[517.188415527344,47.7324333190918,35.3521995544434],[517.188415527344,47.7324333190918,35.8240051269531],[517.188415527344,47.7324333190918,35.9419555664062],[517.188415527344,47.7324333190918,36.0009002685547] ]; $facets = [ [0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,2,1],[12,1,13],[14,15,16],[17,18,19],[20,21,22],[17,19,23],[24,25,26],[27,13,1],[28,25,29],[30,31,32],[28,33,34],[35,36,7],[37,38,39],[40,10,41],[42,43,44],[45,5,4],[46,47,48],[46,48,49],[45,4,50],[51,52,53],[51,54,55],[56,52,57],[58,59,60],[61,50,4],[62,63,64],[65,34,33],[66,67,42],[68,17,69],[70,71,22],[66,42,72],[73,16,15],[35,7,74],[75,76,54],[77,27,1],[78,32,31],[75,54,79],[80,26,25],[81,80,25],[82,83,48],[84,20,85],[81,25,86],[87,88,19],[0,89,1],[90,91,92],[90,10,93],[38,94,39],[94,95,39],[3,7,96],[97,15,98],[97,99,15],[92,91,100],[89,101,1],[102,39,95],[103,11,10],[104,96,7],[105,15,99],[106,61,4],[107,108,33],[76,55,54],[109,91,110],[111,23,19],[112,63,113],[114,115,48],[116,59,117],[118,20,119],[120,31,121],[122,44,43],[110,91,123],[124,125,126],[127,128,129],[127,130,124],[131,124,132],[126,133,134],[135,136,126],[137,138,127],[139,127,138],[128,140,141],[142,128,143],[144,140,145],[100,91,146],[147,148,134],[101,149,1],[102,150,39],[103,10,151],[145,140,152],[152,140,153],[148,154,134],[154,155,134],[156,15,105],[157,104,7],[36,8,7],[158,37,39],[159,19,88],[160,19,159],[161,59,58],[161,117,59],[162,31,30],[162,121,31],[163,43,164],[163,165,43],[166,167,43],[167,164,43],[168,57,52],[82,48,169],[114,170,171],[108,65,33],[64,63,112],[114,172,170],[160,173,170],[171,170,173],[172,174,170],[160,170,174],[175,176,177],[178,77,1],[179,31,120],[175,180,176],[181,182,176],[177,176,182],[180,183,176],[181,176,183],[184,42,67],[185,69,17],[160,111,19],[186,187,160],[188,189,114],[190,188,114],[114,48,191],[192,114,193],[194,160,195],[196,160,194],[197,198,181],[199,197,181],[122,43,165],[200,201,175],[202,175,203],[204,175,202],[205,119,20],[206,181,207],[208,209,15],[210,15,209],[211,10,9],[212,10,211],[213,214,215],[216,217,218],[219,14,17],[113,63,220],[221,222,48],[191,48,222],[22,223,20],[205,20,223],[224,40,42],[123,91,225],[214,226,215],[227,215,226],[218,217,228],[229,228,217],[215,230,213],[125,135,126],[217,216,231],[129,128,142],[216,213,232],[130,132,124],[213,216,233],[234,213,235],[236,227,237],[238,237,227],[239,240,216],[233,216,240],[241,242,229],[243,229,242],[215,227,244],[245,215,246],[217,247,229],[248,249,217],[232,213,250],[230,250,213],[133,147,134],[244,227,251],[236,252,227],[251,227,252],[231,216,253],[254,253,216],[141,140,144],[247,255,229],[241,229,256],[255,256,229],[257,241,258],[259,146,91],[260,261,236],[262,1,149],[263,264,241],[265,241,264],[266,236,267],[268,267,236],[49,48,83],[166,43,269],[270,271,272],[273,274,275],[276,274,277],[278,151,10],[279,280,272],[281,39,150],[272,282,279],[155,283,134],[274,276,284],[153,140,285],[286,276,287],[265,276,286],[288,289,279],[268,288,279],[290,291,272],[271,290,272],[292,274,293],[275,274,292],[294,265,295],[276,265,294],[296,297,268],[279,296,268],[241,265,298],[298,265,299],[236,300,268],[300,301,268],[107,33,78],[302,303,59],[304,305,279],[282,304,279],[306,276,307],[284,276,306],[185,17,73],[308,309,221],[158,39,70],[310,41,10],[15,311,208],[7,6,312],[313,314,6],[315,6,314],[316,208,317],[318,317,208],[258,241,319],[319,241,320],[261,321,236],[321,322,236],[6,315,323],[208,324,318],[270,325,318],[326,318,325],[327,328,315],[273,315,328],[118,329,20],[330,20,329],[331,332,25],[86,25,332],[333,334,52],[335,52,334],[115,336,48],[169,48,336],[62,106,4],[35,15,210],[35,337,15],[158,10,212],[158,310,10],[338,178,1],[339,59,116],[107,302,59],[66,22,340],[66,341,22],[185,221,342],[185,308,221],[75,31,179],[75,343,31],[166,20,330],[166,85,20],[81,52,335],[81,168,52],[82,19,344],[82,87,19],[108,339,345],[346,108,345],[64,347,348],[349,347,64],[178,109,350],[351,178,350],[179,352,353],[354,352,179],[355,208,356],[356,208,311],[357,358,6],[358,312,6],[68,22,21],[68,340,22],[221,48,47],[184,342,221],[359,270,360],[318,360,270],[361,362,273],[315,273,362],[272,102,270],[363,270,102],[274,273,103],[364,103,273],[21,19,18],[21,20,84],[184,46,42],[43,42,46],[12,22,71],[365,22,12],[14,98,15],[14,220,63],[40,93,10],[40,225,91],[45,221,309],[366,221,45],[313,367,212],[212,367,368],[36,369,367],[313,36,367],[316,37,367],[37,368,367],[210,367,369],[316,367,210],[362,370,315],[370,323,315],[360,318,371],[371,318,324],[372,331,159],[159,195,160],[373,115,56],[115,114,189],[52,56,161],[374,161,56],[25,28,331],[375,331,28],[376,333,163],[163,203,175],[377,118,24],[118,181,198],[25,24,162],[378,162,24],[52,51,333],[379,333,51],[167,380,381],[376,167,381],[377,381,330],[330,381,380],[335,381,382],[376,381,335],[373,383,169],[169,383,384],[168,385,383],[373,168,383],[372,87,383],[87,384,383],[377,80,381],[80,382,381],[86,383,385],[372,383,86],[106,348,347],[386,106,347],[375,65,346],[108,346,65],[64,112,349],[387,349,112],[171,190,114],[346,345,171],[374,190,345],[171,345,190],[349,172,347],[172,114,192],[386,347,192],[172,192,347],[173,160,196],[171,173,346],[375,346,196],[173,196,346],[172,349,174],[174,186,160],[387,186,349],[174,349,186],[64,348,62],[106,62,348],[108,107,339],[59,339,107],[374,345,116],[339,116,345],[76,353,352],[379,76,352],[388,77,351],[178,351,77],[179,120,354],[378,354,120],[177,200,175],[351,350,177],[389,200,350],[177,350,200],[354,180,352],[180,175,204],[379,352,204],[180,204,352],[182,181,206],[177,182,351],[388,351,206],[182,206,351],[180,354,183],[183,199,181],[378,199,354],[183,354,199],[91,109,338],[178,338,109],[76,75,353],[179,353,75],[389,350,110],[109,110,350],[390,391,392],[393,394,395],[224,122,389],[122,175,201],[365,388,205],[205,207,181],[66,340,396],[68,396,340],[184,396,342],[185,342,396],[66,396,67],[184,67,396],[68,69,396],[185,396,69],[219,111,387],[111,160,187],[366,386,191],[191,193,114],[150,272,280],[102,272,150],[151,277,274],[103,151,274],[161,374,117],[116,117,374],[366,61,386],[106,386,61],[111,187,387],[186,387,187],[56,188,374],[190,374,188],[191,386,193],[192,193,386],[331,375,194],[196,194,375],[28,34,375],[65,375,34],[219,387,113],[112,113,387],[224,389,123],[110,123,389],[51,55,379],[76,379,55],[24,197,378],[199,378,197],[122,201,389],[200,389,201],[333,379,202],[204,202,379],[205,388,207],[206,207,388],[365,27,388],[77,388,27],[162,378,121],[120,121,378],[162,30,25],[30,29,25],[51,53,54],[303,60,59],[28,29,33],[29,397,33],[161,58,52],[53,52,58],[21,84,19],[84,344,19],[46,49,43],[49,269,43],[208,316,209],[210,209,316],[327,313,211],[212,211,313],[36,35,369],[210,369,35],[37,158,368],[212,368,158],[6,8,313],[36,313,8],[326,38,316],[37,316,38],[392,391,398],[399,398,391],[394,400,395],[401,395,400],[390,214,391],[214,213,234],[393,395,218],[218,239,216],[402,230,403],[230,215,245],[125,124,131],[404,125,403],[405,406,231],[231,248,217],[129,137,127],[407,406,129],[130,127,139],[402,130,408],[194,195,331],[159,331,195],[115,189,56],[188,56,189],[14,219,220],[113,220,219],[45,50,366],[61,366,50],[221,366,222],[191,222,366],[17,23,219],[111,219,23],[118,198,24],[197,24,198],[202,203,333],[163,333,203],[40,224,225],[123,225,224],[12,13,365],[27,365,13],[22,365,223],[205,223,365],[42,44,224],[122,224,44],[399,391,234],[214,234,391],[401,239,395],[218,395,239],[214,390,226],[226,238,227],[218,228,393],[228,229,243],[401,399,233],[233,235,213],[392,409,390],[410,390,409],[394,393,411],[412,411,393],[402,403,131],[125,131,403],[405,137,406],[129,406,137],[405,408,139],[130,139,408],[230,245,403],[404,403,245],[231,406,248],[407,248,406],[232,254,216],[402,408,232],[413,404,244],[244,246,215],[414,247,407],[247,217,249],[133,126,136],[415,133,413],[141,143,128],[416,414,141],[410,238,390],[226,390,238],[412,393,243],[228,243,393],[233,399,235],[234,235,399],[237,260,236],[238,410,237],[417,260,410],[237,410,260],[239,401,240],[233,240,401],[242,241,257],[243,242,412],[418,412,257],[242,257,412],[401,419,399],[398,399,419],[417,410,420],[409,420,410],[400,421,401],[419,401,421],[418,422,412],[411,412,422],[413,135,404],[125,404,135],[414,407,142],[129,142,407],[130,402,132],[131,132,402],[133,136,413],[135,413,136],[423,147,415],[133,415,147],[137,405,138],[139,138,405],[141,414,143],[142,143,414],[424,416,144],[141,144,416],[405,254,408],[232,408,254],[244,404,246],[245,246,404],[247,249,407],[248,407,249],[232,250,402],[230,402,250],[415,413,251],[244,251,413],[252,236,266],[251,252,415],[423,415,266],[252,266,415],[231,253,405],[254,405,253],[416,255,414],[247,414,255],[256,263,241],[255,416,256],[424,263,416],[256,416,263],[257,258,418],[425,418,258],[260,417,261],[426,261,417],[422,418,427],[427,259,91],[420,428,417],[428,1,262],[147,423,148],[429,148,423],[263,424,264],[264,295,265],[266,267,423],[267,268,297],[144,145,424],[430,424,145],[49,431,269],[166,269,431],[82,431,83],[49,83,431],[84,85,431],[166,431,85],[82,344,431],[84,431,344],[432,278,90],[10,90,278],[433,0,281],[39,281,0],[362,361,434],[435,271,359],[270,359,271],[436,361,275],[273,275,361],[360,437,359],[277,287,276],[151,278,277],[280,279,289],[150,280,281],[436,438,439],[439,285,140],[90,92,432],[440,432,92],[282,272,291],[441,282,442],[284,293,274],[443,438,284],[278,432,286],[286,299,265],[281,288,433],[288,268,301],[0,433,89],[444,89,433],[435,445,442],[445,134,283],[439,446,436],[361,436,446],[442,290,435],[271,435,290],[438,436,292],[275,292,436],[445,435,447],[359,447,435],[286,287,278],[277,278,287],[288,281,289],[280,289,281],[145,152,430],[443,430,152],[148,429,154],[441,154,429],[424,430,294],[294,307,276],[423,296,429],[296,279,305],[425,440,100],[92,100,440],[290,442,291],[282,291,442],[292,293,438],[284,438,293],[298,320,241],[432,440,298],[300,236,322],[433,300,444],[426,101,444],[89,444,101],[107,448,302],[302,79,54],[78,31,343],[107,78,448],[75,79,448],[302,448,79],[78,343,448],[75,448,343],[427,418,259],[425,259,418],[428,262,417],[426,417,262],[437,449,359],[447,359,449],[434,361,450],[446,450,361],[32,33,397],[78,33,32],[53,303,54],[302,54,303],[152,153,443],[438,443,153],[429,304,441],[282,441,304],[430,443,306],[284,306,443],[154,441,155],[442,155,441],[298,299,432],[286,432,299],[300,433,301],[288,301,433],[185,451,308],[308,74,7],[73,15,337],[185,73,451],[35,74,451],[308,451,74],[73,337,451],[35,451,337],[158,452,310],[310,72,42],[70,22,341],[158,70,452],[66,72,452],[310,452,72],[70,341,452],[66,452,341],[313,327,314],[315,314,327],[316,317,326],[318,326,317],[15,156,311],[356,311,156],[7,312,157],[358,157,312],[211,9,327],[364,327,9],[38,326,94],[363,94,326],[294,295,424],[264,424,295],[296,423,297],[267,297,423],[262,149,426],[101,426,149],[258,319,425],[440,425,319],[261,426,321],[444,321,426],[259,425,146],[100,146,425],[306,307,430],[294,430,307],[304,429,305],[296,305,429],[319,320,440],[298,440,320],[321,444,322],[300,322,444],[445,283,442],[155,442,283],[439,438,285],[153,285,438],[17,68,18],[21,18,68],[46,184,47],[221,47,184],[102,95,363],[94,363,95],[9,11,364],[103,364,11],[6,323,357],[370,357,323],[371,324,355],[208,355,324],[270,363,325],[326,325,363],[327,364,328],[273,328,364],[0,2,39],[12,39,2],[90,93,91],[40,91,93],[14,16,17],[73,17,16],[45,309,7],[308,7,309],[12,71,39],[70,39,71],[40,41,42],[310,42,41],[97,98,63],[14,63,98],[3,5,7],[45,7,5],[118,377,329],[330,329,377],[331,372,332],[86,332,372],[333,376,334],[335,334,376],[115,373,336],[169,336,373],[167,166,380],[330,380,166],[80,81,382],[335,382,81],[86,385,81],[168,81,385],[169,384,82],[87,82,384],[159,88,372],[87,372,88],[163,164,376],[167,376,164],[24,26,377],[80,377,26],[56,57,373],[168,373,57],[32,397,30],[29,30,397],[58,60,53],[303,53,60],[205,181,119],[118,119,181],[163,175,165],[122,165,175],[453,454,455],[454,456,455],[457,455,456],[458,455,457],[459,455,458],[460,455,459],[461,462,463],[464,465,466],[467,468,469],[470,471,472],[465,473,474],[475,476,477],[478,479,480],[481,482,478],[483,484,481],[485,486,483],[487,488,485],[489,490,487],[491,492,489],[493,494,491],[495,496,493],[497,498,495],[499,500,497],[501,502,499],[503,504,501],[505,504,503],[506,504,505],[507,504,506],[508,504,507],[509,504,508],[510,504,509],[511,504,510],[512,504,511],[513,504,512],[514,504,513],[476,515,516],[517,518,519],[520,517,521],[518,522,523],[522,480,479],[524,525,526],[468,470,527],[525,467,528],[529,475,530],[531,532,533],[534,531,535],[536,537,538],[473,539,540],[539,536,541],[537,534,542],[471,520,543],[532,529,544],[545,524,546],[453,461,547],[463,464,548],[523,549,504],[527,550,551],[519,552,553],[521,554,555],[466,556,557],[469,558,559],[528,560,561],[477,562,563],[543,564,565],[535,566,567],[530,568,569],[540,570,571],[474,572,573],[542,574,575],[538,576,577],[541,578,579],[472,580,581],[526,582,583],[533,584,585],[544,586,587],[516,545,588],[588,589,590],[455,460,4],[591,592,63],[462,455,4],[592,547,63],[547,548,63],[465,462,4],[548,557,63],[127,124,501],[127,501,499],[505,503,124],[124,126,507],[124,507,506],[509,508,126],[126,134,512],[126,512,511],[510,509,126],[128,127,493],[128,493,491],[497,495,127],[489,487,128],[140,128,483],[140,483,481],[487,485,128],[478,480,140],[480,522,140],[514,513,134],[504,514,134],[551,581,437],[471,470,434],[445,447,555],[445,555,553],[134,445,553],[134,553,504],[446,439,518],[446,518,517],[439,140,522],[439,522,518],[515,476,358],[563,588,356],[557,573,63],[473,465,4],[437,360,559],[437,559,551],[360,371,561],[360,561,559],[362,434,470],[362,470,468],[370,362,468],[370,468,467],[499,497,127],[506,505,124],[495,493,127],[513,512,134],[481,478,140],[447,449,565],[447,565,555],[450,446,517],[450,517,520],[356,156,569],[356,569,563],[157,358,476],[157,476,475],[357,370,467],[357,467,525],[371,355,583],[371,583,561],[460,459,4],[63,62,593],[63,593,591],[62,4,459],[62,459,458],[532,531,104],[531,534,104],[567,585,105],[575,567,105],[4,3,539],[4,539,473],[536,539,3],[97,63,573],[97,573,571],[571,579,97],[99,97,579],[99,579,577],[105,99,577],[105,577,575],[96,104,534],[96,534,537],[3,96,537],[3,537,536],[503,501,124],[508,507,126],[491,489,128],[511,510,126],[485,483,128],[434,450,520],[434,520,471],[449,437,581],[449,581,565],[156,105,585],[156,585,587],[587,569,156],[104,157,529],[104,529,532],[475,529,157],[590,583,355],[355,356,588],[355,588,590],[358,357,524],[358,524,515],[525,524,357],[458,457,62],[457,593,62],[479,478,482],[479,504,549],[479,482,504],[482,481,484],[472,551,550],[581,551,472],[482,484,504],[484,483,486],[523,553,552],[504,553,523],[540,573,572],[571,573,540],[544,585,584],[587,585,544],[542,577,576],[575,577,542],[526,590,589],[583,590,526],[535,575,574],[567,575,535],[533,567,566],[585,567,533],[538,579,578],[577,579,538],[543,581,580],[565,581,543],[477,569,568],[563,569,477],[530,587,586],[569,587,530],[541,571,570],[579,571,541],[528,583,582],[561,583,528],[591,453,592],[547,592,453],[521,565,564],[555,565,521],[474,557,556],[573,557,474],[516,563,562],[588,563,516],[519,555,554],[553,555,519],[527,559,558],[551,559,527],[469,561,560],[559,561,469],[462,461,455],[453,455,461],[461,463,547],[548,547,463],[465,464,462],[463,462,464],[464,466,548],[557,548,466],[469,560,467],[528,467,560],[472,550,470],[527,470,550],[474,556,465],[466,465,556],[477,568,475],[530,475,568],[516,562,476],[477,476,562],[519,554,517],[521,517,554],[521,564,520],[543,520,564],[523,552,518],[519,518,552],[479,549,522],[523,522,549],[526,589,524],[589,546,524],[527,558,468],[469,468,558],[528,582,525],[526,525,582],[530,586,529],[544,529,586],[533,566,531],[535,531,566],[535,574,534],[542,534,574],[538,578,536],[541,536,578],[540,572,473],[474,473,572],[541,570,539],[540,539,570],[542,576,537],[538,537,576],[543,580,471],[472,471,580],[544,584,532],[533,532,584],[524,545,515],[516,515,545],[545,546,588],[589,588,546],[453,591,454],[593,454,591],[484,486,504],[486,485,488],[486,488,504],[488,487,490],[488,490,504],[490,489,492],[490,492,504],[492,491,494],[492,494,504],[494,493,496],[494,496,504],[496,495,498],[496,498,504],[498,497,500],[498,500,504],[500,499,502],[500,502,504],[501,504,502],[454,593,456],[457,456,593],[594,595,596],[597,598,594],[599,597,594],[600,599,594],[601,600,594],[602,601,594],[603,602,594],[604,603,594],[605,604,594],[606,607,608],[609,606,608],[610,609,608],[611,610,608],[612,611,608],[613,612,608],[614,613,608],[615,614,608],[616,615,608],[617,616,608],[618,617,608],[619,618,608],[620,619,608],[596,608,607],[595,594,598],[608,596,595],[605,594,91],[91,338,602],[91,602,603],[598,597,1],[594,596,91],[608,595,1],[595,598,1],[616,617,392],[610,611,394],[419,421,613],[419,613,614],[422,427,607],[422,607,606],[427,91,596],[427,596,607],[428,420,619],[428,619,620],[1,428,620],[1,620,608],[420,409,618],[420,618,619],[411,422,606],[411,606,609],[398,419,614],[398,614,615],[421,400,612],[421,612,613],[409,392,617],[409,617,618],[394,411,609],[394,609,610],[604,605,91],[338,1,599],[338,599,600],[392,398,615],[392,615,616],[400,394,611],[400,611,612],[603,604,91],[601,602,338],[597,599,1],[600,601,338] ]; } elsif ($name eq 'gt2_teeth') { $vertices = [ [15.8899993896484,19.444055557251,2.67489433288574],[15.9129991531372,19.1590557098389,2.67489433288574],[15.9039993286133,19.1500549316406,2.67489433288574],[15.9489994049072,19.2490558624268,2.67489433288574],[15.9579992294312,19.3570556640625,2.67489433288574],[15.8819999694824,18.690055847168,2.67489433288574],[15.8319997787476,17.7460556030273,2.67489433288574],[15.8489999771118,18.819055557251,2.67489433288574],[15.8589992523193,17.7190551757812,2.67489433288574],[15.8769998550415,19.0490550994873,2.67489433288574],[15.7529993057251,17.8080558776855,2.67489433288574],[15.7869997024536,19.5010547637939,2.67489433288574],[14.0329990386963,18.7170543670654,2.67489433288574],[13.9599990844727,18.7460556030273,2.67489433288574],[13.9869995117188,20.2840557098389,2.67489433288574],[14.2029991149902,20.149055480957,2.67489433288574],[14.1939992904663,19.9560546875,2.67489433288574],[14.1939992904663,20.1670551300049,2.67489433288574],[14.2119998931885,20.0590553283691,2.67489433288574],[12.1899995803833,19.1840553283691,2.67489433288574],[12.096999168396,19.1950550079346,2.67489433288574],[12.1099996566772,20.6690559387207,2.67489433288574],[11.382999420166,19.9750556945801,2.67489433288574],[11.2599992752075,19.2490558624268,2.67489433288574],[11.2369995117188,19.9320545196533,2.67489433288574],[11.5349998474121,20.0640544891357,2.67489433288574],[11.6259994506836,20.1550559997559,2.67489433288574],[11.6829986572266,20.2390556335449,2.67489433288574],[11.7369995117188,20.3570556640625,2.67489433288574],[11.8449993133545,20.645055770874,2.67489433288574],[11.7729988098145,20.4640560150146,2.67489433288574],[11.7799987792969,20.5370559692383,9.41389465332031],[11.7639999389648,20.4470558166504,2.67489433288574],[11.9559993743896,20.6810550689697,2.67489433288574],[12.3079996109009,20.6020545959473,2.67489433288574],[12.1959991455078,19.1860542297363,2.67489433288574],[12.2059993743896,20.6540546417236,2.67489433288574],[12.3489990234375,20.3740558624268,2.67489433288574],[12.3579998016357,20.2750549316406,2.67489433288574],[12.3669996261597,20.266056060791,2.67489433288574],[12.3849992752075,20.1670551300049,2.67489433288574],[12.4269990921021,20.0680541992188,2.67489433288574],[12.5029993057251,19.9540557861328,2.67489433288574],[12.6169996261597,19.8550548553467,2.67489433288574],[12.7449989318848,19.7800559997559,2.67489433288574],[12.7629995346069,19.7800559997559,2.67489433288574],[12.8799991607666,19.7350559234619,2.67489433288574],[13.0369997024536,19.7250556945801,2.67489433288574],[13.0149993896484,19.0340557098389,2.67489433288574],[11.1699991226196,19.2580547332764,2.67489433288574],[11.0959987640381,19.2580547332764,2.67489433288574],[11.1209993362427,19.9230556488037,2.67489433288574],[13.0599994659424,19.024055480957,2.67489433288574],[14.9049997329712,18.3170547485352,2.67489433288574],[14.8779993057251,18.3400554656982,2.67489433288574],[14.8779993057251,19.149055480957,2.67489433288574],[13.3039989471436,19.77805519104,2.67489433288574],[13.1589994430542,18.9890556335449,2.67489433288574],[13.1559991836548,19.7350559234619,2.67489433288574],[13.4269990921021,19.8600559234619,2.67489433288574],[13.5339994430542,19.9700546264648,2.67389440536499],[13.6359996795654,20.1220550537109,2.67489433288574],[13.6359996795654,20.1400547027588,2.67489433288574],[13.6719989776611,20.2210559844971,2.67489433288574],[13.6899995803833,20.2300548553467,2.67489433288574],[13.7509994506836,20.3010559082031,2.67489433288574],[13.8539991378784,20.3180541992188,2.67489433288574],[14.8329992294312,18.3580551147461,2.67489433288574],[14.1849994659424,19.8530559539795,2.67489433288574],[14.0769996643066,18.7000541687012,2.67489433288574],[14.1099996566772,20.2400550842285,2.67489433288574],[14.2009992599487,19.6230545043945,2.67489433288574],[14.2729997634888,19.4670543670654,2.67489433288574],[14.3379993438721,19.3790550231934,2.67489433288574],[14.4549999237061,19.2770557403564,2.67489433288574],[14.5899991989136,19.2040557861328,2.67489433288574],[14.6079998016357,19.2040557861328,2.67489433288574],[14.7209997177124,19.1600551605225,2.67489433288574],[15.1379995346069,19.210054397583,2.67489433288574],[14.9949998855591,18.2680549621582,2.67489433288574],[15.0029993057251,19.1580543518066,2.67489433288574],[15.2369995117188,19.2760543823242,2.67489433288574],[15.3779993057251,19.4060554504395,2.67489433288574],[15.4539995193481,19.520055770874,2.67489433288574],[15.471999168396,19.52805519104,2.67489433288574],[15.5449991226196,19.5830554962158,2.67489433288574],[15.6529998779297,19.573055267334,2.67489433288574],[15.7059993743896,17.8360557556152,2.67489433288574],[15.9449996948242,18.5560550689697,2.67489433288574],[15.8589992523193,18.9380550384521,2.67489433288574],[14.9589996337891,18.2950553894043,2.67489433288574],[15.7779998779297,19.5100555419922,2.67489433288574],[14.0049991607666,20.2750549316406,2.67489433288574],[12.3489990234375,20.5000553131104,2.67489433288574],[13.0689992904663,19.0150547027588,2.67489433288574],[13.0999994277954,19.0100555419922,2.67489433288574],[15.9489994049072,19.3670558929443,9.41489505767822],[15.9489994049072,19.2490558624268,9.41489505767822],[15.75,17.8080558776855,9.41489505767822],[15.6639995574951,19.5710544586182,9.41489505767822],[15.5709991455078,17.9260559082031,9.41489505767822],[15.8769998550415,18.690055847168,9.41489505767822],[15.8499994277954,18.8170547485352,9.41489505767822],[15.9459991455078,18.5520553588867,9.41489505767822],[15.914999961853,17.6890544891357,9.41489505767822],[15.3999996185303,19.4290542602539,9.41489505767822],[15.3099994659424,19.339054107666,9.41489505767822],[15.3729991912842,18.0440559387207,9.41489505767822],[15.4579992294312,19.5170555114746,9.41489505767822],[15.5469999313354,19.5820541381836,9.41489505767822],[13.2309989929199,19.7610549926758,9.41489505767822],[13.168999671936,19.7360553741455,9.41489505767822],[13.096999168396,19.0140552520752,9.41489505767822],[13.1999988555908,18.9870548248291,9.41489505767822],[15.1399993896484,19.2080554962158,9.41489505767822],[15.0159997940063,19.1600551605225,9.41489505767822],[14.9859991073608,18.2770557403564,9.41489505767822],[15.1749992370605,18.1690559387207,9.41489505767822],[15.9039993286133,19.1320552825928,9.41489505767822],[15.8949995040894,19.4460544586182,9.41489505767822],[15.8769998550415,19.0420551300049,9.41489505767822],[12.2169990539551,20.6500549316406,9.41489505767822],[11.9379997253418,20.6810550689697,9.41489505767822],[11.8629989624023,19.2130546569824,9.41489505767822],[12.096999168396,19.1950550079346,9.41489505767822],[14.1669998168945,18.6640548706055,9.41489505767822],[14.1039991378784,20.2460556030273,9.41489505767822],[13.9849996566772,18.7360553741455,9.41489505767822],[14.7349996566772,19.1590557098389,9.41489505767822],[14.5849990844727,19.2050552368164,9.41489505767822],[14.5719995498657,18.4850559234619,9.41489505767822],[14.1939992904663,19.6760559082031,9.41489505767822],[14.1849994659424,19.9330558776855,9.41489505767822],[14.1759996414185,18.6640548706055,9.41489505767822],[14.261999130249,19.4890556335449,9.41489505767822],[14.3539991378784,19.3610553741455,9.41489505767822],[14.3559989929199,18.5830554962158,9.41489505767822],[11.6039991378784,20.1250553131104,9.41489505767822],[11.5209999084473,20.0520553588867,9.41489505767822],[11.4209995269775,19.2480545043945,9.41489505767822],[11.6989994049072,20.2690544128418,9.41389465332031],[11.7609996795654,20.4310550689697,9.41489505767822],[11.8359994888306,19.2130546569824,9.41489505767822],[14.1889991760254,20.1710548400879,9.41489505767822],[13.9689998626709,20.2840557098389,9.41489505767822],[13.8739995956421,20.315055847168,9.41489505767822],[13.7799997329712,18.8080558776855,9.41489505767822],[13.9869995117188,20.2750549316406,9.41489505767822],[12.3129997253418,20.5980548858643,9.41489505767822],[12.3399991989136,20.5090560913086,9.41489505767822],[12.3489990234375,20.3830547332764,9.41489505767822],[12.3599996566772,20.2680549621582,9.41489505767822],[12.3849992752075,20.1850547790527,9.41489505767822],[12.3849992752075,20.1670551300049,9.41489505767822],[12.4249992370605,20.065055847168,9.41489505767822],[12.4729995727539,19.1350555419922,9.41489505767822],[14.4399995803833,19.2900543212891,9.41489505767822],[14.3649997711182,18.5740547180176,9.41489505767822],[13.5729999542236,20.0310554504395,9.41489505767822],[13.4889993667603,19.9140548706055,9.41489505767822],[13.5639991760254,18.8710556030273,9.41489505767822],[13.6389999389648,20.1310558319092,9.41489505767822],[13.6719989776611,20.2130546569824,9.41489505767822],[13.75,20.3020553588867,9.41489505767822],[12.7399997711182,19.7810554504395,9.41489505767822],[12.6189994812012,19.8520545959473,9.41489505767822],[12.5799999237061,19.1200542449951,9.41489505767822],[12.8349990844727,19.069055557251,9.41489505767822],[11.2669992446899,19.9350547790527,9.41489505767822],[11.1029987335205,19.9230556488037,9.41489505767822],[11.0209999084473,19.2600555419922,9.41489505767822],[11.3819999694824,19.9710559844971,9.41489505767822],[13.418999671936,19.8530559539795,9.41489505767822],[13.4329996109009,18.9160556793213,9.41489505767822],[11.8399991989136,20.6430549621582,9.41489505767822],[13.3119993209839,19.7800559997559,9.41489505767822],[15.2189998626709,19.2600555419922,9.41489505767822],[15.1839990615845,18.1600551605225,9.41489505767822],[15.3639993667603,18.0520553588867,9.41489505767822],[13.0189990997314,19.7250556945801,9.41489505767822],[12.8949995040894,19.7350559234619,9.41489505767822],[15.9039993286133,19.1500549316406,9.41489505767822],[15.7699995040894,19.5140552520752,9.41489505767822],[15.8589992523193,18.9340553283691,9.41489505767822],[14.1939992904663,19.9510555267334,9.41489505767822],[14.2119998931885,20.0630550384521,9.41489505767822],[14.8589992523193,19.149055480957,9.41489505767822],[14.8159999847412,18.3670558929443,9.41489505767822],[14.8959999084473,18.3220558166504,9.41489505767822],[12.5189990997314,19.9360542297363,9.41489505767822],[11.0209999084473,19.9290542602539,9.41489505767822],[11.0209999084473,19.2530555725098,2.67489433288574],[11.0209999084473,19.9300556182861,2.67489433288574],[15.9799995422363,18.505931854248,5.58724021911621],[15.9799995422363,18.5044555664062,9.41489505767822],[15.9799995422363,18.5041732788086,2.67489433288574],[15.9799995422363,18.1684837341309,2.67489433288574],[15.9799995422363,18.1288299560547,9.41489505767822],[15.9799995422363,17.9876575469971,2.67489433288574],[15.9799995422363,17.6247596740723,3.91620373725891],[15.9799995422363,17.6247596740723,2.67489433288574],[15.9799995422363,17.6254329681396,4.32245063781738],[15.9799995422363,17.8920269012451,9.41489505767822],[15.9799995422363,17.8795108795166,2.67489433288574],[15.9799995422363,17.629810333252,4.58585262298584],[15.9799995422363,17.6336059570312,5.27938556671143],[15.9799995422363,17.8311748504639,2.67489433288574],[15.9799995422363,17.638355255127,9.41489505767822],[15.9799995422363,17.6346111297607,5.98653984069824],[15.9799995422363,17.8728256225586,2.67489433288574],[15.9799995422363,18.2221603393555,2.67489433288574] ]; $facets = [ [0,1,2],[0,3,1],[0,4,3],[5,6,7],[8,6,5],[2,9,0],[6,10,11],[12,13,14],[15,16,17],[18,16,15],[19,20,21],[22,23,24],[25,23,22],[26,23,25],[27,23,26],[28,23,27],[29,30,31],[29,32,30],[29,28,32],[33,28,29],[33,23,28],[21,23,33],[20,23,21],[34,35,36],[37,35,34],[38,35,37],[39,35,38],[40,35,39],[41,35,40],[42,35,41],[43,35,42],[44,35,43],[45,35,44],[46,35,45],[47,35,46],[48,35,47],[49,50,51],[52,48,47],[23,49,24],[53,54,55],[56,57,58],[59,57,56],[60,57,59],[61,57,60],[62,57,61],[63,57,62],[64,57,63],[65,57,64],[66,57,65],[13,57,66],[54,67,55],[68,69,70],[71,69,68],[72,69,71],[73,69,72],[74,69,73],[75,69,74],[76,69,75],[77,69,76],[67,69,77],[70,16,68],[70,17,16],[78,79,80],[81,79,78],[82,79,81],[83,79,82],[84,79,83],[85,79,84],[86,79,85],[87,79,86],[88,8,5],[11,7,6],[11,89,7],[11,9,89],[11,0,9],[55,90,53],[55,79,90],[55,80,79],[91,11,10],[92,69,12],[92,70,69],[34,93,37],[47,94,52],[47,95,94],[47,57,95],[47,58,57],[51,24,49],[21,35,19],[21,36,35],[14,92,12],[86,10,87],[86,91,10],[77,55,67],[66,14,13],[96,97,4],[98,99,100],[101,102,98],[103,101,98],[104,103,98],[105,106,107],[108,105,107],[109,108,107],[100,109,107],[110,111,112],[113,110,112],[114,115,116],[117,114,116],[118,119,120],[121,122,123],[124,121,123],[125,126,127],[128,129,130],[131,132,133],[71,131,133],[134,71,133],[135,134,133],[136,135,133],[137,138,139],[140,137,139],[141,140,139],[142,31,141],[142,141,139],[143,126,132],[144,145,146],[147,144,146],[127,147,146],[148,121,124],[149,148,124],[150,149,124],[151,150,124],[152,151,124],[153,152,124],[154,153,124],[155,154,124],[129,156,157],[130,129,157],[158,159,160],[161,158,160],[162,161,160],[163,162,160],[146,163,160],[164,165,166],[167,164,166],[168,169,170],[171,168,170],[139,171,170],[159,172,173],[123,174,142],[175,110,113],[173,175,113],[106,176,177],[178,106,177],[179,180,167],[112,179,167],[175,173,172],[119,118,181],[119,181,97],[119,97,96],[182,98,102],[182,102,183],[182,183,120],[182,120,119],[143,132,184],[184,185,143],[147,127,126],[174,123,122],[159,173,160],[126,125,133],[126,133,132],[186,187,188],[186,188,116],[186,116,115],[99,98,182],[109,100,99],[106,178,107],[114,117,177],[114,177,176],[128,130,187],[128,187,186],[135,136,157],[135,157,156],[163,146,145],[164,167,180],[179,112,111],[171,139,138],[189,155,166],[189,166,165],[149,150,93],[154,155,189],[31,142,174],[114,176,78],[81,78,176],[7,89,183],[89,9,120],[89,120,183],[78,80,114],[176,106,81],[88,5,103],[183,102,7],[118,120,9],[9,2,181],[9,181,118],[115,114,80],[82,81,106],[101,103,5],[102,101,5],[5,7,102],[97,181,2],[2,1,97],[1,3,97],[80,55,115],[172,159,59],[59,56,172],[3,4,97],[4,0,96],[105,108,82],[186,115,55],[82,106,105],[83,82,108],[60,59,159],[175,172,56],[119,96,0],[0,11,119],[108,109,84],[84,83,108],[55,77,186],[56,58,110],[56,110,175],[60,159,158],[11,91,182],[182,119,11],[91,86,182],[85,84,109],[86,85,99],[128,186,77],[58,111,110],[158,161,60],[26,25,137],[138,137,25],[99,182,86],[109,99,85],[77,76,128],[58,47,111],[61,60,161],[137,140,26],[27,26,140],[25,22,138],[129,128,76],[76,75,129],[75,74,129],[74,73,156],[73,72,135],[68,16,184],[68,184,132],[16,18,185],[161,162,62],[62,61,161],[179,111,47],[171,138,22],[156,129,74],[135,156,73],[134,135,72],[72,71,134],[68,132,131],[185,184,16],[18,15,185],[63,62,162],[28,27,140],[22,24,171],[71,68,131],[15,17,143],[15,143,185],[17,70,143],[70,92,126],[162,163,64],[64,63,162],[180,179,47],[47,46,180],[140,141,28],[168,171,24],[126,143,70],[92,14,147],[147,126,92],[14,66,144],[14,144,147],[65,64,163],[66,65,145],[46,45,180],[32,28,141],[24,51,168],[145,144,66],[163,145,65],[164,180,45],[45,44,164],[44,43,164],[43,42,165],[38,37,151],[150,151,37],[37,93,150],[141,31,30],[30,32,141],[169,168,51],[165,164,43],[189,165,42],[42,41,189],[40,39,152],[40,152,153],[151,152,39],[39,38,151],[93,34,149],[154,189,41],[153,154,41],[41,40,153],[148,149,34],[34,36,148],[36,21,121],[31,174,29],[121,148,36],[21,33,122],[21,122,121],[33,29,122],[174,122,29],[116,188,53],[104,98,10],[87,10,98],[98,100,87],[79,87,100],[79,100,107],[90,79,107],[90,107,178],[178,177,90],[53,90,177],[53,177,117],[117,116,53],[54,53,188],[54,188,187],[67,54,187],[67,187,130],[69,67,130],[69,130,157],[12,69,157],[12,157,136],[136,133,12],[12,133,125],[125,127,12],[13,12,127],[127,146,13],[57,13,146],[57,146,160],[95,57,160],[95,160,173],[173,113,95],[94,95,113],[113,112,94],[52,94,112],[48,52,112],[112,167,48],[35,48,167],[35,167,166],[19,35,166],[139,170,50],[50,49,139],[166,155,19],[20,19,155],[155,124,20],[23,20,124],[23,124,123],[49,23,123],[49,123,142],[142,139,49],[190,191,170],[192,191,190],[191,192,51],[191,51,50],[170,169,190],[169,51,192],[169,192,190],[170,191,50],[193,194,195],[196,197,198],[199,200,201],[198,202,203],[204,201,200],[205,204,200],[206,207,208],[206,208,205],[206,205,200],[207,206,209],[207,209,203],[207,203,202],[202,198,197],[197,196,210],[197,210,195],[197,195,194],[8,88,195],[8,195,210],[210,196,8],[196,198,8],[198,203,8],[203,209,8],[209,206,8],[206,200,8],[202,197,104],[207,202,104],[103,104,197],[103,197,194],[193,195,88],[88,103,194],[88,194,193],[200,199,8],[199,201,8],[204,205,6],[6,8,201],[6,201,204],[10,6,205],[10,205,208],[104,10,208],[104,208,207] ]; } elsif ($name eq 'pyramid') { $vertices = [ [10,10,40],[0,0,0],[20,0,0],[20,20,0],[0,20,0], ]; $facets = [ [0,1,2],[0,3,4],[3,1,4],[1,3,2],[3,0,2],[4,1,0], ]; } elsif ($name eq 'two_hollow_squares') { $vertices = [ [66.7133483886719,104.286666870117,0],[66.7133483886719,95.7133331298828,0],[65.6666870117188,94.6666717529297,0],[75.2866821289062,95.7133331298828,0],[76.3333435058594,105.333335876465,0],[76.3333435058594,94.6666717529297,0],[65.6666870117188,105.33332824707,0],[75.2866821289062,104.286666870117,0],[71.1066818237305,104.58666229248,2.79999995231628],[66.4133529663086,104.58666229248,2.79999995231628],[75.5866851806641,104.58666229248,2.79999995231628],[66.4133529663086,99.8933334350586,2.79999995231628],[66.4133529663086,95.4133377075195,2.79999995231628],[71.1066818237305,95.4133377075195,2.79999995231628],[75.5866851806641,95.4133377075195,2.79999995231628],[75.5866851806641,100.106666564941,2.79999995231628],[74.5400161743164,103.540000915527,2.79999995231628],[70.0320129394531,103.540000915527,2.79999995231628],[67.4600067138672,103.540000915527,2.79999995231628],[67.4600067138672,100.968002319336,2.79999995231628],[67.4600067138672,96.4599990844727,2.79999995231628],[74.5400161743164,99.0319976806641,2.79999995231628],[74.5400161743164,96.4599990844727,2.79999995231628],[70.0320129394531,96.4599990844727,2.79999995231628],[123.666717529297,94.6666717529297,0],[134.333312988281,94.6666717529297,0],[124.413360595703,95.4133377075195,2.79999995231628],[129.106674194336,95.4133377075195,2.79999995231628],[133.586669921875,95.4133377075195,2.79999995231628],[123.666717529297,105.33332824707,0],[124.413360595703,104.58666229248,2.79999995231628],[124.413360595703,99.8933334350586,2.79999995231628],[134.333312988281,105.33332824707,0],[129.106674194336,104.58666229248,2.79999995231628],[133.586669921875,104.58666229248,2.79999995231628],[133.586669921875,100.106666564941,2.79999995231628],[124.713317871094,104.286666870117,0],[124.713317871094,95.7133331298828,0],[133.286712646484,95.7133331298828,0],[133.286712646484,104.286666870117,0],[132.540023803711,103.540000915527,2.79999995231628],[128.032028198242,103.540008544922,2.79999995231628],[125.460006713867,103.540000915527,2.79999995231628],[125.460006713867,100.968002319336,2.79999995231628],[125.460006713867,96.4599990844727,2.79999995231628],[132.540023803711,99.0319976806641,2.79999995231628],[132.540023803711,96.4599990844727,2.79999995231628],[128.032028198242,96.4599990844727,2.79999995231628], ]; $facets = [ [0,1,2],[3,4,5],[6,4,0],[6,0,2],[2,1,5],[7,4,3],[1,3,5],[0,4,7],[4,6,8],[6,9,8],[4,8,10],[6,2,9],[2,11,9],[2,12,11],[2,5,12],[5,13,12],[5,14,13],[4,10,15],[5,4,14],[4,15,14],[7,16,17],[0,7,18],[7,17,18],[1,19,20],[1,0,19],[0,18,19],[7,3,21],[3,22,21],[7,21,16],[3,23,22],[3,1,23],[1,20,23],[24,25,26],[25,27,26],[25,28,27],[29,24,30],[24,31,30],[24,26,31],[32,29,33],[29,30,33],[32,33,34],[32,34,35],[25,32,28],[32,35,28],[36,37,24],[38,32,25],[29,32,36],[29,36,24],[24,37,25],[39,32,38],[37,38,25],[36,32,39],[39,40,41],[36,39,42],[39,41,42],[37,43,44],[37,36,43],[36,42,43],[39,38,45],[38,46,45],[39,45,40],[38,47,46],[38,37,47],[37,44,47],[16,8,9],[16,10,8],[10,16,15],[15,16,21],[22,15,21],[15,22,14],[22,23,14],[23,20,14],[17,16,9],[18,17,9],[19,18,9],[19,9,11],[19,11,20],[13,14,20],[20,11,12],[13,20,12],[41,40,30],[42,41,30],[43,42,30],[43,30,31],[43,31,44],[27,28,44],[44,31,26],[27,44,26],[40,33,30],[40,34,33],[34,40,35],[35,40,45],[46,35,45],[35,46,28],[46,47,28],[47,44,28], ]; } elsif ($name eq 'small_dorito') { $vertices = [ [6.00058937072754,-22.9982089996338,0],[22.0010242462158,-49.9998741149902,0],[-9.99957847595215,-49.999870300293,0],[6.00071382522583,-32.2371635437012,28.0019245147705],[11.1670551300049,-37.9727020263672,18.9601669311523],[6.00060224533081,-26.5392456054688,10.7321853637695] ]; $facets = [ [0,1,2],[3,4,5],[2,1,4],[2,4,3],[2,3,5],[2,5,0],[5,4,1],[5,1,0] ]; } else { return undef; } my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadFromPerl($vertices, $facets); $mesh->repair; $mesh->scale_xyz(Slic3r::Pointf3->new(@{$params{scale_xyz}})) if $params{scale_xyz}; $mesh->translate(@{$params{translate}}) if $params{translate}; return $mesh; } sub model { my ($model_name, %params) = @_; my $model = Slic3r::Model->new; my $object = $model->add_object(input_file => "${model_name}.stl"); $model->set_material($model_name); $object->add_volume(mesh => mesh($model_name, %params), material_id => $model_name); $object->add_instance( offset => Slic3r::Pointf->new(0,0), rotation => $params{rotation} // 0, scaling_factor => $params{scale} // 1, ); return $model; } sub init_print { my ($models, %params) = @_; my $config = Slic3r::Config->new; $config->apply($params{config}) if $params{config}; $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; my $print = Slic3r::Print->new; $print->apply_config($config); $models = [$models] if ref($models) ne 'ARRAY'; $models = [ map { ref($_) ? $_ : model($_, %params) } @$models ]; for my $model (@$models) { die "Unknown model in test" if !defined $model; if (defined $params{duplicate} && $params{duplicate} > 1) { $model->duplicate($params{duplicate} // 1, $print->config->min_object_distance); } $model->arrange_objects($print->config->min_object_distance); $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); foreach my $model_object (@{$model->objects}) { $print->auto_assign_extruders($model_object); $print->add_model_object($model_object); } } $print->validate; # We return a proxy object in order to keep $models alive as required by the Print API. return Slic3r::Test::Print->new( print => $print, models => $models, ); } sub gcode { my ($print) = @_; $print = $print->print if $print->isa('Slic3r::Test::Print'); my $fh = IO::Scalar->new(\my $gcode); $print->process; $print->export_gcode(output_fh => $fh, quiet => 1); $fh->close; return $gcode; } sub _eq { my ($a, $b) = @_; return abs($a - $b) < epsilon; } sub add_facet { my ($facet, $vertices, $facets) = @_; push @$facets, []; for my $i (0..2) { my $v = first { $vertices->[$_][X] == $facet->[$i][X] && $vertices->[$_][Y] == $facet->[$i][Y] && $vertices->[$_][Z] == $facet->[$i][Z] } 0..$#$vertices; if (!defined $v) { push @$vertices, [ @{$facet->[$i]}[X,Y,Z] ]; $v = $#$vertices; } $facets->[-1][$i] = $v; } } package Slic3r::Test::Print; use Moo; has 'print' => (is => 'ro', required => 1, handles => [qw(process apply_config)]); has 'models' => (is => 'ro', required => 1); 1; Slic3r-1.2.9/lib/Slic3r/Test/000077500000000000000000000000001254023100400154755ustar00rootroot00000000000000Slic3r-1.2.9/lib/Slic3r/Test/SectionCut.pm000066400000000000000000000163171254023100400201230ustar00rootroot00000000000000package Slic3r::Test::SectionCut; use Moo; use List::Util qw(first min max); use Slic3r::Geometry qw(unscale); use Slic3r::Geometry::Clipper qw(intersection_pl); use SVG; use Slic3r::SVG; has 'print' => (is => 'ro', required => 1); has 'scale' => (is => 'ro', default => sub { 30 }); has 'y_percent' => (is => 'ro', default => sub { 0.5 }); # Y coord of section line expressed as factor has 'line' => (is => 'rw'); has '_height' => (is => 'rw'); has '_svg' => (is => 'rw'); has '_svg_style' => (is => 'rw', default => sub { {} }); sub BUILD { my $self = shift; # calculate the Y coordinate of the section line my $bb = $self->print->bounding_box; my $y = ($bb->y_min + $bb->y_max) * $self->y_percent; # store our section line $self->line(Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ])); } sub export_svg { my $self = shift; my ($filename) = @_; # get bounding box of print and its height # (Print should return a BoundingBox3 object instead) my $bb = $self->print->bounding_box; my $print_size = $bb->size; $self->_height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects})); # initialize the SVG canvas $self->_svg(my $svg = SVG->new( width => $self->scale * unscale($print_size->x), height => $self->scale * $self->_height, )); # set default styles $self->_svg_style->{'stroke-width'} = 1; $self->_svg_style->{'fill-opacity'} = 0.5; $self->_svg_style->{'stroke-opacity'} = 0.2; # plot perimeters $self->_svg_style->{'stroke'} = '#EE0000'; $self->_svg_style->{'fill'} = '#FF0000'; $self->_plot_group(sub { map @{$_->perimeters}, @{$_[0]->regions} }); # plot infill $self->_svg_style->{'stroke'} = '#444444'; $self->_svg_style->{'fill'} = '#454545'; $self->_plot_group(sub { map @{$_->fills}, @{$_[0]->regions} }); # plot support material $self->_svg_style->{'stroke'} = '#12EF00'; $self->_svg_style->{'fill'} = '#22FF00'; $self->_plot_group(sub { $_[0]->isa('Slic3r::Layer::Support') ? ($_[0]->support_fills, $_[0]->support_interface_fills) : () }); Slic3r::open(\my $fh, '>', $filename); print $fh $svg->xmlify; close $fh; printf "Section cut SVG written to %s\n", $filename; } sub _plot_group { my $self = shift; my ($filter) = @_; my $bb = $self->print->bounding_box; my $g = $self->_svg->group(style => { %{$self->_svg_style} }); foreach my $object (@{$self->print->objects}) { foreach my $copy (@{$object->_shifted_copies}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { # get all ExtrusionPath objects my @paths = map $_->clone, map { ($_->isa('Slic3r::ExtrusionLoop') || $_->isa('Slic3r::ExtrusionPath::Collection')) ? @$_ : $_ } grep defined $_, $filter->($layer); # move paths to location of copy $_->polyline->translate(@$copy) for @paths; if (0) { # export plan with section line and exit require "Slic3r/SVG.pm"; Slic3r::SVG::output( "line.svg", no_arrows => 1, 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->width/2), )}; die "Intersection has more than two points!\n" if defined first { @$_ > 2 } @intersections; # turn intersections to lines my @lines = map Slic3r::Line->new(@$_), @intersections; # align intersections to canvas $_->translate(-$bb->x_min, 0) for @lines; # we want lines oriented from left to right in order to draw # rectangles correctly foreach my $line (@lines) { $line->reverse if $line->a->x > $line->b->x; } if ($path->is_bridge) { foreach my $line (@lines) { my $radius = $path->width / 2; my $width = unscale abs($line->b->x - $line->a->x); if ((10 * $radius) < $width) { # we're cutting the path in the longitudinal direction, so we've got a rectangle $g->rectangle( 'x' => $self->scale * unscale($line->a->x), 'y' => $self->scale * $self->_y($layer->print_z), 'width' => $self->scale * $width, 'height' => $self->scale * $radius * 2, 'rx' => $self->scale * $radius * 0.35, 'ry' => $self->scale * $radius * 0.35, ); } else { $g->circle( 'cx' => $self->scale * (unscale($line->a->x) + $radius), 'cy' => $self->scale * $self->_y($layer->print_z - $radius), 'r' => $self->scale * $radius, ); } } } else { foreach my $line (@lines) { my $height = $path->height; $height = $layer->height if $height == -1; $g->rectangle( 'x' => $self->scale * unscale($line->a->x), 'y' => $self->scale * $self->_y($layer->print_z), 'width' => $self->scale * unscale($line->b->x - $line->a->x), 'height' => $self->scale * $height, 'rx' => $self->scale * $height * 0.5, 'ry' => $self->scale * $height * 0.5, ); } } } } } } } } sub _y { my $self = shift; my ($y) = @_; return $self->_height - $y; } 1; Slic3r-1.2.9/slic3r.pl000077500000000000000000000671101254023100400144150ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use List::Util qw(first); use POSIX qw(setlocale LC_NUMERIC); use Slic3r; use Time::HiRes qw(gettimeofday tv_interval); $|++; binmode STDOUT, ':utf8'; our %opt = (); my %cli_options = (); { my %options = ( 'help' => sub { usage() }, 'version' => sub { print "$Slic3r::VERSION\n"; exit 0 }, 'debug' => \$Slic3r::debug, 'gui' => \$opt{gui}, 'o|output=s' => \$opt{output}, 'save=s' => \$opt{save}, 'load=s@' => \$opt{load}, 'autosave=s' => \$opt{autosave}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'no-plater' => \$opt{no_plater}, 'gui-mode=s' => \$opt{gui_mode}, 'datadir=s' => \$opt{datadir}, 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, 'repair' => \$opt{repair}, 'cut=f' => \$opt{cut}, 'split' => \$opt{split}, 'info' => \$opt{info}, 'scale=f' => \$opt{scale}, 'rotate=i' => \$opt{rotate}, 'duplicate=i' => \$opt{duplicate}, 'duplicate-grid=s' => \$opt{duplicate_grid}, 'print-center=s' => \$opt{print_center}, ); foreach my $opt_key (keys %{$Slic3r::Config::Options}) { my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; # allow both the dash-separated option name and the full opt_key $options{ "$opt_key|$cli" } = \$cli_options{$opt_key}; } GetOptions(%options) or usage(1); } # load configuration files my @external_configs = (); if ($opt{load}) { foreach my $configfile (@{$opt{load}}) { $configfile = Slic3r::decode_path($configfile); if (-e $configfile) { push @external_configs, Slic3r::Config->load($configfile); } elsif (-e "$FindBin::Bin/$configfile") { printf STDERR "Loading $FindBin::Bin/$configfile\n"; push @external_configs, Slic3r::Config->load("$FindBin::Bin/$configfile"); } else { $opt{ignore_nonexistent_config} or die "Cannot find specified configuration file ($configfile).\n"; } } } # 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}) { if (@{$cli_config->get_keys} > 0) { $cli_config->save($opt{save}); } else { Slic3r::Config->new_from_defaults->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{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { { no warnings 'once'; $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // ''); $Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::mode = $opt{gui_mode}; $Slic3r::GUI::autosave = $opt{autosave}; } $gui = Slic3r::GUI->new; setlocale(LC_NUMERIC, 'C'); $gui->{mainframe}->load_config_file($_) for @{$opt{load}}; $gui->{mainframe}->load_config($cli_config); foreach my $input_file (@ARGV) { $input_file = Slic3r::decode_path($input_file); $gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater}; } $gui->MainLoop; exit; } die $@ if $@ && $opt{gui}; if (@ARGV) { # slicing from command line $config->validate; if ($opt{repair}) { foreach my $file (@ARGV) { $file = Slic3r::decode_path($file); die "Repair is currently supported only on STL files\n" if $file !~ /\.stl$/i; my $output_file = $file; $output_file =~ s/\.(stl)$/_fixed.obj/i; my $tmesh = Slic3r::TriangleMesh->new; $tmesh->ReadSTLFile($file); $tmesh->repair; $tmesh->WriteOBJFile($output_file); } exit; } if ($opt{cut}) { foreach my $file (@ARGV) { $file = Slic3r::decode_path($file); 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; } if ($opt{split}) { foreach my $file (@ARGV) { $file = Slic3r::decode_path($file); my $model = Slic3r::Model->read_from_file($file); $model->add_default_instances; my $mesh = $model->mesh; $mesh->repair; my $part_count = 0; foreach my $new_mesh (@{$mesh->split}) { my $output_file = sprintf '%s_%02d.stl', $file, ++$part_count; printf "Writing to %s\n", basename($output_file); Slic3r::Format::STL->write_file($output_file, $new_mesh, binary => 1); } } exit; } while (my $input_file = shift @ARGV) { $input_file = Slic3r::decode_path($input_file); my $model; if ($opt{merge}) { my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0); $model = Slic3r::Model->merge(@models); } else { $model = Slic3r::Model->read_from_file($input_file); } if ($opt{info}) { $model->print_info; next; } if (defined $opt{duplicate_grid}) { $opt{duplicate_grid} = [ split /[,x]/, $opt{duplicate_grid}, 2 ]; } if (defined $opt{print_center}) { $opt{print_center} = Slic3r::Pointf->new(split /[,x]/, $opt{print_center}, 2); } my $sprint = Slic3r::Print::Simple->new( scale => $opt{scale} // 1, rotate => $opt{rotate} // 0, duplicate => $opt{duplicate} // 1, duplicate_grid => $opt{duplicate_grid} // [1,1], print_center => $opt{print_center} // Slic3r::Pointf->new(100,100), status_cb => sub { my ($percent, $message) = @_; printf "=> %s\n", $message; }, output_file => $opt{output}, ); $sprint->apply_config($config); $sprint->set_model($model); if ($opt{export_svg}) { $sprint->export_svg; } else { my $t0 = [gettimeofday]; $sprint->export_gcode; # output some statistics { my $duration = tv_interval($t0); printf "Done. Process took %d minutes and %.3f seconds\n", int($duration/60), ($duration - int($duration/60)*60); # % truncates to integer } printf "Filament required: %.1fmm (%.1fcm3)\n", $sprint->total_used_filament, $sprint->total_extruded_volume/1000; } } } else { usage(1) unless $opt{save}; } sub usage { my ($exit_code) = @_; my $config = Slic3r::Config->new_from_defaults->as_hash; my $j = ''; if ($Slic3r::have_threads) { $j = <<"EOF"; -j, --threads Number of threads to use (1+, default: $config->{threads}) EOF } print <<"EOF"; Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers written by Alessandro Ranellucci - http://slic3r.org/ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... --help Output this usage screen and exit --version Output the version of Slic3r and exit --save Save configuration to the specified file --load Load configuration from the specified file. It can be used more than once to load options from multiple files. -o, --output 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.) 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 _fixed.obj --cut Cut given input files at given Z (relative) and export them as _upper.stl and _lower.stl --split Split the shells contained in given STL file into several STL files --info Output information about the supplied file(s) and exit $j GUI options: --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file Output options: --output-filename-format Output file name format; all config options enclosed in brackets will be replaced by their values, as well as [input_filename_base] and [input_filename] (default: $config->{output_filename_format}) --post-process Generated G-code will be processed with the supplied script; call this more than once to process through multiple scripts. --export-svg Export a SVG file containing slices instead of G-code. -m, --merge If multiple files are supplied, they will be composed into a single print rather than processed individually. Printer options: --nozzle-diameter Diameter of nozzle in mm (default: $config->{nozzle_diameter}->[0]) --print-center Coordinates in mm of the point to center the print around (default: 100,100) --z-offset Additional height in mm to add to vertical coordinates (+/-, default: $config->{z_offset}) --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/machinekit/no-extrusion, default: $config->{gcode_flavor}) --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no) --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported by all firmwares) --gcode-comments Make G-code verbose by adding comments (default: no) --vibration-limit Limit the frequency of moves on X and Y axes (Hz, set zero to disable; default: $config->{vibration_limit}) --pressure-advance Adjust pressure using the experimental advance algorithm (K constant, set zero to disable; default: $config->{pressure_advance}) Filament options: --filament-diameter Diameter in mm of your raw filament (default: $config->{filament_diameter}->[0]) --extrusion-multiplier Change this to alter the amount of plastic extruded. There should be very little need to change this value, which is only useful to compensate for filament packing (default: $config->{extrusion_multiplier}->[0]) --temperature Extrusion temperature in degree Celsius, set 0 to disable (default: $config->{temperature}->[0]) --first-layer-temperature Extrusion temperature for the first layer, in degree Celsius, set 0 to disable (default: same as --temperature) --bed-temperature Heated bed temperature in degree Celsius, set 0 to disable (default: $config->{bed_temperature}) --first-layer-bed-temperature Heated bed temperature for the first layer, in degree Celsius, set 0 to disable (default: same as --bed-temperature) Speed options: --travel-speed Speed of non-print moves in mm/s (default: $config->{travel_speed}) --perimeter-speed Speed of print moves for perimeters in mm/s (default: $config->{perimeter_speed}) --small-perimeter-speed Speed of print moves for small perimeters in mm/s or % over perimeter speed (default: $config->{small_perimeter_speed}) --external-perimeter-speed Speed of print moves for the external perimeter in mm/s or % over perimeter speed (default: $config->{external_perimeter_speed}) --infill-speed Speed of print moves in mm/s (default: $config->{infill_speed}) --solid-infill-speed Speed of print moves for solid surfaces in mm/s or % over infill speed (default: $config->{solid_infill_speed}) --top-solid-infill-speed Speed of print moves for top surfaces in mm/s or % over solid infill speed (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 value or as a percentage over normal speeds (default: $config->{first_layer_speed}) Acceleration options: --perimeter-acceleration Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero to disable; default: $config->{perimeter_acceleration}) --infill-acceleration Overrides firmware's default acceleration for infill. (mm/s^2, set zero to disable; default: $config->{infill_acceleration}) --bridge-acceleration Overrides firmware's default acceleration for bridges. (mm/s^2, set zero to disable; default: $config->{bridge_acceleration}) --first-layer-acceleration Overrides firmware's default acceleration for first layer. (mm/s^2, set zero to disable; default: $config->{first_layer_acceleration}) --default-acceleration Acceleration will be reset to this value after the specific settings above have been applied. (mm/s^2, set zero to disable; default: $config->{default_acceleration}) Accuracy options: --layer-height Layer height in mm (default: $config->{layer_height}) --first-layer-height Layer height for first layer (mm or %, default: $config->{first_layer_height}) --infill-every-layers Infill every N layers (default: $config->{infill_every_layers}) --solid-infill-every-layers Force a solid layer every N layers (default: $config->{solid_infill_every_layers}) Print options: --perimeters Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters}) --top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: $config->{top_solid_layers}) --bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: $config->{bottom_solid_layers}) --solid-layers Shortcut for setting the two options above at once --fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%) --fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle}) --fill-pattern Pattern to use to fill non-solid layers (default: $config->{fill_pattern}) --external-fill-pattern Pattern to use to fill solid layers (default: $config->{external_fill_pattern}) --start-gcode Load initial G-code from the supplied file. This will overwrite the default command (home all axes [G28]). --end-gcode Load final G-code from the supplied file. This will overwrite the default commands (turn off temperature [M104 S0], home X axis [G28 X], disable motors [M84]). --before-layer-gcode Load before-layer-change G-code from the supplied file (default: nothing). --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). --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) --only-retract-when-crossing-perimeters Disable retraction when travelling between infill paths inside the same island. (default: no) --solid-infill-below-area Force solid infill when a region has a smaller area than this threshold (mm^2, default: $config->{solid_infill_below_area}) --infill-only-where-needed Only infill under ceilings (default: no) --infill-first Make infill before perimeters (default: no) 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) --thin-walls Detect single-width walls (default: yes) --overhangs Experimental option to use bridge flow, speed and fan for overhangs (default: yes) Support material options: --support-material Generate support material for overhangs --support-material-threshold Overhang threshold angle (range: 0-90, set 0 for automatic detection, default: $config->{support_material_threshold}) --support-material-pattern Pattern to use for support material (default: $config->{support_material_pattern}) --support-material-spacing Spacing between pattern lines (mm, default: $config->{support_material_spacing}) --support-material-angle Support material angle in degrees (range: 0-90, default: $config->{support_material_angle}) --support-material-contact-distance Vertical distance between object and support material (0+, default: $config->{support_material_contact_distance}) --support-material-interface-layers Number of perpendicular layers between support material and object (0+, default: $config->{support_material_interface_layers}) --support-material-interface-spacing Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: $config->{support_material_interface_spacing}) --raft-layers Number of layers to raise the printed objects by (range: 0+, default: $config->{raft_layers}) --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]) --retract-speed Speed for retraction in mm/s (default: $config->{retract_speed}[0]) --retract-restart-extra Additional amount of filament in mm to push after compensating retraction (default: $config->{retract_restart_extra}[0]) --retract-before-travel Only retract before travel moves of this length in mm (default: $config->{retract_before_travel}[0]) --retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0]) --retract-layer-change Enforce a retraction before each Z move (default: no) --wipe Wipe the nozzle while doing a retraction (default: no) Retraction options for multi-extruder setups: --retract-length-toolchange Length of retraction in mm when disabling tool (default: $config->{retract_length_toolchange}[0]) --retract-restart-extra-toolchange Additional amount of filament in mm to push after switching tool (default: $config->{retract_restart_extra_toolchange}[0]) Cooling options: --cooling Enable fan and cooling control --min-fan-speed Minimum fan speed (default: $config->{min_fan_speed}%) --max-fan-speed Maximum fan speed (default: $config->{max_fan_speed}%) --bridge-fan-speed Fan speed to use when bridging (default: $config->{bridge_fan_speed}%) --fan-below-layer-time Enable fan if layer print time is below this approximate number of seconds (default: $config->{fan_below_layer_time}) --slowdown-below-layer-time Slow down if layer print time is below this approximate number of seconds (default: $config->{slowdown_below_layer_time}) --min-print-speed Minimum print speed (mm/s, default: $config->{min_print_speed}) --disable-fan-first-layers Disable fan for the first N layers (default: $config->{disable_fan_first_layers}) --fan-always-on Keep fan always on at min fan speed, even for layers that don't need cooling Skirt options: --skirts Number of skirts to draw (0+, default: $config->{skirts}) --skirt-distance Distance in mm between innermost skirt and object (default: $config->{skirt_distance}) --skirt-height Height of skirts to draw (expressed in layers, 0+, default: $config->{skirt_height}) --min-skirt-length Generate no less than the number of loops required to consume this length of filament on the first layer, for each extruder (mm, 0+, default: $config->{min_skirt_length}) --brim-width Width of the brim that will get added to each object to help adhesion (mm, default: $config->{brim_width}) Transform options: --scale Factor for scaling input object (default: 1) --rotate Rotation angle in degrees (0-360, default: 0) --duplicate Number of items with auto-arrange (1+, default: 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 starting the next one; watch out for extruder collisions (default: no) --extruder-clearance-radius Radius in mm above which extruder won't collide with anything (default: $config->{extruder_clearance_radius}) --extruder-clearance-height Maximum vertical extruder depth; i.e. vertical distance from extruder tip and carriage bottom (default: $config->{extruder_clearance_height}) Miscellaneous options: --notes Notes to be added as comments to the output file --resolution Minimum detail resolution (mm, set zero for full resolution, default: $config->{resolution}) Flow options (advanced): --extrusion-width Set extrusion width manually; it accepts either an absolute value in mm (like 0.65) or a percentage over layer height (like 200%) --first-layer-extrusion-width 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 Set a different extrusion width for solid infill --top-infill-extrusion-width Set a different extrusion width for top infill --support-material-extrusion-width Set a different extrusion width for support material --infill-overlap Overlap between infill and perimeters (default: $config->{infill_overlap}) --bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: $config->{bridge_flow_ratio}) Multiple extruder options: --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement (can be specified multiple times, default: 0x0) --perimeter-extruder Extruder to use for perimeters and brim (1+, default: $config->{perimeter_extruder}) --infill-extruder Extruder to use for infill (1+, default: $config->{infill_extruder}) --solid-infill-extruder Extruder to use for solid infill (1+, default: $config->{solid_infill_extruder}) --support-material-extruder Extruder to use for support material, raft and skirt (1+, default: $config->{support_material_extruder}) --support-material-interface-extruder Extruder to use for support material interface (1+, default: $config->{support_material_interface_extruder}) --ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping (default: no) --standby-temperature-delta Temperature difference to be applied when an extruder is not active and --ooze-prevention is enabled (default: $config->{standby_temperature_delta}) EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/t/000077500000000000000000000000001254023100400131145ustar00rootroot00000000000000Slic3r-1.2.9/t/angles.t000066400000000000000000000057461254023100400145660ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 34; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Geometry qw(rad2deg_dir angle3points PI); #========================================================== { is line_atan([ [0, 0], [10, 0] ]), (0), 'E atan2'; is line_atan([ [10, 0], [0, 0] ]), (PI), 'W atan2'; is line_atan([ [0, 0], [0, 10] ]), (PI/2), 'N atan2'; is line_atan([ [0, 10], [0, 0] ]), -(PI/2), 'S atan2'; is line_atan([ [10, 10], [0, 0] ]), -(PI*3/4), 'SW atan2'; is line_atan([ [0, 0], [10, 10] ]), (PI*1/4), 'NE atan2'; is line_atan([ [0, 10], [10, 0] ]), -(PI*1/4), 'SE atan2'; is line_atan([ [10, 0], [0, 10] ]), (PI*3/4), 'NW atan2'; } #========================================================== { is line_orientation([ [0, 0], [10, 0] ]), (0), 'E orientation'; is line_orientation([ [0, 0], [0, 10] ]), (PI/2), 'N orientation'; is line_orientation([ [10, 0], [0, 0] ]), (PI), 'W orientation'; is line_orientation([ [0, 10], [0, 0] ]), (PI*3/2), 'S orientation'; is line_orientation([ [0, 0], [10, 10] ]), (PI*1/4), 'NE orientation'; is line_orientation([ [10, 0], [0, 10] ]), (PI*3/4), 'NW orientation'; is line_orientation([ [10, 10], [0, 0] ]), (PI*5/4), 'SW orientation'; is line_orientation([ [0, 10], [10, 0] ]), (PI*7/4), 'SE orientation'; } #========================================================== { is line_direction([ [0, 0], [10, 0] ]), (0), 'E direction'; is line_direction([ [10, 0], [0, 0] ]), (0), 'W direction'; is line_direction([ [0, 0], [0, 10] ]), (PI/2), 'N direction'; is line_direction([ [0, 10], [0, 0] ]), (PI/2), 'S direction'; is line_direction([ [10, 10], [0, 0] ]), (PI*1/4), 'SW direction'; is line_direction([ [0, 0], [10, 10] ]), (PI*1/4), 'NE direction'; is line_direction([ [0, 10], [10, 0] ]), (PI*3/4), 'SE direction'; is line_direction([ [10, 0], [0, 10] ]), (PI*3/4), 'NW direction'; } #========================================================== { is rad2deg_dir(0), 90, 'E (degrees)'; is rad2deg_dir(PI), 270, 'W (degrees)'; is rad2deg_dir(PI/2), 0, 'N (degrees)'; is rad2deg_dir(-(PI/2)), 180, 'S (degrees)'; is rad2deg_dir(PI*1/4), 45, 'NE (degrees)'; is rad2deg_dir(PI*3/4), 135, 'NW (degrees)'; is rad2deg_dir(PI/6), 60, '30°'; is rad2deg_dir(PI/6*2), 30, '60°'; } #========================================================== { is angle3points([0,0], [10,0], [0,10]), PI/2, 'CW angle3points'; is angle3points([0,0], [0,10], [10,0]), PI/2*3, 'CCW angle3points'; } #========================================================== sub line_atan { my ($l) = @_; return Slic3r::Line->new(@$l)->atan2_; } sub line_orientation { my ($l) = @_; return Slic3r::Line->new(@$l)->orientation; } sub line_direction { my ($l) = @_; return Slic3r::Line->new(@$l)->direction; }Slic3r-1.2.9/t/arcs.t000066400000000000000000000120201254023100400142240ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 24; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scaled_epsilon epsilon scale unscale X Y deg2rad); { 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'; ok my $arc = Slic3r::GCode::ArcFitting::polyline_to_arc($polyline), 'arc is detected'; is $ccw, $arc->is_ccw, 'arc orientation is correct'; 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], [12.3344536385591,19.7236992039768], [13.0901699437495,19.5105651629515], [13.8268343236509,19.2387953251129], [14.5399049973955,18.9100652418837], [15.2249856471595,18.5264016435409], [15.8778525229247,18.0901699437495], [16.4944804833018,17.6040596560003] ); my $path1 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@points), role => EXTR_ROLE_FILL, mm3_per_mm => 0.5, ); my $path2 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(reverse @points), role => EXTR_ROLE_FILL, mm3_per_mm => 0.5, ); my @paths1 = $path1->detect_arcs(10, scale 1); my @paths2 = $path2->detect_arcs(10, scale 1); is scalar(@paths1), 1, 'path collection now contains one path'; is scalar(@paths2), 1, 'path collection now contains one path'; isa_ok $paths1[0], 'Slic3r::ExtrusionPath::Arc', 'path'; isa_ok $paths2[0], 'Slic3r::ExtrusionPath::Arc', 'path'; my $expected_length = scale 7.06858347057701; ok abs($paths1[0]->length - $expected_length) < scaled_epsilon, 'cw oriented arc has correct length'; ok abs($paths2[0]->length - $expected_length) < scaled_epsilon, 'ccw oriented arc has correct length'; is $paths1[0]->orientation, 'cw', 'cw orientation was correctly detected'; is $paths2[0]->orientation, 'ccw', 'ccw orientation was correctly detected'; is $paths1[0]->flow_spacing, $path1->flow_spacing, 'flow spacing was correctly preserved'; my $center1 = [ map sprintf('%.0f', $_), @{ $paths1[0]->center } ]; ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; my $center2 = [ map sprintf('%.0f', $_), @{ $paths2[0]->center } ]; ok abs($center2->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; } #========================================================== Slic3r-1.2.9/t/avoid_crossing_perimeters.t000066400000000000000000000007601254023100400205540ustar00rootroot00000000000000use Test::More tests => 1; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('avoid_crossing_perimeters', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects"; } __END__ Slic3r-1.2.9/t/bridges.t000066400000000000000000000071221254023100400147220ustar00rootroot00000000000000use 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) = @_; if (ref($lower) eq 'ARRAY') { $lower = Slic3r::ExPolygon::Collection->new(@$lower); } $expected_coverage //= -1; $expected_coverage = $bridge->area if $expected_coverage == -1; my $bd = Slic3r::BridgeDetector->new($bridge, $lower, scale 0.5); $tolerance //= rad2deg($bd->resolution) + epsilon; $bd->detect_angle; my $result = $bd->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__ Slic3r-1.2.9/t/clean_polylines.t000066400000000000000000000106671254023100400164730ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 6; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; { my $polyline = Slic3r::Polyline->new( [0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0], ); $polyline->simplify(1); is_deeply $polyline->pp, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker'; } { my $polyline = Slic3r::Polyline->new( [0,0], [50,50], [100,0], [125,-25], [150,50], ); $polyline->simplify(25); is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; } { my $gear = Slic3r::Polygon->new_scale( [144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464], [121.7238,291.5082], [117.1631,290.2776], [107.9198,308.2068], [100.1735,304.5101], [104.9896,290.3672], [106.6511,286.2133], [93.453,279.2327], [81.0065,271.4171], [67.7886,286.5055], [60.7927,280.1127], [69.3928,268.2566], [72.7271,264.9224], [61.8152,253.9959], [52.2273,242.8494], [47.5799,245.7224], [34.6577,252.6559], [30.3369,245.2236], [42.1712,236.3251], [46.1122,233.9605], [43.2099,228.4876], [35.0862,211.5672], [33.1441,207.0856], [13.3923,212.1895], [10.6572,203.3273], [6.0707,204.8561], [7.2775,204.4259], [29.6713,196.3631], [25.9815,172.1277], [25.4589,167.2745], [19.8337,167.0129], [5.0625,166.3346], [5.0625,156.9425], [5.3701,156.9282], [21.8636,156.1628], [25.3713,156.4613], [25.4243,155.9976], [29.3432,155.8157], [30.3838,149.3549], [26.3596,147.8137], [27.1085,141.2604], [29.8466,126.8337], [24.5841,124.9201], [10.6664,119.8989], [13.4454,110.9264], [33.1886,116.0691], [38.817,103.1819], [45.8311,89.8133], [30.4286,76.81], [35.7686,70.0812], [48.0879,77.6873], [51.564,81.1635], [61.9006,69.1791], [72.3019,58.7916], [60.5509,42.5416], [68.3369,37.1532], [77.9524,48.1338], [80.405,52.2215], [92.5632,44.5992], [93.0123,44.3223], [106.3561,37.2056], [100.8631,17.4679], [108.759,14.3778], [107.3148,11.1283], [117.0002,32.8627], [140.9109,27.3974], [145.7004,26.4994], [145.1346,6.1011], [154.502,5.4063], [156.9398,25.6501], [171.0557,26.2017], [181.3139,27.323], [186.2377,27.8532], [191.6031,8.5474], [200.6724,11.2756], [197.2362,30.2334], [220.0789,39.1906], [224.3261,41.031], [236.3506,24.4291], [243.6897,28.6723], [234.2956,46.7747], [245.6562,55.1643], [257.2523,65.0901], [261.4374,61.5679], [273.1709,52.8031], [278.555,59.5164], [268.4334,69.8001], [264.1615,72.3633], [268.2763,77.9442], [278.8488,93.5305], [281.4596,97.6332], [286.4487,95.5191], [300.2821,90.5903], [303.4456,98.5849], [286.4523,107.7253], [293.7063,131.1779], [294.9748,135.8787], [314.918,133.8172], [315.6941,143.2589], [300.9234,146.1746], [296.6419,147.0309], [297.1839,161.7052], [296.6136,176.3942], [302.1147,177.4857], [316.603,180.3608], [317.1658,176.7341], [315.215,189.6589], [315.1749,189.6548], [294.9411,187.5222], [291.13,201.7233], [286.2615,215.5916], [291.1944,218.2545], [303.9158,225.1271], [299.2384,233.3694], [285.7165,227.6001], [281.7091,225.1956], [273.8981,237.6457], [268.3486,245.2248], [267.4538,246.4414], [264.8496,250.0221], [268.6392,253.896], [278.5017,265.2131], [272.721,271.4403], [257.2776,258.3579], [234.4345,276.5687], [242.6222,294.8315], [234.9061,298.5798], [227.0321,286.2841], [225.2505,281.8301], [211.5387,287.8187], [202.3025,291.0935], [197.307,292.831], [199.808,313.1906], [191.5298,315.0787], [187.3082,299.8172], [186.4201,295.3766], [180.595,296.0487], [161.7854,297.4248], [156.8058,297.6214], [154.3395,317.8592], ); my $num_points = scalar @$gear; my $simplified = $gear->simplify(1000); ok @$simplified == 1, 'gear simplified to a single polygon'; ###note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); ok @{$simplified->[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; } { my $hole_in_square = Slic3r::Polygon->new( # cw [140, 140], [140, 160], [160, 160], [160, 140], ); my $simplified = $hole_in_square->simplify(2); is scalar(@$simplified), 1, 'hole simplification returns one polygon'; ok $simplified->[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon'; } Slic3r-1.2.9/t/clipper.t000066400000000000000000000044001254023100400147350ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 6; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(sum); use Slic3r; use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex diff_pl); { my $square = [ # ccw [10, 10], [20, 10], [20, 20], [10, 20], ]; my $hole_in_square = [ # cw [14, 14], [14, 16], [16, 16], [16, 14], ]; my $square2 = [ # ccw [5, 12], [25, 12], [25, 18], [5, 18], ]; my $intersection = intersection_ex([ $square, $hole_in_square ], [ $square2 ]); is sum(map $_->area, @$intersection), Slic3r::ExPolygon->new( [ [20, 18], [10, 18], [10, 12], [20, 12], ], [ [14, 16], [16, 16], [16, 14], [14, 14], ], )->area, 'hole is preserved after intersection'; } #========================================================== { my $contour1 = [ [0,0], [40,0], [40,40], [0,40] ]; # ccw my $contour2 = [ [10,10], [30,10], [30,30], [10,30] ]; # ccw my $hole = [ [15,15], [15,25], [25,25], [25,15] ]; # cw my $union = union_ex([ $contour1, $contour2, $hole ]); is_deeply [ map $_->pp, @$union ], [[ [ [40,40], [0,40], [0,0], [40,0] ] ]], 'union of two ccw and one cw is a contour with no holes'; my $diff = diff_ex([ $contour1, $contour2 ], [ $hole ]); is sum(map $_->area, @$diff), Slic3r::ExPolygon->new([ [40,40], [0,40], [0,0], [40,0] ], [ [15,25], [25,25], [25,15], [15,15] ])->area, '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__ Slic3r-1.2.9/t/collinear.t000066400000000000000000000041411254023100400152510ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 11; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Geometry qw(collinear); #========================================================== { my @lines = ( Slic3r::Line->new([0,4], [4,2]), Slic3r::Line->new([2,3], [8,0]), Slic3r::Line->new([6,1], [8,0]), ); is collinear($lines[0], $lines[1]), 1, 'collinear'; is collinear($lines[1], $lines[2]), 1, 'collinear'; is collinear($lines[0], $lines[2]), 1, 'collinear'; } #========================================================== { # horizontal my @lines = ( Slic3r::Line->new([0,1], [5,1]), Slic3r::Line->new([2,1], [8,1]), ); is collinear($lines[0], $lines[1]), 1, 'collinear'; } #========================================================== { # vertical my @lines = ( Slic3r::Line->new([1,0], [1,5]), Slic3r::Line->new([1,2], [1,8]), ); is collinear($lines[0], $lines[1]), 1, 'collinear'; } #========================================================== { # non overlapping my @lines = ( Slic3r::Line->new([0,1], [5,1]), Slic3r::Line->new([7,1], [10,1]), ); is collinear($lines[0], $lines[1], 1), 0, 'non overlapping'; is collinear($lines[0], $lines[1], 0), 1, 'overlapping'; } #========================================================== { # with one common point my @lines = ( Slic3r::Line->new([0,4], [4,2]), Slic3r::Line->new([4,2], [8,0]), ); is collinear($lines[0], $lines[1], 1), 1, 'one common point'; is collinear($lines[0], $lines[1], 0), 1, 'one common point'; } #========================================================== { # not collinear my @lines = ( Slic3r::Line->new([290000000,690525600], [285163380,684761540]), Slic3r::Line->new([285163380,684761540], [193267599,575244400]), ); is collinear($lines[0], $lines[1], 0), 0, 'not collinear'; is collinear($lines[0], $lines[1], 1), 0, 'not collinear'; } #========================================================== Slic3r-1.2.9/t/combineinfill.t000066400000000000000000000142401254023100400161140ustar00rootroot00000000000000use Test::More; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Surface ':types'; use Slic3r::Test; plan tests => 8; { my $test = sub { my ($config) = @_; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; my $tool = undef; my %layers = (); # layer_z => 1 my %layer_infill = (); # layer_z => has_infill Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) { $layer_infill{$self->Z} //= 0; if ($tool == $config->infill_extruder-1) { $layer_infill{$self->Z} = 1; } } $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} > 0; }); my $layers_with_perimeters = scalar(keys %layer_infill); my $layers_with_infill = grep $_ > 0, values %layer_infill; is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers'; # first infill layer is never combined, so we don't consider it $layers_with_infill--; $layers_with_perimeters--; # we expect that infill is generated for half the number of combined layers # plus for each single layer that was not combined (remainder) is $layers_with_infill, int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers), 'infill is only present in correct number of layers'; }; my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); $config->set('infill_every_layers', 2); $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); $config->set('support_material_extruder', 3); $config->set('support_material_interface_extruder', 3); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $test->($config); $config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers $config->set('raft_layers', 5); $test->($config); } { my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); $config->set('infill_every_layers', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); $print->process; ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 } @{$print->print->get_object(0)->layers}), 'infill combination produces internal void surfaces'; # we disable combination after infill has been generated $config->set('infill_every_layers', 1); $print->apply_config($config); $print->process; ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } @{$print->print->get_object(0)->layers}), 'infill combination is idempotent'; } # the following needs to be adapted to the new API if (0) { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('top_solid_layers', 0); $config->set('infill_every_layers', 6); $config->set('layer_height', 0.06); $config->set('perimeters', 1); my $test = sub { my ($shift) = @_; my $self = Slic3r::Test::init_print('20mm_cube', config => $config); $shift /= &Slic3r::SCALING_FACTOR; my $scale = 4; # make room for fat infill lines with low layer height # Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight). # The test here is to put such a slight slope on the walls that it should # not trigger any extra fill on fill layers that should be empty when # combine infill is enabled. $_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; $_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; $_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices}; # copy of Print::export_gcode() up to the point # after fill surfaces are combined $_->slice for @{$self->objects}; $_->make_perimeters for @{$self->objects}; $_->detect_surfaces_type for @{$self->objects}; $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; $_->discover_horizontal_shells for @{$self->objects}; $_->combine_infill for @{$self->objects}; # Only layers with id % 6 == 0 should have fill. my $spurious_infill = 0; foreach my $layer (map @{$_->layers}, @{$self->objects}) { ++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions}); } $spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6; fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)" unless $spurious_infill == 0; 1; }; # Test with mm skew offsets for the top of the 20mm-high box for my $shift (0, 0.0001, 1) { ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical"; } } __END__ Slic3r-1.2.9/t/config.t000066400000000000000000000007731254023100400145550ustar00rootroot00000000000000use Test::More tests => 2; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.123); $config->setenv; is $ENV{SLIC3R_LAYER_HEIGHT}, '0.123', 'setenv'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('perimeter_extrusion_width', '250%'); ok $config->validate, 'percent extrusion width is validated'; } __END__ Slic3r-1.2.9/t/cooling.t000066400000000000000000000117201254023100400147340ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 11; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Test; sub buffer { my $config = shift || Slic3r::Config->new; my $print_config = Slic3r::Config::Print->new; $print_config->apply_dynamic($config); my $buffer = Slic3r::GCode::CoolingBuffer->new( config => $print_config, gcodegen => Slic3r::GCode->new(print_config => $print_config, layer_count => 10, extruders => []), ); return $buffer; } my $config = Slic3r::Config->new_from_defaults; $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time + 1); my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; } { my $buffer = buffer($config); $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); my $gcode = $buffer->append("G1 X50 F2500\nG1 X100 E1 F3000\nG1 E4 F400", 0, 0, 0.4) . $buffer->flush; unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; like $gcode, qr/F2500/, 'speed is not altered for travel moves'; like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; } { my $buffer = buffer($config); $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time + 1); my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; } { my $buffer = buffer($config); my $gcode = ""; for my $obj_id (0 .. 1) { # use an elapsed time which is < the slowdown threshold but greater than it when summed twice $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4); } $gcode .= $buffer->flush; like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at same Z'; } { my $buffer = buffer($config); my $gcode = ""; for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold but greater than it when summed twice $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } $gcode .= $buffer->flush; unlike $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z'; } { my $buffer = buffer($config); my $gcode = ""; for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold even when summed twice $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time/2 - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } $gcode .= $buffer->flush; like $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('cooling', 1); $config->set('bridge_fan_speed', 100); $config->set('fan_below_layer_time', 0); $config->set('slowdown_below_layer_time', 0); $config->set('bridge_speed', 99); $config->set('top_solid_layers', 1); # internal bridges use solid_infil speed $config->set('bottom_solid_layers', 1); # internal bridges use solid_infil speed $config->set('vibration_limit', 30); # test that fan is turned on even when vibration limit (or other G-code post-processor) is enabled my $print = Slic3r::Test::init_print('overhang', config => $config); my $fan = 0; my $fan_with_incorrect_speeds = my $fan_with_incorrect_print_speeds = 0; my $bridge_with_no_fan = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'M106') { $fan = $args->{S}; $fan_with_incorrect_speeds++ if $fan != 255; } elsif ($cmd eq 'M107') { $fan = 0; } elsif ($info->{extruding} && $info->{dist_XY} > 0) { $fan_with_incorrect_print_speeds++ if ($fan > 0) && ($args->{F} // $self->F) != 60*$config->bridge_speed; $bridge_with_no_fan++ if !$fan && ($args->{F} // $self->F) == 60*$config->bridge_speed; } }); ok !$fan_with_incorrect_speeds, 'bridge fan speed is applied correctly'; ok !$fan_with_incorrect_print_speeds, 'bridge fan is only turned on for bridges'; ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; } __END__ Slic3r-1.2.9/t/custom_gcode.t000066400000000000000000000117451254023100400157640ustar00rootroot00000000000000use Test::More tests => 15; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('2x20x10', config => $conf); my $last_move_was_z_change = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($last_move_was_z_change && $cmd ne $config->layer_gcode) { fail 'custom layer G-code was not applied after Z change'; } if (!$last_move_was_z_change && $cmd eq $config->layer_gcode) { fail 'custom layer G-code was not applied after Z change'; } $last_move_was_z_change = (defined $info->{dist_Z} && $info->{dist_Z} > 0); }); 1; }; $config->set('start_gcode', '_MY_CUSTOM_START_GCODE_'); # to avoid dealing with the nozzle lift in start G-code $config->set('layer_gcode', '_MY_CUSTOM_LAYER_GCODE_'); ok $test->(), "custom layer G-code is applied after Z move and before other moves"; } #========================================================== { my $parser = Slic3r::GCode::PlaceholderParser->new; $parser->apply_config(my $config = Slic3r::Config->new_from_defaults); is $parser->process('[temperature_[foo]]', { foo => '1' }), $config->temperature->[0], "nested config options"; } { my $config = Slic3r::Config->new_from_defaults; $config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode'); $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->print->expanded_output_filepath; my ($t, $h) = map $config->$_, qw(travel_speed layer_height); ok $output_file =~ /ts_${t}_/, 'print config options are replaced in output filename'; ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename'; my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /TRAVEL:$t/, 'print config options are replaced in custom G-code'; 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/, 'temperature placeholder for unused extruder populated with first value'; } } { my $config = Slic3r::Config->new_from_defaults; $config->set('before_layer_gcode', ';BEFORE [layer_num]'); $config->set('layer_gcode', ';CHANGE [layer_num]'); $config->set('support_material', 1); $config->set('layer_height', 0.2); my $print = Slic3r::Test::init_print('overhang', config => $config); my $gcode = Slic3r::Test::gcode($print); my @before = (); my @change = (); foreach my $line (split /\R+/, $gcode) { if ($line =~ /;BEFORE (\d+)/) { push @before, $1; } elsif ($line =~ /;CHANGE (\d+)/) { push @change, $1; fail 'inconsistent layer_num before and after layer change' if $1 != $before[-1]; } } is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes'; ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change), 'layer_num grows continously'; # i.e. no duplicates or regressions } __END__ Slic3r-1.2.9/t/dynamic.t000066400000000000000000000060741254023100400147340ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'variable-width paths are currently disabled'; plan tests => 20; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(X Y scale epsilon); use Slic3r::Surface ':types'; sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $square = Slic3r::ExPolygon->new([ scale_points [0,0], [10,0], [10,10], [0,10], ]); my @offsets = @{$square->noncollapsing_offset_ex(- scale 5)}; is scalar @offsets, 1, 'non-collapsing offset'; } { local $Slic3r::Config = Slic3r::Config->new( perimeters => 3, ); my $w = 0.7; my $perimeter_flow = Slic3r::Flow->new( nozzle_diameter => 0.5, layer_height => 0.4, width => $w, ); my $print = Slic3r::Print->new; my $region = Slic3r::Print::Region->new( print => $print, flows => { perimeter => $perimeter_flow }, ); push @{$print->regions}, $region; my $object = Slic3r::Print::Object->new( print => $print, size => [1,1], ); my $make_layer = sub { my ($width) = @_; my $layer = Slic3r::Layer->new( object => $object, id => 1, slices => [ Slic3r::Surface->new( surface_type => S_TYPE_INTERNAL, expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,$width], [0,$width] ]), ), ], thin_walls => [], ); my $layerm = $layer->region(0); $layer->make_perimeters; return $layerm; }; my %widths = ( 1 * $w => { perimeters => 1, gaps => 0 }, 1.3 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 0.2 * $w)->spacing }, 1.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 0.5 * $w)->spacing }, 2 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->spacing }, 2.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 1.5 * $w)->spacing }, 3 * $w => { perimeters => 2, gaps => 0 }, 4 * $w => { perimeters => 2, gaps => 1, gap_flow_spacing => $perimeter_flow->spacing }, ); foreach my $width (sort keys %widths) { my $layerm = $make_layer->($width); is scalar @{$layerm->perimeters}, $widths{$width}{perimeters}, 'right number of perimeters'; is scalar @{$layerm->thin_fills} ? 1 : 0, $widths{$width}{gaps}, ($widths{$width}{gaps} ? 'gaps were filled' : 'no gaps detected'); # TODO: we should check the exact number of gaps, but we need a better medial axis algorithm my @gaps = map $_, @{$layerm->thin_fills}; if (@gaps) { ok +(!first { abs($_->flow_spacing - $widths{$width}{gap_flow_spacing}) > epsilon } @gaps), 'flow spacing was dynamically adjusted'; } } } __END__ Slic3r-1.2.9/t/fill.t000066400000000000000000000304431254023100400142330ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 43; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); use Slic3r::Surface qw(:types); use Slic3r::Test; sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $print = Slic3r::Print->new; my $filler = Slic3r::Fill::Rectilinear->new( print => $print, bounding_box => Slic3r::Geometry::BoundingBox->new_from_points([ Slic3r::Point->new(0, 0), Slic3r::Point->new(10, 10) ]), ); my $surface_width = 250; my $distance = $filler->adjust_solid_spacing( width => $surface_width, distance => 100, ); is $distance, 125, 'adjusted solid distance'; is $surface_width % $distance, 0, 'adjusted solid distance'; } { my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); my $filler = Slic3r::Fill::Rectilinear->new( bounding_box => $expolygon->bounding_box, angle => 0, ); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_TOP, expolygon => $expolygon, ); my $flow = Slic3r::Flow->new( width => 0.69, height => 0.4, nozzle_diameter => 0.50, ); $filler->spacing($flow->spacing); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); my @paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); is scalar @paths, 1, 'one continuous path'; } } { my $test = sub { my ($expolygon, $flow_spacing, $angle, $density) = @_; my $filler = Slic3r::Fill::Rectilinear->new( bounding_box => $expolygon->bounding_box, angle => $angle // 0, ); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, ); my $flow = Slic3r::Flow->new( width => $flow_spacing, height => 0.4, nozzle_diameter => $flow_spacing, ); $filler->spacing($flow->spacing); my @paths = $filler->fill_surface( $surface, layer_height => $flow->height, density => $density // 1, ); # check whether any part was left uncovered my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); # ignore very small dots @$uncovered = grep $_->area > (scale $flow_spacing)**2, @$uncovered; is scalar(@$uncovered), 0, 'solid surface is fully filled'; if (0 && @$uncovered) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "uncovered.svg", expolygons => [$expolygon], red_expolygons => $uncovered, ); exit; } }; my $expolygon = Slic3r::ExPolygon->new([ [6883102, 9598327.01296997], [6883102, 20327272.01297], [3116896, 20327272.01297], [3116896, 9598327.01296997], ]); $test->($expolygon, 0.55); for (1..20) { $expolygon->scale(1.05); $test->($expolygon, 0.55); } $expolygon = Slic3r::ExPolygon->new( [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] ); $test->($expolygon, 0.524341649025257); $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); $test->($expolygon, 0.5, 45, 0.99); # non-solid infill } { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained path'; } { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([4,0], [10,0], [15,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained path'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained path'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained path'; } for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config->new_from_defaults; $config->set('fill_pattern', $pattern); $config->set('external_fill_pattern', $pattern); $config->set('perimeters', 1); $config->set('skirts', 0); $config->set('fill_density', 20); $config->set('layer_height', 0.05); $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; my $tool = undef; my @perimeter_points = my @infill_points = (); Slic3r::GCode::Reader->new->parse($gcode, 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->perimeter_extruder-1) { push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); } elsif ($tool == $config->infill_extruder-1) { push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); } } }); my $convex_hull = convex_hull(\@perimeter_points); ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; } { my $config = Slic3r::Config->new_from_defaults; $config->set('infill_only_where_needed', 1); $config->set('bottom_solid_layers', 0); $config->set('infill_extruder', 2); $config->set('infill_extrusion_width', 0.5); $config->set('fill_density', 40); $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'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('solid_infill_below_area', 20000000); $config->set('solid_infill_every_layers', 2); $config->set('perimeter_speed', 99); $config->set('external_perimeter_speed', 99); $config->set('cooling', 0); $config->set('first_layer_speed', '100%'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %layers_with_extrusion = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { if (($args->{F} // $self->F) != $config->perimeter_speed*60) { $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); } } }); ok !%layers_with_extrusion, "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('fill_density', 0); $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.35]); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 2); $config->set('infill_extrusion_width', 0.52); $config->set('solid_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 ... ] 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 ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { if ($tool == $config->infill_extruder-1) { my $z = 1 * $self->Z; $infill{$z} ||= []; push @{$infill{$z}}, Slic3r::Line->new_scale( [ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ], ); } } }); 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 = diff($layer0_infill, $layer1_infill); $diff = offset2_ex($diff, -$grow_d, +$grow_d); $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; } __END__ Slic3r-1.2.9/t/flow.t000066400000000000000000000055141254023100400142550ustar00rootroot00000000000000use 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; # allow some tolerance because solid rectilinear infill might be adjusted/stretched ok !(defined first { abs($_ - $E_per_mm_avg) > 0.015 } @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__ Slic3r-1.2.9/t/gaps.t000066400000000000000000000040761254023100400142420ustar00rootroot00000000000000use 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__ Slic3r-1.2.9/t/gcode.t000066400000000000000000000175651254023100400144000ustar00rootroot00000000000000use Test::More tests => 23; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; { my $gcodegen = Slic3r::GCode->new( layer_count => 1, extruders => [], ); $gcodegen->set_origin(Slic3r::Pointf->new(10, 10)); is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('wipe', [1]); $config->set('retract_layer_change', [0]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $have_wipe = 0; my @retract_speeds = (); my $extruded_on_this_layer = 0; my $wiping_on_new_layer = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{travel} && $info->{dist_Z}) { # changing layer $extruded_on_this_layer = 0; } elsif ($info->{extruding} && $info->{dist_XY}) { $extruded_on_this_layer = 1; } elsif ($info->{retracting} && $info->{dist_XY} > 0) { $have_wipe = 1; $wiping_on_new_layer = 1 if !$extruded_on_this_layer; my $move_time = $info->{dist_XY} / ($args->{F} // $self->F); push @retract_speeds, abs($info->{dist_E}) / $move_time; } }); ok $have_wipe, "wipe"; ok !defined (first { abs($_ - $config->retract_speed->[0]*60) < 5 } @retract_speeds), 'wipe moves don\'t retract faster than configured speed'; ok !$wiping_on_new_layer, 'no wiping after layer change'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('z_offset', 5); $config->set('start_gcode', ''); my $test = sub { my ($comment) = @_; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $moves_below_z_offset = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{travel} && exists $args->{Z}) { $moves_below_z_offset++ if $args->{Z} < $config->z_offset; } }); is $moves_below_z_offset, 0, "no Z moves below Z offset ($comment)"; }; $test->("no lift"); $config->set('retract_lift', [3]); $test->("lift < z_offset"); $config->set('retract_lift', [6]); $test->("lift > z_offset"); } { # This tests the following behavior: # - 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 # - temperatures are set correctly my $config = Slic3r::Config->new_from_defaults; $config->set('gcode_comments', 1); $config->set('complete_objects', 1); $config->set('extrusion_axis', 'A'); $config->set('start_gcode', ''); # prevent any default extra Z move $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('temperature', [200]); $config->set('first_layer_temperature', [210]); 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 my @temps = (); 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 } } elsif ($cmd eq 'M104' || $cmd eq 'M109') { push @temps, $args->{S} if !@temps || $args->{S} != $temps[-1]; } }); 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'; is_deeply \@temps, [210, 200, 210, 200, 0], 'expected temperature changes'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('retract_length', [1000000]); $config->set('use_relative_e_distances', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); Slic3r::Test::gcode($print); ok $print->print->total_used_filament > 0, 'final retraction is not considered in total used filament'; } { my $test = sub { my ($print, $comment) = @_; my @percent = (); my $got_100 = 0; my $extruding_after_100 = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'M73') { push @percent, $args->{P}; $got_100 = 1 if $args->{P} eq '100'; } if ($info->{extruding} && $got_100) { $extruding_after_100 = 1; } }); # the extruder heater is turned off when M73 P100 is reached ok !(defined first { $_ > 100 } @percent), "M73 is never given more than 100% ($comment)"; ok !$extruding_after_100, "no extrusions after M73 P100 ($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'); } { my $config = Slic3r::Config->new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]); $test->($print, 'one layer object'); } } { my $config = Slic3r::Config->new_from_defaults; $config->set('start_gcode', 'START:[input_filename]'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('spiral_vase', 1); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); my $spiral = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && exists $args->{E} && exists $args->{Z}) { $spiral = 1; } }); ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops'; } __END__ Slic3r-1.2.9/t/geometry.t000066400000000000000000000234471254023100400151460ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 42; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } 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] ]; my $line2 = [ [10, 20], [10, 10] ]; is_deeply Slic3r::Geometry::line_intersection($line1, $line2, 1)->arrayref, [10, 15], 'line_intersection'; #========================================================== $line1 = [ [73.6310778185108/0.0000001, 371.74239268924/0.0000001], [73.6310778185108/0.0000001, 501.74239268924/0.0000001] ]; $line2 = [ [75/0.0000001, 437.9853/0.0000001], [62.7484/0.0000001, 440.4223/0.0000001] ]; isnt Slic3r::Geometry::line_intersection($line1, $line2, 1), undef, 'line_intersection'; #========================================================== { my $polygon = Slic3r::Polygon->new( [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], [285273900, 461246400], [254081000, 515273900], ); # this points belongs to $polyline # note: it's actually a vertex, while we should better check an intermediate point my $point = Slic3r::Point->new(104577600, 327748400); local $Slic3r::Geometry::epsilon = 1E-5; is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp, [ [107014700, 340000000], [104577600, 327748400] ], 'polygon_segment_having_point'; } #========================================================== { my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; } #========================================================== { my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; } #========================================================== my $polygons = [ Slic3r::Polygon->new( # contour, ccw [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], [285273900, 461246400], [254081000, 515273900], ), Slic3r::Polygon->new( # hole, cw [75000000, 502014700], [87251500, 499577600], [97637800, 492637800], [104577600, 482251500], [107014700, 470000000], [104577600, 457748400], [97637800, 447362100], [87251500, 440422300], [75000000, 437985300], [62748400, 440422300], [52362100, 447362100], [45422300, 457748400], [42985300, 470000000], [45422300, 482251500], [52362100, 492637800], [62748400, 499577600], ), ]; #========================================================== { my $p1 = [10, 10]; my $p2 = [10, 20]; my $p3 = [10, 30]; my $p4 = [20, 20]; my $p5 = [0, 20]; is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points'; } { my $p1 = [30, 30]; my $p2 = [20, 20]; my $p3 = [10, 10]; my $p4 = [30, 10]; is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points'; } #========================================================== { my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ]; is polygon_is_convex($cw_square), 0, 'cw square is not convex'; is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex'; my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ]; is polygon_is_convex($convex1), 0, 'concave polygon'; } #========================================================== { my $polyline = Slic3r::Polyline->new([0, 0], [10, 0], [20, 0]); is_deeply [ map $_->pp, @{$polyline->lines} ], [ [ [0, 0], [10, 0] ], [ [10, 0], [20, 0] ], ], 'polyline_lines'; } #========================================================== { my $polygon = Slic3r::Polygon->new([0, 0], [10, 0], [5, 5]); my $result = $polygon->split_at_index(1); is ref($result), 'Slic3r::Polyline', 'split_at_index returns polyline'; is_deeply $result->pp, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index'; } #========================================================== { my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); $bb->scale(2); is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; } #========================================================== { my $line = Slic3r::Line->new([10,10], [20,10]); is $line->grow(5)->[0]->area, Slic3r::Polygon->new([10,5], [20,5], [20,15], [10,15])->area, 'grow line'; } #========================================================== { # if chained_path() works correctly, these points should be joined with no diagonal paths # (thus 26 units long) my @points = map Slic3r::Point->new_scale(@$_), [26,26],[52,26],[0,26],[26,52],[26,0],[0,52],[52,52],[52,0]; my @ordered = @points[@{chained_path_from(\@points, $points[0])}]; ok !(grep { abs($ordered[$_]->distance_to($ordered[$_+1]) - scale 26) > epsilon } 0..$#ordered-1), 'chained_path'; } #========================================================== { my $line = Slic3r::Line->new([0, 0], [20, 0]); is +Slic3r::Point->new(10, 10)->distance_to_line($line), 10, 'distance_to'; is +Slic3r::Point->new(50, 0)->distance_to_line($line), 30, 'distance_to'; is +Slic3r::Point->new(0, 0)->distance_to_line($line), 0, 'distance_to'; is +Slic3r::Point->new(20, 0)->distance_to_line($line), 0, 'distance_to'; is +Slic3r::Point->new(10, 0)->distance_to_line($line), 0, 'distance_to'; } #========================================================== { my $square = Slic3r::Polygon->new_scale( [100,100], [200,100], [200,200], [100,200], ); is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in ccw square'; is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in ccw square'; $square->make_clockwise; is scalar(@{$square->concave_points(PI*4/3)}), 4, 'fuor concave vertices detected in cw square'; is scalar(@{$square->convex_points(PI*2/3)}), 0, 'no convex vertices detected in cw square'; } { 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'; } { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [31286371,461008], ); is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle'; is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle'; } { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [20000000,461012], [31286371,461012], ); is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle having collinear point'; is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle having collinear point'; } { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [31286371,461008], ); my $simplified = $triangle->simplify(250000)->[0]; is scalar(@$simplified), 3, 'triangle is never simplified to less than 3 points'; } __END__ Slic3r-1.2.9/t/layers.t000066400000000000000000000042261254023100400146040ustar00rootroot00000000000000use Test::More tests => 5; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Test qw(_eq); { my $config = Slic3r::Config->new_from_defaults; my $test = sub { my ($conf) = @_; $conf ||= $config; 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 = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{dist_Z}) { push @z, 1*$args->{Z}; } }); ok $z[-1] > 20*1.8 && $z[-1] < 20*2.2, 'resulting G-code has reasonable height'; } __END__ Slic3r-1.2.9/t/loops.t000066400000000000000000000037541254023100400144460ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'temporarily disabled'; plan tests => 4; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Test; { # We only need to slice at one height, so we'll build a non-manifold mesh # that still produces complete loops at that height. Triangular walls are # enough for this purpose. # Basically we want to check what happens when three concentric loops happen # to be at the same height, the two external ones being ccw and the other being # a hole, thus cw. my (@vertices, @facets) = (); Slic3r::Test::add_facet($_, \@vertices, \@facets) for # external surface below the slicing Z [ [0,0,0], [20,0,10], [0,0,10] ], [ [20,0,0], [20,20,10], [20,0,10] ], [ [20,20,0], [0,20,10], [20,20,10] ], [ [0,20,0], [0,0,10], [0,20,10] ], # external insetted surface above the slicing Z [ [2,2,10], [18,2,10], [2,2,20] ], [ [18,2,10], [18,18,10], [18,2,20] ], [ [18,18,10], [2,18,10], [18,18,20] ], [ [2,18,10], [2,2,10], [2,18,20] ], # insetted hole below the slicing Z [ [15,5,0], [5,5,10], [15,5,10] ], [ [15,15,0], [15,5,10], [15,15,10] ], [ [5,15,0], [15,15,10], [5,15,10] ], [ [5,5,0], [5,15,10], [5,5,10] ]; my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadFromPerl(\@vertices, \@facets); $mesh->analyze; my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets; my $loops = Slic3r::TriangleMesh::make_loops(\@lines); is scalar(@$loops), 3, 'correct number of loops detected'; is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected'; my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0); is scalar(@surfaces), 1, 'one surface detected'; is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole'; } __END__ Slic3r-1.2.9/t/multi.t000066400000000000000000000177771254023100400144560ustar00rootroot00000000000000use Test::More tests => 13; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Geometry::Clipper qw(offset); use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('raft_layers', 2); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 3); $config->set('support_material_extruder', 4); $config->set('ooze_prevention', 1); $config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]); $config->set('temperature', [200, 180, 170, 160]); $config->set('first_layer_temperature', [206, 186, 166, 156]); $config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $tool = undef; my @tool_temp = (0,0,0,0); my @toolchange_points = (); my @extrusion_points = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { # ignore initial toolchange if (defined $tool) { my $expected_temp = $self->Z == ($config->get_value('first_layer_height') + $config->z_offset) ? $config->first_layer_temperature->[$tool] : $config->temperature->[$tool]; die 'standby temperature was not set before toolchange' if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta; push @toolchange_points, my $point = Slic3r::Point->new_scale($self->X, $self->Y); } $tool = $1; } elsif ($cmd eq 'M104' || $cmd eq 'M109') { my $t = $args->{T} // $tool; if ($tool_temp[$t] == 0) { fail 'initial temperature is not equal to first layer temperature + standby delta' unless $args->{S} == $config->first_layer_temperature->[$t] + $config->standby_temperature_delta; } $tool_temp[$t] = $args->{S}; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); $point->translate(map +scale($_), @{ $config->extruder_offset->[$tool] }); } }); my $convex_hull = convex_hull(\@extrusion_points); my @t = (); foreach my $point (@toolchange_points) { foreach my $offset (@{$config->extruder_offset}) { push @t, my $p = $point->clone; $p->translate(map +scale($_), @$offset); } } ok !(defined first { $convex_hull->contains_point($_) } @t), 'all nozzles are outside skirt at toolchange'; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "ooze_prevention_test.svg", no_arrows => 1, polygons => [$convex_hull], red_points => \@t, points => \@toolchange_points, ); } # 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'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('support_material_extruder', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'no errors when using non-consecutive extruders'; } { 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); $lower_config->set('top_solid_layers', 1); $upper_config->set('extruder', 2); $upper_config->set('bottom_solid_layers', 1); $upper_config->set('top_solid_layers', 0); my $config = Slic3r::Config->new_from_defaults; $config->set('fill_density', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $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($model, config => $config); my $tool = undef; my %T0_shells = my %T1_shells = (); # 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 (($args->{F} // $self->F) == $config->solid_infill_speed*60) { if ($tool == 0) { $T0_shells{$self->Z} = 1; } elsif ($tool == 1) { $T1_shells{$self->Z} = 1; } } } }); return [ sort keys %T0_shells ], [ sort keys %T1_shells ]; }; { my ($t0, $t1) = $test->(); is scalar(@$t0), 0, 'no interface shells'; is scalar(@$t1), 0, 'no interface shells'; } { $config->set('interface_shells', 1); my ($t0, $t1) = $test->(); is scalar(@$t0), $lower_config->top_solid_layers, 'top interface shells'; is scalar(@$t1), $upper_config->bottom_solid_layers, 'bottom interface shells'; } } { my $model = stacked_cubes(); my $object = $model->objects->[0]; my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('skirts', 0); my $print = Slic3r::Test::init_print($model, config => $config); is $object->volumes->[0]->config->extruder, 1, 'auto_assign_extruders() assigned correct extruder to first volume'; is $object->volumes->[1]->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__ Slic3r-1.2.9/t/perimeters.t000066400000000000000000000521331254023100400154640ustar00rootroot00000000000000use Test::More tests => 33; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r::ExtrusionLoop ':roles'; use Slic3r::ExtrusionPath ':roles'; use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI scale unscale); use Slic3r::Geometry::Clipper qw(union_ex diff union offset); use Slic3r::Surface ':types'; use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('fill_density', 0); $config->set('perimeters', 3); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('cooling', 0); # to prevent speeds from being altered $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered { my $print = Slic3r::Test::init_print('overhang', config => $config); my $has_cw_loops = 0; my $cur_loop; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { $cur_loop ||= [ [$self->X, $self->Y] ]; push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; } else { if ($cur_loop) { $has_cw_loops = 1 if Slic3r::Polygon->new(@$cur_loop)->is_clockwise; $cur_loop = undef; } } }); ok !$has_cw_loops, 'all perimeters extruded ccw'; } foreach my $model (qw(cube_with_hole cube_with_concave_hole)) { $config->set('external_perimeter_speed', 68); my $print = Slic3r::Test::init_print( $model, config => $config, duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves) ); my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0; my $cur_loop; my %external_loops = (); # print_z => count of external loops Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { $cur_loop ||= [ [$self->X, $self->Y] ]; push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; } else { if ($cur_loop) { $has_cw_loops = 1 if Slic3r::Polygon->new_scale(@$cur_loop)->is_clockwise; if ($self->F == $config->external_perimeter_speed*60) { my $move_dest = Slic3r::Point->new_scale(@$info{qw(new_X new_Y)}); # reset counter for second object $external_loops{$self->Z} = 0 if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2; $external_loops{$self->Z}++; my $is_contour = $external_loops{$self->Z} == 2; my $is_hole = $external_loops{$self->Z} == 1; my $loop = Slic3r::Polygon->new_scale(@$cur_loop); my $loop_contains_point = $loop->contains_point($move_dest); $has_outwards_move = 1 if (!$loop_contains_point && $is_contour) # contour should include destination || ($loop_contains_point && $is_hole); # hole should not if ($model eq 'cube_with_concave_hole') { # check that loop starts at a concave vertex my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]); my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex $starts_on_convex_point = 1 if ($convex && $is_contour) || (!$convex && $is_hole); } } $cur_loop = undef; } } }); ok !$has_cw_loops, 'all perimeters extruded ccw'; ok !$has_outwards_move, 'move inwards after completing external loop'; ok !$starts_on_convex_point, 'loops start on concave point if any'; } { $config->set('perimeters', 1); $config->set('perimeter_speed', 77); $config->set('external_perimeter_speed', 66); $config->set('bridge_speed', 99); $config->set('cooling', 1); $config->set('fan_below_layer_time', 0); $config->set('slowdown_below_layer_time', 0); $config->set('bridge_fan_speed', 100); $config->set('bridge_flow_ratio', 33); # arbitrary value $config->set('overhangs', 1); my $print = Slic3r::Test::init_print('overhang', config => $config); my %layer_speeds = (); # print Z => [ speeds ] my $fan_speed = 0; my $bridge_mm_per_mm = ($config->nozzle_diameter->[0]**2) / ($config->filament_diameter->[0]**2) * $config->bridge_flow_ratio; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $fan_speed = 0 if $cmd eq 'M107'; $fan_speed = $args->{S} if $cmd eq 'M106'; if ($info->{extruding} && $info->{dist_XY} > 0) { $layer_speeds{$self->Z} ||= {}; $layer_speeds{$self->Z}{my $feedrate = $args->{F} // $self->F} = 1; fail 'wrong speed found' if $feedrate != $config->perimeter_speed*60 && $feedrate != $config->external_perimeter_speed*60 && $feedrate != $config->bridge_speed*60; if ($feedrate == $config->bridge_speed*60) { fail 'printing overhang but fan is not enabled or running at wrong speed' if $fan_speed != 255; my $mm_per_mm = $info->{dist_E} / $info->{dist_XY}; fail 'wrong bridge flow' if abs($mm_per_mm - $bridge_mm_per_mm) > 0.01; } else { fail 'fan is running when not supposed to' if $fan_speed > 0; } } }); is scalar(grep { keys %$_ > 1 } values %layer_speeds), 1, 'only overhang layer has more than one speed'; } } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.35); $config->set('extra_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_speed', 99); $config->set('external_perimeter_speed', 99); $config->set('small_perimeter_speed', 99); $config->set('thin_walls', 0); my $print = Slic3r::Test::init_print('ipadstand', config => $config); my %perimeters = (); # z => number of loops my $in_loop = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) { $perimeters{$self->Z}++ if !$in_loop; $in_loop = 1; } else { $in_loop = 0; } }); ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('nozzle_diameter', [0.4]); $config->set('perimeters', 2); $config->set('perimeter_extrusion_width', 0.4); $config->set('infill_extrusion_width', 0.53); $config->set('solid_infill_extrusion_width', 0.53); # we just need a pre-filled Print object my $print = Slic3r::Test::init_print('20mm_cube', config => $config); # override a layer's slices my $expolygon = Slic3r::ExPolygon->new([[-71974463,-139999376],[-71731792,-139987456],[-71706544,-139985616],[-71682119,-139982639],[-71441248,-139946912],[-71417487,-139942895],[-71379384,-139933984],[-71141800,-139874480],[-71105247,-139862895],[-70873544,-139779984],[-70838592,-139765856],[-70614943,-139660064],[-70581783,-139643567],[-70368368,-139515680],[-70323751,-139487872],[-70122160,-139338352],[-70082399,-139306639],[-69894800,-139136624],[-69878679,-139121327],[-69707992,-138933008],[-69668575,-138887343],[-69518775,-138685359],[-69484336,-138631632],[-69356423,-138418207],[-69250040,-138193296],[-69220920,-138128976],[-69137992,-137897168],[-69126095,-137860255],[-69066568,-137622608],[-69057104,-137582511],[-69053079,-137558751],[-69017352,-137317872],[-69014392,-137293456],[-69012543,-137268207],[-68999369,-137000000],[-63999999,-137000000],[-63705947,-136985551],[-63654984,-136977984],[-63414731,-136942351],[-63364756,-136929840],[-63129151,-136870815],[-62851950,-136771631],[-62585807,-136645743],[-62377483,-136520895],[-62333291,-136494415],[-62291908,-136463728],[-62096819,-136319023],[-62058644,-136284432],[-61878676,-136121328],[-61680968,-135903184],[-61650275,-135861807],[-61505591,-135666719],[-61354239,-135414191],[-61332211,-135367615],[-61228359,-135148063],[-61129179,-134870847],[-61057639,-134585262],[-61014451,-134294047],[-61000000,-134000000],[-61000000,-107999999],[-61014451,-107705944],[-61057639,-107414736],[-61129179,-107129152],[-61228359,-106851953],[-61354239,-106585808],[-61505591,-106333288],[-61680967,-106096816],[-61878675,-105878680],[-62096820,-105680967],[-62138204,-105650279],[-62333292,-105505591],[-62585808,-105354239],[-62632384,-105332207],[-62851951,-105228360],[-62900463,-105211008],[-63129152,-105129183],[-63414731,-105057640],[-63705947,-105014448],[-63999999,-105000000],[-68999369,-105000000],[-69012543,-104731792],[-69014392,-104706544],[-69017352,-104682119],[-69053079,-104441248],[-69057104,-104417487],[-69066008,-104379383],[-69125528,-104141799],[-69137111,-104105248],[-69220007,-103873544],[-69234136,-103838591],[-69339920,-103614943],[-69356415,-103581784],[-69484328,-103368367],[-69512143,-103323752],[-69661647,-103122160],[-69693352,-103082399],[-69863383,-102894800],[-69878680,-102878679],[-70066999,-102707992],[-70112656,-102668576],[-70314648,-102518775],[-70368367,-102484336],[-70581783,-102356424],[-70806711,-102250040],[-70871040,-102220919],[-71102823,-102137992],[-71139752,-102126095],[-71377383,-102066568],[-71417487,-102057104],[-71441248,-102053079],[-71682119,-102017352],[-71706535,-102014392],[-71731784,-102012543],[-71974456,-102000624],[-71999999,-102000000],[-104000000,-102000000],[-104025536,-102000624],[-104268207,-102012543],[-104293455,-102014392],[-104317880,-102017352],[-104558751,-102053079],[-104582512,-102057104],[-104620616,-102066008],[-104858200,-102125528],[-104894751,-102137111],[-105126455,-102220007],[-105161408,-102234136],[-105385056,-102339920],[-105418215,-102356415],[-105631632,-102484328],[-105676247,-102512143],[-105877839,-102661647],[-105917600,-102693352],[-106105199,-102863383],[-106121320,-102878680],[-106292007,-103066999],[-106331424,-103112656],[-106481224,-103314648],[-106515663,-103368367],[-106643575,-103581783],[-106749959,-103806711],[-106779080,-103871040],[-106862007,-104102823],[-106873904,-104139752],[-106933431,-104377383],[-106942896,-104417487],[-106946920,-104441248],[-106982648,-104682119],[-106985607,-104706535],[-106987456,-104731784],[-107000630,-105000000],[-112000000,-105000000],[-112294056,-105014448],[-112585264,-105057640],[-112870848,-105129184],[-112919359,-105146535],[-113148048,-105228360],[-113194624,-105250392],[-113414191,-105354239],[-113666711,-105505591],[-113708095,-105536279],[-113903183,-105680967],[-114121320,-105878679],[-114319032,-106096816],[-114349720,-106138200],[-114494408,-106333288],[-114645760,-106585808],[-114667792,-106632384],[-114771640,-106851952],[-114788991,-106900463],[-114870815,-107129151],[-114942359,-107414735],[-114985551,-107705943],[-115000000,-107999999],[-115000000,-134000000],[-114985551,-134294048],[-114942359,-134585263],[-114870816,-134870847],[-114853464,-134919359],[-114771639,-135148064],[-114645759,-135414192],[-114494407,-135666720],[-114319031,-135903184],[-114121320,-136121327],[-114083144,-136155919],[-113903184,-136319023],[-113861799,-136349712],[-113666711,-136494416],[-113458383,-136619264],[-113414192,-136645743],[-113148049,-136771631],[-112870848,-136870815],[-112820872,-136883327],[-112585264,-136942351],[-112534303,-136949920],[-112294056,-136985551],[-112000000,-137000000],[-107000630,-137000000],[-106987456,-137268207],[-106985608,-137293440],[-106982647,-137317872],[-106946920,-137558751],[-106942896,-137582511],[-106933991,-137620624],[-106874471,-137858208],[-106862888,-137894751],[-106779992,-138126463],[-106765863,-138161424],[-106660080,-138385055],[-106643584,-138418223],[-106515671,-138631648],[-106487855,-138676256],[-106338352,-138877839],[-106306647,-138917600],[-106136616,-139105199],[-106121320,-139121328],[-105933000,-139291999],[-105887344,-139331407],[-105685351,-139481232],[-105631632,-139515663],[-105418216,-139643567],[-105193288,-139749951],[-105128959,-139779072],[-104897175,-139862016],[-104860247,-139873904],[-104622616,-139933423],[-104582511,-139942896],[-104558751,-139946912],[-104317880,-139982656],[-104293463,-139985616],[-104268216,-139987456],[-104025544,-139999376],[-104000000,-140000000],[-71999999,-140000000]],[[-105000000,-138000000],[-105000000,-104000000],[-71000000,-104000000],[-71000000,-138000000]],[[-69000000,-132000000],[-69000000,-110000000],[-64991180,-110000000],[-64991180,-132000000]],[[-111008824,-132000000],[-111008824,-110000000],[-107000000,-110000000],[-107000000,-132000000]]); my $object = $print->print->objects->[0]; $object->slice; my $layer = $object->get_layer(1); my $layerm = $layer->regions->[0]; $layerm->slices->clear; $layerm->slices->append(Slic3r::Surface->new(surface_type => S_TYPE_INTERNAL, expolygon => $expolygon)); # make perimeters $layer->make_perimeters; # compute the covered area my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER); my $iflow = $layerm->flow(FLOW_ROLE_INFILL); my $covered_by_perimeters = union_ex([ (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}), ]); my $covered_by_infill = union_ex([ (map $_->p, @{$layerm->fill_surfaces}), (map @{$_->polyline->grow($iflow->scaled_width/2)}, @{$layerm->thin_fills}), ]); # compute the non covered area my $non_covered = diff( [ map @{$_->expolygon}, @{$layerm->slices} ], [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], ); if (0) { printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); require "Slic3r/SVG.pm"; Slic3r::SVG::output( "gaps.svg", expolygons => [ map $_->expolygon, @{$layerm->slices} ], red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), green_expolygons => union_ex($non_covered), ); } ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('layer_height', 0.4); $config->set('bridge_speed', 99); $config->set('fill_density', 0); # to prevent bridging over sparse infill $config->set('overhangs', 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) = @_; my %z_with_bridges = (); # z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { $z_with_bridges{$self->Z} = 1 if ($args->{F} // $self->F) == $config->bridge_speed*60; } }); return scalar keys %z_with_bridges; }; ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, 'no overhangs printed with bridge speed'; # except for the first internal solid layers above void ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 1, 'overhangs printed with bridge speed'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('seam_position', 'random'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; } { my $test = sub { my ($model_name) = @_; my $config = Slic3r::Config->new_from_defaults; $config->set('seam_position', 'aligned'); $config->set('skirts', 0); $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]); my $was_extruding = 0; my @seam_points = (); my $print = Slic3r::Test::init_print($model_name, config => $config); Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding}) { if (!$was_extruding) { push @seam_points, Slic3r::Point->new_scale($self->X, $self->Y); } $was_extruding = 1; } else { $was_extruding = 0; } }); my @dist = map unscale($_), map $seam_points[$_]->distance_to($seam_points[$_+1]), 0..($#seam_points-1); ok !(defined first { $_ > 3 } @dist), 'seam is aligned'; }; $test->('20mm_cube'); $test->('small_dorito'); } { my $flow = Slic3r::Flow->new( width => 1, height => 1, nozzle_diameter => 1, ); my $config = Slic3r::Config->new; my $test = sub { my ($expolygons, %expected) = @_; my $slices = Slic3r::Surface::Collection->new; $slices->append(Slic3r::Surface->new( surface_type => S_TYPE_INTERNAL, expolygon => $_, )) for @$expolygons; my $g = Slic3r::Layer::PerimeterGenerator->new( # input: layer_height => 1, slices => $slices, flow => $flow, ); $g->config->apply_dynamic($config); $g->process; is scalar(@{$g->loops}), scalar(@$expolygons), 'expected number of collections'; ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}), 'everything is returned as collections'; is scalar(map @$_, @{$g->loops}), $expected{total}, 'expected number of loops'; is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, map @$_, @{$g->loops}), $expected{external}, 'expected number of external loops'; is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, map @$_, @{$g->loops}), $expected{cinternal}, 'expected number of internal contour loops'; is scalar(grep $_->polygon->is_counter_clockwise, map @$_, @{$g->loops}), $expected{ccw}, 'expected number of ccw loops'; return $g; }; $config->set('perimeters', 3); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), ), ], total => 3, external => 1, cinternal => 1, ccw => 3, ); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), ), ], total => 6, external => 2, cinternal => 1, ccw => 3, ); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), ), # nested: Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), ), ], total => 4*3, external => 4, cinternal => 2, ccw => 2*3, ); } __END__ Slic3r-1.2.9/t/polyclip.t000066400000000000000000000145031254023100400151370ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 18; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Geometry::Clipper qw(intersection_pl); #========================================================== is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([5, 10], [20, 10])), 1, 'point in horizontal segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(30, 10), Slic3r::Line->new([5, 10], [20, 10])), 0, 'point not in horizontal segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([10, 5], [10, 20])), 1, 'point in vertical segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 30), Slic3r::Line->new([10, 5], [10, 20])), 0, 'point not in vertical segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(15, 15), Slic3r::Line->new([10, 10], [20, 20])), 1, 'point in diagonal segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(20, 15), Slic3r::Line->new([10, 10], [20, 20])), 0, 'point not in diagonal segment'; #========================================================== my $square = Slic3r::Polygon->new( # ccw [100, 100], [200, 100], [200, 200], [100, 200], ); #========================================================== { my $hole_in_square = [ # cw [140, 140], [140, 160], [160, 160], [160, 140], ]; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); #is $expolygon->contains_point(Slic3r::Point->new(100, 100)), 1, 'corner point is recognized'; #is $expolygon->contains_point(Slic3r::Point->new(100, 180)), 1, 'point on contour is recognized'; #is $expolygon->contains_point(Slic3r::Point->new(140, 150)), 1, 'point on hole contour is recognized'; #is $expolygon->contains_point(Slic3r::Point->new(140, 140)), 1, 'point on hole corner is recognized'; { my $intersection = intersection_pl([Slic3r::Polyline->new([150,180], [150,150])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([150, 180], [150, 160])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([150,150], [150,120])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([150, 140], [150, 120])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([120,180], [180,180])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([120,180], [180,180])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([50, 150], [300, 150])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([100, 150], [140, 150])->length, 'line is clipped to square with hole'; is $intersection->[1]->length, Slic3r::Line->new([160, 150], [200, 150])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([300, 150], [50, 150])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([200, 150], [160, 150])->length, 'reverse line is clipped to square with hole'; is $intersection->[1]->length, Slic3r::Line->new([140, 150], [100, 150])->length, 'reverse line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([100,180], [200,180])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([100,180], [200,180])->length, 'tangent line is clipped to square with hole'; } } #========================================================== { my $large_circle = Slic3r::Polygon->new_scale( # ccw [151.8639,288.1192], [133.2778,284.6011], [115.0091,279.6997], [98.2859,270.8606], [82.2734,260.7933], [68.8974,247.4181], [56.5622,233.0777], [47.7228,216.3558], [40.1617,199.0172], [36.6431,180.4328], [34.932,165.2312], [37.5567,165.1101], [41.0547,142.9903], [36.9056,141.4295], [40.199,124.1277], [47.7776,106.7972], [56.6335,90.084], [68.9831,75.7557], [82.3712,62.3948], [98.395,52.3429], [115.1281,43.5199], [133.4004,38.6374], [151.9884,35.1378], [170.8905,35.8571], [189.6847,37.991], [207.5349,44.2488], [224.8662,51.8273], [240.0786,63.067], [254.407,75.4169], [265.6311,90.6406], [275.6832,106.6636], [281.9225,124.52], [286.8064,142.795], [287.5061,161.696], [286.7874,180.5972], [281.8856,198.8664], [275.6283,216.7169], [265.5604,232.7294], [254.3211,247.942], [239.9802,260.2776], [224.757,271.5022], [207.4179,279.0635], [189.5605,285.3035], [170.7649,287.4188], ); ok $large_circle->is_counter_clockwise, "contour is counter-clockwise"; my $small_circle = Slic3r::Polygon->new_scale( # cw [158.227,215.9007], [164.5136,215.9007], [175.15,214.5007], [184.5576,210.6044], [190.2268,207.8743], [199.1462,201.0306], [209.0146,188.346], [213.5135,177.4829], [214.6979,168.4866], [216.1025,162.3325], [214.6463,151.2703], [213.2471,145.1399], [209.0146,134.9203], [199.1462,122.2357], [189.8944,115.1366], [181.2504,111.5567], [175.5684,108.8205], [164.5136,107.3655], [158.2269,107.3655], [147.5907,108.7656], [138.183,112.6616], [132.5135,115.3919], [123.5943,122.2357], [113.7259,134.92], [109.2269,145.7834], [108.0426,154.7799], [106.638,160.9339], [108.0941,171.9957], [109.4933,178.1264], [113.7259,188.3463], [123.5943,201.0306], [132.8461,208.1296], [141.4901,211.7094], [147.172,214.4458], ); ok $small_circle->is_clockwise, "hole is clockwise"; my $expolygon = Slic3r::ExPolygon->new($large_circle, $small_circle); my $line = Slic3r::Polyline->new_scale([152.742,288.086671142818], [152.742,34.166466971035]); my $intersection = intersection_pl([$line], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([152742000, 288086661], [152742000, 215178843])->length, 'line is clipped to square with hole'; is $intersection->[1]->length, Slic3r::Line->new([152742000, 108087507], [152742000, 35166477])->length, 'line is clipped to square with hole'; } #========================================================== Slic3r-1.2.9/t/pressure.t000066400000000000000000000015771254023100400151630ustar00rootroot00000000000000use Test::More tests => 1; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(); use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('pressure_advance', 10); $config->set('retract_length', [1]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); my $retracted = $config->retract_length->[0]; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && !$info->{dist_XY}) { $retracted += $info->{dist_E}; } elsif ($info->{retracting}) { $retracted += $info->{dist_E}; } }); ok abs($retracted) < 0.01, 'all retractions are compensated'; } __END__ Slic3r-1.2.9/t/print.t000066400000000000000000000044711254023100400144430ustar00rootroot00000000000000use Test::More tests => 6; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(epsilon unscale X Y); use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; my $print_center = [100,100]; my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center); my @extrusion_points = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); } }); my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@extrusion_points); my $center = $bb->center; ok abs(unscale($center->[X]) - $print_center->[X]) < epsilon, 'print is centered around print_center (X)'; ok abs(unscale($center->[Y]) - $print_center->[Y]) < epsilon, 'print is centered around print_center (Y)'; } { # this represents the aggregate config from presets my $config = Slic3r::Config->new_from_defaults; # user adds one object to the plater my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); # user sets a per-region option $print->print->objects->[0]->model_object->config->set('fill_density', 100); $print->print->reload_object(0); is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; # user exports G-code, thus the default config is reapplied $print->print->apply_config($config); is $print->print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; # user assigns object extruders $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->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__ Slic3r-1.2.9/t/retraction.t000066400000000000000000000173141254023100400154610ustar00rootroot00000000000000use Test::More tests => 18; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Test qw(_eq); { my $config = Slic3r::Config->new_from_defaults; my $duplicate = 1; my $test = sub { my ($conf) = @_; $conf ||= $config; 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; 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]; 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'; } } 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' && exit 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)'); $duplicate = 1; $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('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection $config->set('retract_length', [0]); $config->set('retract_layer_change', [0]); $config->set('retract_lift', [0.2]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $retracted = 0; my $layer_changes_with_retraction = 0; my $retractions = my $z_restores = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{retracting}) { $retracted = 1; $retractions++; } elsif ($info->{extruding} && $retracted) { $retracted = 0; } if ($info->{dist_Z} && $retracted) { $layer_changes_with_retraction++; } if ($info->{dist_Z} && $args->{Z} < $self->Z) { $z_restores++; } }); is $layer_changes_with_retraction, 0, 'no retraction on layer change'; is $retractions, 0, 'no retractions'; is $z_restores, 0, 'no lift'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('use_firmware_retraction', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $retracted = 0; my $double_retractions = my $double_unretractions = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G10') { $double_retractions++ if $retracted; $retracted = 1; } elsif ($cmd eq 'G11') { $double_unretractions++ if !$retracted; $retracted = 0; } }); is $double_retractions, 0, 'no double retractions'; is $double_unretractions, 0, 'no double unretractions'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('use_firmware_retraction', 1); $config->set('retract_length', [0]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $retracted = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G10') { $retracted = 1; } }); ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled'; } __END__ Slic3r-1.2.9/t/shells.t000066400000000000000000000325611254023100400146020ustar00rootroot00000000000000use Test::More tests => 21; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('bridge_speed', 72); $config->set('first_layer_speed', '100%'); $config->set('cooling', 0); my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %z = (); # Z => 1 my %layers_with_solid_infill = (); # Z => $count my %layers_with_bridge_infill = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($self->Z > 0) { $z{ $self->Z } = 1; if ($info->{extruding} && $info->{dist_XY} > 0) { my $F = $args->{F} // $self->F; $layers_with_solid_infill{$self->Z} = 1 if $F == $config->solid_infill_speed*60; $layers_with_bridge_infill{$self->Z} = 1 if $F == $config->bridge_speed*60; } } }); my @z = sort { $a <=> $b } keys %z; my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z; fail "insufficient number of bottom solid layers" unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]); fail "excessive number of bottom solid layers" unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers; fail "insufficient number of top solid layers" unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]); fail "excessive number of top solid layers" unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers; if ($config->top_solid_layers > 0) { fail "unexpected solid infill speed in first solid layer over sparse infill" if $layers_with_solid_infill{ $z[-$config->top_solid_layers] }; die "bridge speed not used in first solid layer over sparse infill" if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] }; } 1; }; $config->set('top_solid_layers', 3); $config->set('bottom_solid_layers', 3); ok $test->(), "proper number of shells is applied"; $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); ok $test->(), "no shells are applied when both top and bottom are set to zero"; $config->set('perimeters', 1); $config->set('top_solid_layers', 3); $config->set('bottom_solid_layers', 3); $config->set('fill_density', 0); ok $test->(), "proper number of shells is applied even when fill density is none"; } # issue #1161 { my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.3); $config->set('first_layer_height', '100%'); $config->set('bottom_solid_layers', 0); $config->set('top_solid_layers', 3); $config->set('cooling', 0); $config->set('bridge_speed', 99); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('first_layer_speed', '100%'); my $print = Slic3r::Test::init_print('V', config => $config); my %layers_with_solid_infill = (); # Z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $layers_with_solid_infill{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3, "correct number of top solid shells is generated in V-shaped object"; } { my $config = Slic3r::Config->new_from_defaults; # we need to check against one perimeter because this test is calibrated # (shape, extrusion_width) so that perimeters cover the bottom surfaces of # their lower layer - the test checks that shells are not generated on the # above layers (thus 'across' the shadow perimeter) # the test is actually calibrated to leave a narrow bottom region for each # layer - we test that in case of fill_density = 0 such narrow shells are # discarded instead of grown $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('cooling', 0); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('extrusion_width', 0.55); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 0); $config->set('solid_infill_speed', 99); my $print = Slic3r::Test::init_print('V', config => $config); my %layers = (); # Z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $layers{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(keys %layers), $config->bottom_solid_layers, "shells are not propagated across perimeters of the neighbor layer"; } { my $config = Slic3r::Config->new_from_defaults; $config->set('perimeters', 3); $config->set('cooling', 0); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 3); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('bridge_speed', 99); my $print = Slic3r::Test::init_print('sloping_hole', config => $config); my %solid_layers = (); # Z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $solid_layers{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(keys %solid_layers), $config->bottom_solid_layers + $config->top_solid_layers, "no superfluous shells are generated"; } { 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', ''); $config->set('temperature', [200]); $config->set('first_layer_temperature', [205]); # TODO: this needs to be tested with a model with sloping edges, where starting # points of each layer are not aligned - in that case we would test that no # travel moves are left to move to the new starting point - in a cube, end # points coincide with next layer starting points (provided there's no clipping) my $test = sub { my ($model_name, $description) = @_; my $print = Slic3r::Test::init_print($model_name, config => $config); my $travel_moves_after_first_extrusion = 0; my $started_extruding = 0; my $first_layer_temperature_set = 0; my $temperature_set = 0; my @z_steps = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { $started_extruding = 1 if $info->{extruding}; push @z_steps, $info->{dist_Z} if $started_extruding && $info->{dist_Z} > 0; $travel_moves_after_first_extrusion++ if $info->{travel} && $started_extruding && !exists $args->{Z}; } elsif ($cmd eq 'M104') { $first_layer_temperature_set = 1 if $args->{S} == 205; $temperature_set = 1 if $args->{S} == 200; } }); ok $first_layer_temperature_set, 'first layer temperature is preserved'; ok $temperature_set, 'temperature is preserved'; # we allow one travel move after first extrusion: i.e. when moving to the first # spiral point after moving to second layer (bottom layer had loop clipping, so # we're slightly distant from the starting point of the loop) ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)"; ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)"; }; $test->('20mm_cube', 'solid model'); $config->set('z_offset', -10); $test->('20mm_cube', 'solid model with negative z-offset'); ### Disabled because the current unreliable medial axis code doesn't ### always produce valid loops. ###$test->('40x10', 'hollow model with negative z-offset'); } { 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; my @this_layer = (); # [ dist_Z, dist_XY ], ... my $bottom_layer_not_flat = 0; my $null_z_moves_not_layer_changes = 0; my $null_z_moves_not_multiples_of_layer_height = 0; 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) = @_; if ($cmd eq 'G1') { if ($z_moves < 2) { # skip everything up to the second Z move # (i.e. start of second layer) if (exists $args->{Z}) { $z_moves++; $bottom_layer_not_flat = 1 if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height; } } elsif ($info->{dist_Z} == 0 && $args->{Z}) { $null_z_moves_not_layer_changes = 1 if $info->{dist_XY} != 0; # % doesn't work easily with floats $null_z_moves_not_multiples_of_layer_height = 1 if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon; 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; 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]) > 0.1; } @this_layer = (); } elsif ($info->{extruding} && $info->{dist_XY} > 0) { $horizontal_extrusions = 1 if $info->{dist_Z} == 0; push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ]; } } }); ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase'; ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes'; ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height'; ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height'; ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope'; 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__ Slic3r-1.2.9/t/skirt_brim.t000066400000000000000000000125041254023100400154500ustar00rootroot00000000000000use Test::More tests => 6; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(unscale convex_hull); use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 1); $config->set('skirt_height', 2); $config->set('perimeters', 0); $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 my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); my %layers_with_skirt = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if (defined $self->Z) { $layers_with_skirt{$self->Z} //= 0; $layers_with_skirt{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->support_material_speed*60; } }); fail "wrong number of layers with skirt" unless (grep $_, values %layers_with_skirt) == $config->skirt_height; }; ok $test->(), "skirt_height is honored when printing multiple objects too"; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 0); $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 my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %layers_with_brim = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if (defined $self->Z) { $layers_with_brim{$self->Z} //= 0; $layers_with_brim{$self->Z} = 1 if $info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) != $config->infill_speed*60; } }); 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'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 1); $config->set('skirt_height', 0); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'successful G-code generation when skirt_height = 0 and skirts > 0'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('skirts', 1); $config->set('skirt_distance', 0); $config->set('support_material_speed', 99); $config->set('perimeter_extruder', 1); $config->set('support_material_extruder', 2); $config->set('cooling', 0); # to prevent speeds to be altered $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered my $print = Slic3r::Test::init_print('overhang', config => $config); $print->process; # we enable support material after skirt has been generated $config->set('support_material', 1); $print->apply_config($config); my $skirt_length = 0; my @extrusion_points = (); 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 (defined $self->Z && $self->Z == $config->first_layer_height) { # we're on first layer if ($info->{extruding} && $info->{dist_XY} > 0) { my $speed = ($args->{F} // $self->F) / 60; if ($speed == $config->support_material_speed && $tool == $config->perimeter_extruder-1) { # skirt uses support material speed but first object's extruder $skirt_length += $info->{dist_XY}; } else { push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); } } } }); my $convex_hull = convex_hull(\@extrusion_points); my $hull_perimeter = unscale($convex_hull->split_at_first_point->length); ok $skirt_length > $hull_perimeter, 'skirt lenght is large enough to contain object with support'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('min_skirt_length', 20); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'no crash when using min_skirt_length'; } __END__ Slic3r-1.2.9/t/slice.t000066400000000000000000000112171254023100400144020ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'temporarily disabled'; plan tests => 16; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } # temporarily disable compilation errors due to constant not being exported anymore sub Slic3r::TriangleMesh::I_B {} sub Slic3r::TriangleMesh::I_FACET_EDGE {} sub Slic3r::TriangleMesh::FE_BOTTOM { sub Slic3r::TriangleMesh::FE_TOP {}} use Slic3r; use Slic3r::Geometry qw(X Y Z A B); my @lines; my $z = 20; my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices # NOTE: # the first point of the intersection lines is replaced by -1 because TriangleMesh.pm # is saving memory and doesn't store point A anymore since it's not actually needed. # We disable this test because intersect_facet() now assumes we never feed a horizontal # facet to it. # is_deeply lines(20, 20, 20), [ # [ -1, $points[1] ], # $points[0] # [ -1, $points[2] ], # $points[1] # [ -1, $points[0] ], # $points[2] # ], 'horizontal'; is_deeply lines(22, 20, 20), [ [ -1, $points[2] ] ], 'lower edge on layer'; # $points[1] is_deeply lines(20, 20, 22), [ [ -1, $points[1] ] ], 'lower edge on layer'; # $points[0] is_deeply lines(20, 22, 20), [ [ -1, $points[0] ] ], 'lower edge on layer'; # $points[2] is_deeply lines(20, 20, 10), [ [ -1, $points[0] ] ], 'upper edge on layer'; # $points[1] is_deeply lines(10, 20, 20), [ [ -1, $points[1] ] ], 'upper edge on layer'; # $points[2] is_deeply lines(20, 10, 20), [ [ -1, $points[2] ] ], 'upper edge on layer'; # $points[0] is_deeply lines(20, 15, 10), [ ], 'upper vertex on layer'; is_deeply lines(28, 20, 30), [ ], 'lower vertex on layer'; { my @z = (24, 10, 16); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), ] ], 'two edges intersect'; } { my @z = (16, 24, 10); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), ] ], 'two edges intersect'; } { my @z = (10, 16, 24); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), ] ], 'two edges intersect'; } { my @z = (24, 10, 20); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), $points[2], ] ], 'one vertex on plane and one edge intersects'; } { my @z = (10, 20, 24); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), $points[1], ] ], 'one vertex on plane and one edge intersects'; } { my @z = (20, 24, 10); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), $points[0], ] ], 'one vertex on plane and one edge intersects'; } my @lower = intersect(22, 20, 20); my @upper = intersect(20, 20, 10); is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer'; is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer'; my $mesh; sub intersect { $mesh = Slic3r::TriangleMesh->new( facets => [], vertices => [], ); push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; $mesh->analyze; return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); } sub vertices { push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2; [ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ] } sub lines { my @lines = intersect(@_); #$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines; #$_->a->[Y] = sprintf('%.0f', $_->a->[Y]) for @lines; $_->[Slic3r::TriangleMesh::I_B][X] = sprintf('%.0f', $_->[Slic3r::TriangleMesh::I_B][X]) for @lines; $_->[Slic3r::TriangleMesh::I_B][Y] = sprintf('%.0f', $_->[Slic3r::TriangleMesh::I_B][Y]) for @lines; return [ map [ -1, $_->[Slic3r::TriangleMesh::I_B] ], @lines ]; } sub line_plane_intersection { my ($line) = @_; @$line = map $mesh->vertices->[$_], @$line; return [ map sprintf('%.0f', $_), map +($line->[B][$_] + ($line->[A][$_] - $line->[B][$_]) * ($z - $line->[B][Z]) / ($line->[A][Z] - $line->[B][Z])), (X,Y) ]; } __END__ Slic3r-1.2.9/t/support.t000066400000000000000000000242241254023100400150210ustar00rootroot00000000000000use Test::More tests => 27; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(epsilon scale); use Slic3r::Geometry::Clipper qw(diff); use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; $config->set('support_material', 1); my @contact_z = my @top_z = (); my $test = sub { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $flow = $print->print->objects->[0]->support_material_flow; my $support = Slic3r::Print::SupportMaterial->new( object_config => $print->print->objects->[0]->config, print_config => $print->print->config, flow => $flow, interface_flow => $flow, first_layer_flow => $flow, ); my $support_z = $support->support_layers_z(\@contact_z, \@top_z, $config->layer_height); my $expected_top_spacing = $support->contact_distance($config->layer_height, $config->nozzle_diameter->[0]); is $support_z->[0], $config->first_layer_height, 'first layer height is honored'; is scalar(grep { $support_z->[$_]-$support_z->[$_-1] <= 0 } 1..$#$support_z), 0, 'no null or negative support layers'; is scalar(grep { $support_z->[$_]-$support_z->[$_-1] > $config->nozzle_diameter->[0] + epsilon } 1..$#$support_z), 0, 'no layers thicker than nozzle diameter'; my $wrong_top_spacing = 0; foreach my $top_z (@top_z) { # find layer index of this top surface my $layer_id = first { abs($support_z->[$_] - $top_z) < epsilon } 0..$#$support_z; # check that first support layer above this top surface (or the next one) is spaced with nozzle diameter $wrong_top_spacing = 1 if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $expected_top_spacing && ($support_z->[$layer_id+2] - $support_z->[$layer_id]) != $expected_top_spacing; } ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly'; }; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.3); @contact_z = (1.9); @top_z = (1.1); $test->(); $config->set('first_layer_height', 0.4); $test->(); $config->set('layer_height', $config->nozzle_diameter->[0]); $test->(); } { my $config = Slic3r::Config->new_from_defaults; $config->set('raft_layers', 3); $config->set('brim_width', 0); $config->set('skirts', 0); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); my $print = Slic3r::Test::init_print('overhang', config => $config); ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; my $tool = 0; Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($info->{extruding}) { if ($self->Z <= ($config->raft_layers * $config->layer_height)) { fail 'not extruding raft with support material extruder' if $tool != ($config->support_material_extruder-1); } else { fail 'support material exceeds raft layers' if $tool == $config->support_material_extruder-1; # TODO: we should test that full support is generated when we use raft too } } }); } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('raft_layers', 3); $config->set('support_material_pattern', 'honeycomb'); $config->set('support_material_extrusion_width', 0.6); $config->set('first_layer_extrusion_width', '100%'); $config->set('bridge_speed', 99); $config->set('cooling', 0); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('start_gcode', ''); # prevent any unexpected Z move my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $layer_id = -1; # so that first Z move sets this to 0 my @raft = my @first_object_layer = (); my %first_object_layer_speeds = (); # F => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { if ($layer_id <= $config->raft_layers) { # this is a raft layer or the first object layer my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]); my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))}; if ($layer_id < $config->raft_layers) { # this is a raft layer push @raft, @path; } else { push @first_object_layer, @path; $first_object_layer_speeds{ $args->{F} // $self->F } = 1; } } } elsif ($cmd eq 'G1' && $info->{dist_Z} > 0) { $layer_id++; } }); ok !@{diff(\@first_object_layer, \@raft)}, 'first object layer is completely supported by raft'; is scalar(keys %first_object_layer_speeds), 1, 'only one speed used in first object layer'; ok +(keys %first_object_layer_speeds)[0] == $config->bridge_speed*60, 'bridge speed used in first object layer'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $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 $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); } { my $config = Slic3r::Config->new_from_defaults; $config->set('brim_width', 0); $config->set('skirts', 0); $config->set('support_material', 1); $config->set('top_solid_layers', 0); # so that we don't have the internal bridge over infill $config->set('bridge_speed', 99); $config->set('cooling', 0); $config->set('first_layer_speed', '100%'); my $test = sub { my $print = Slic3r::Test::init_print('overhang', config => $config); my $has_bridge_speed = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding}) { if (($args->{F} // $self->F) == $config->bridge_speed*60) { $has_bridge_speed = 1; } } }); return $has_bridge_speed; }; $config->set('support_material_contact_distance', 0.2); ok $test->(), 'bridge speed is used when support_material_contact_distance > 0'; $config->set('support_material_contact_distance', 0); ok !$test->(), 'bridge speed is not used when support_material_contact_distance == 0'; $config->set('raft_layers', 5); $config->set('support_material_contact_distance', 0.2); ok $test->(), 'bridge speed is used when raft_layers > 0 and support_material_contact_distance > 0'; $config->set('support_material_contact_distance', 0); ok !$test->(), 'bridge speed is not used when raft_layers > 0 and support_material_contact_distance == 0'; } { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('start_gcode', ''); $config->set('raft_layers', 8); $config->set('nozzle_diameter', [0.4, 1]); $config->set('layer_height', 0.1); $config->set('first_layer_height', 0.8); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); $config->set('support_material_contact_distance', 0); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok my $gcode = Slic3r::Test::gcode($print), 'first_layer_height is validated with support material extruder nozzle diameter when using raft layers'; my $tool = undef; my @z = (0); my %layer_heights_by_tool = (); # tool => [ lh, lh... ] Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && exists $args->{Z} && $args->{Z} != $self->Z) { push @z, $args->{Z}; } elsif ($info->{extruding} && $info->{dist_XY} > 0) { $layer_heights_by_tool{$tool} ||= []; push @{ $layer_heights_by_tool{$tool} }, $z[-1] - $z[-2]; } }); ok !defined(first { $_ > $config->nozzle_diameter->[0] + epsilon } @{ $layer_heights_by_tool{$config->perimeter_extruder-1} }), 'no object layer is thicker than nozzle diameter'; ok !defined(first { abs($_ - $config->layer_height) < epsilon } @{ $layer_heights_by_tool{$config->support_material_extruder-1} }), 'no support material layer is as thin as object layers'; } __END__ Slic3r-1.2.9/t/svg.t000066400000000000000000000013211254023100400140750ustar00rootroot00000000000000use Test::More tests => 2; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Test; { my $print = Slic3r::Test::init_print('20mm_cube'); 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'; } { 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__ Slic3r-1.2.9/t/thin.t000066400000000000000000000144311254023100400142460ustar00rootroot00000000000000use Test::More tests => 21; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use List::Util qw(first sum); use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y); use Slic3r::Test; # Disable this until a more robust implementation is provided. It currently # fails on Linux 32bit because some spurious extrudates are generated. if (0) { my $config = Slic3r::Config->new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', '100%'); $config->set('extrusion_width', 0.5); $config->set('first_layer_extrusion_width', '200%'); # check this one too $config->set('skirts', 0); $config->set('thin_walls', 1); my $print = Slic3r::Test::init_print('gt2_teeth', config => $config); my %extrusion_paths = (); # Z => count of continuous extrusions my $extruding = 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}) { if (!$extruding) { $extrusion_paths{$self->Z} //= 0; $extrusion_paths{$self->Z}++; } $extruding = 1; } else { $extruding = 0; } } }); ok !(first { $_ != 3 } values %extrusion_paths), 'no superfluous thin walls are generated for toothed profile'; } { my $square = Slic3r::Polygon->new_scale( # ccw [100, 100], [200, 100], [200, 200], [100, 200], ); my $hole_in_square = Slic3r::Polygon->new_scale( # cw [140, 140], [140, 160], [160, 160], [160, 140], ); 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 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'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [120, 200], [100, 200], )); my $res = $expolygon->medial_axis(scale 1, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [120, 200], [105, 200], # extra point in the short side [100, 200], )); my $res2 = $expolygon->medial_axis(scale 1, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length'; ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis"; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [112, 200], [108, 200], )); my $res = $expolygon->medial_axis(scale 1, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [120, 180], [200, 180], [200, 200], [100, 200], )); my $res = $expolygon->medial_axis(scale 1, scale 0.5); is scalar(@$res), 1, 'medial axis of a L shape is a single polyline'; my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [-203064906,-51459966],[-219312231,-51459966],[-219335477,-51459962],[-219376095,-51459962],[-219412047,-51459966], [-219572094,-51459966],[-219624814,-51459962],[-219642183,-51459962],[-219656665,-51459966],[-220815482,-51459966], [-220815482,-37738966],[-221117540,-37738966],[-221117540,-51762024],[-203064906,-51762024], )); my $polylines = $expolygon->medial_axis(819998, 102499.75); my $perimeter = $expolygon->contour->split_at_first_point->length; ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [50, 100], [300, 102], [50, 104], )); my $res = $expolygon->medial_axis(scale 4, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; } { # GH #2474 my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] )); my $polylines = $expolygon->medial_axis(1871238, 500000); is scalar(@$polylines), 1, 'medial axis is a single polyline'; my $polyline = $polylines->[0]; my $expected_y = $expolygon->bounding_box->center->y; #;; ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,, 'medial axis is horizontal and is centered'; # order polyline from left to right $polyline->reverse if $polyline->first_point->x > $polyline->last_point->x; my $polyline_bb = $polyline->bounding_box; is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min'; is $polyline->last_point->x, $polyline_bb->x_max, 'expected x_max'; is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ], 'medial axis is not self-overlapping'; } __END__ Slic3r-1.2.9/t/threads.t000066400000000000000000000014301254023100400147310ustar00rootroot00000000000000use Test::More; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Test; if (!$Slic3r::have_threads) { plan skip_all => "this perl is not compiled with threads"; } plan tests => 2; { my $print = Slic3r::Test::init_print('20mm_cube'); { my $thread = threads->create(sub { Slic3r::thread_cleanup(); return 1; }); ok $thread->join, "print survives thread spawning"; } } { my $thread = threads->create(sub { { my $print = Slic3r::Test::init_print('20mm_cube'); Slic3r::Test::gcode($print); } Slic3r::thread_cleanup(); return 1; }); ok $thread->join, "process print in a separate thread"; } __END__ Slic3r-1.2.9/t/vibrationlimit.t000066400000000000000000000066721254023100400163500ustar00rootroot00000000000000use Test::More tests => 9; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; my $config = Slic3r::Config->new_from_defaults; # tolerance, in minutes # (our time estimation differs from the internal one because of decimals truncation) my $epsilon = 0.002; my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('2x20x10', config => $conf); my $min_time = 1 / ($conf->vibration_limit * 60); # minimum time between direction changes in minutes my %dir = (X => 0, Y => 0); my %dir_time = (X => 0, Y => 0); my %dir_sleep_time = (X => 0, Y => 0); my $last_cmd_pause = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd !~ /^G[01]$/) { if ($cmd eq 'G4') { $last_cmd_pause = (($args->{P} // 0) / 1000 + ($args->{S} // 0)) / 60; # in minutes $dir_sleep_time{$_} += $last_cmd_pause for qw(X Y); $last_cmd_pause -= $epsilon; # error builds up } return; } # Z moves are slow enough that we can consider any vibration interrupted if ($info->{dist_Z}) { $dir_time{$_} += 99999 for qw(X Y); $last_cmd_pause = 0; return; } elsif ($info->{dist_E} != 0 && $info->{dist_XY} == 0) { my $time = abs($info->{dist_E}) / ($args->{F} // $self->F); # in minutes $dir_time{$_} += $time for qw(X Y); $last_cmd_pause = 0; return; } # compute move time (this assumes that the speed is XY-bound, which happens very likely) my $time = abs($info->{dist_XY}) / ($args->{F} // $self->F); # in minutes my $one_axis_would_trigger_limit_without_pause = 0; foreach my $axis (qw(X Y)) { # get the direction by comparing the new $axis coordinate with the current one # 1 = positive, 0 = no change, -1 = negative my $dir = $info->{"new_$axis"} <=> $self->$axis; # are we changing direction on this axis? if ($dir != 0 && $dir{$axis} != $dir) { # this move changes direction on this axis if ($dir{$axis} != 0) { if (($dir_time{$axis} + $dir_sleep_time{$axis}) < ($min_time - $epsilon)) { fail 'vibration limit exceeded'; } $one_axis_would_trigger_limit_without_pause = 1 if ($dir_time{$axis} - $last_cmd_pause) < $min_time; } $dir{$axis} = $dir; $dir_time{$axis} = 0; $dir_sleep_time{$axis} = 0; } $dir_time{$axis} += $time; } fail 'no unnecessary pauses are added' if $last_cmd_pause > $epsilon && !$one_axis_would_trigger_limit_without_pause; $last_cmd_pause = 0; }); 1; }; $config->set('gcode_comments', 1); $config->set('perimeters', 1); foreach my $frequency (5, 10, 15) { foreach my $gapfillspeed (20, 50, 100) { $config->set('vibration_limit', $frequency); ok $test->(), "vibrations limited to ${frequency}Hz (gap fill speed = ${gapfillspeed} mm/s)"; } } __END__ Slic3r-1.2.9/utils/000077500000000000000000000000001254023100400140115ustar00rootroot00000000000000Slic3r-1.2.9/utils/amf-to-stl.pl000077500000000000000000000017761254023100400163470ustar00rootroot00000000000000#!/usr/bin/perl # This script converts an AMF file to STL use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'ascii' => \$opt{ascii}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $model = Slic3r::Format::AMF->read_file($ARGV[0]); my $output_file = $ARGV[0]; $output_file =~ s/\.amf(?:\.xml)?$/\.stl/i; printf "Writing to %s\n", basename($output_file); Slic3r::Format::STL->write_file($output_file, $model, binary => !$opt{ascii}); } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: amf-to-stl.pl [ OPTIONS ] file.amf --help Output this usage screen and exit --ascii Generate ASCII STL files (default: binary) EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/config-bundle-to-config.pl000077500000000000000000000024441254023100400207540ustar00rootroot00000000000000#!/usr/bin/perl # This script extracts a full active config from a config bundle. # (Often users reporting issues don't attach plain configs, but # bundles...) use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::Test; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'output=s' => \$opt{output}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } ($ARGV[0] && $opt{output}) or usage(1); { my $bundle_ini = Slic3r::Config->read_ini($ARGV[0]) or die "Failed to read $ARGV[0]\n"; my $config_ini = { _ => {} }; foreach my $section (qw(print filament printer)) { my $preset_name = $bundle_ini->{presets}{$section}; $preset_name =~ s/\.ini$//; my $preset = $bundle_ini->{"$section:$preset_name"} or die "Failed to find preset $preset_name in bundle\n"; $config_ini->{_}{$_} = $preset->{$_} for keys %$preset; } Slic3r::Config->write_ini($opt{output}, $config_ini); } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: config-bundle-to-config.pl --output config.ini bundle.ini EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/dump-stl.pl000066400000000000000000000021571254023100400161200ustar00rootroot00000000000000#!/usr/bin/perl # This script dumps a STL file into Perl syntax for writing tests # or dumps a test model into a STL file use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Slic3r; use Slic3r::Test; $|++; $ARGV[0] or usage(1); if (-e $ARGV[0]) { my $model = Slic3r::Format::STL->read_file($ARGV[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}; printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; exit 0; } elsif ((my $model = Slic3r::Test::model($ARGV[0]))) { $ARGV[1] or die "Missing writeable destination as second argument\n"; Slic3r::Format::STL->write_file($ARGV[1], $model); printf "Model $ARGV[0] written to $ARGV[1]\n"; exit 0; } else { die "No such model exists\n"; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: dump-stl.pl file.stl dump-stl.pl modelname file.stl EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/gcode_sectioncut.pl000066400000000000000000000072331254023100400176740ustar00rootroot00000000000000#!/usr/bin/perl # This script generates section cuts from a given G-Code file use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Getopt::Long qw(:config no_auto_abbrev); use IO::All; use List::Util qw(max); use Slic3r; use Slic3r::Geometry qw(X Y A B X1 Y1 X2 Y2); use Slic3r::Geometry::Clipper qw(JT_SQUARE); use Slic3r::Test; use SVG; my %opt = ( layer_height => 0.2, extrusion_width => 0.5, scale => 30, ); { my %options = ( 'help' => sub { usage() }, 'layer-height|h=f' => \$opt{layer_height}, 'extrusion-width|w=f' => \$opt{extrusion_width}, 'scale|s=i' => \$opt{scale}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $input_file = $ARGV[0]; my $output_file = $input_file; $output_file =~ s/\.(?:gcode|gco|ngc|g)$/.svg/; # read paths my %paths = (); # z => [ path, path ... ] Slic3r::GCode::Reader->new->parse(io($input_file)->all, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{extruding}) { $paths{ $self->Z } ||= []; push @{ $paths{ $self->Z } }, Slic3r::Line->new( [ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ], ); } }); # calculate print extents my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, values %paths ]); # calculate section line my $section_y = $bounding_box->center->[Y]; my $section_line = [ [ $bounding_box->x_min, $section_y ], [ $bounding_box->x_max, $section_y ], ]; # initialize output my $max_z = max(keys %paths); my $svg = SVG->new( width => $opt{scale} * $bounding_box->size->[X], height => $opt{scale} * $max_z, ); # put everything into a group my $g = $svg->group(style => { 'stroke-width' => 1, 'stroke' => '#444444', 'fill' => 'grey', }); # draw paths foreach my $z (sort keys %paths) { foreach my $line (@{ $paths{$z} }) { my @intersections = @{intersection_pl( [ $section_line ], [ _grow($line, $opt{extrusion_width}/2) ], )}; $g->rectangle( 'x' => $opt{scale} * ($_->[A][X] - $bounding_box->x_min), 'y' => $opt{scale} * ($max_z - $z), 'width' => $opt{scale} * abs($_->[B][X] - $_->[A][X]), 'height' => $opt{scale} * $opt{layer_height}, 'rx' => $opt{scale} * $opt{layer_height} * 0.35, 'ry' => $opt{scale} * $opt{layer_height} * 0.35, ) for @intersections; } } # write output Slic3r::open(\my $fh, '>', $output_file); print $fh $svg->xmlify; close $fh; printf "Section cut SVG written to %s\n", $output_file; } # replace built-in Line->grow method which relies on int_offset() sub _grow { my ($line, $distance) = @_; my $polygon = [ @$line, CORE::reverse @$line[1..($#$line-1)] ]; return @{Math::Clipper::offset([$polygon], $distance, 100000, JT_SQUARE, 2)}; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: gcode_sectioncut.pl [ OPTIONS ] file.gcode --help Output this usage screen and exit --layer-height, -h Use the specified layer height --extrusion-width, -w Use the specified extrusion width --scale Factor for converting G-code units to SVG units EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/pdf-slices.pl000077500000000000000000000056011254023100400164040ustar00rootroot00000000000000#!/usr/bin/perl # This script exports model slices to a PDF file as solid fills, one per page use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Getopt::Long qw(:config no_auto_abbrev); use PDF::API2; use Slic3r; use Slic3r::Geometry qw(scale unscale X Y); use constant mm => 25.4 / 72; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'output|o=s' => \$opt{output_file}, 'layer-height|h=f' => \$opt{layer_height}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { # prepare config my $config = Slic3r::Config->new; $config->set('layer_height', $opt{layer_height}) if $opt{layer_height}; # read model my $model = Slic3r::Model->read_from_file(my $input_file = $ARGV[0]); # init print object my $sprint = Slic3r::Print::Simple->new( print_center => [0,0], ); $sprint->apply_config($config); $sprint->set_model($model); my $print = $sprint->_print; # compute sizes my $bb = $print->bounding_box; my $size = $bb->size; my $mediabox = [ map unscale($_)/mm, @{$size} ]; # init PDF my $pdf = PDF::API2->new(); my $color = $pdf->colorspace_separation('RDG_GLOSS', 'darkblue'); # slice and build output geometry $_->slice for @{$print->objects}; foreach my $object (@{ $print->objects }) { my $shift = $object->_shifted_copies->[0]; $shift->translate(map $_/2, @$size); 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 } } } # write output file my $output_file = $opt{output_file}; if (!defined $output_file) { $output_file = $input_file; $output_file =~ s/\.(?:stl)$/.pdf/i; } $pdf->saveas($output_file); printf "PDF file written to %s\n", $output_file; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: pdf-slices.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --output, -o Write to the specified file --layer-height, -h Use the specified layer height EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/post-processing/000077500000000000000000000000001254023100400171505ustar00rootroot00000000000000Slic3r-1.2.9/utils/post-processing/decimate.pl000077500000000000000000000015441254023100400212670ustar00rootroot00000000000000#!/usr/bin/perl -i~ use strict; use warnings; my %lastpos = (X => 10000, Y => 10000, Z => 10000, E => 10000, F => 10000); my %pos = (X => 0, Y => 0, Z => 0, E => 0, F => 0); my $mindist = 0.33; my $mindistz = 0.005; my $mindistsq = $mindist * $mindist; sub dist { my $sq = 0; for (qw/X Y Z E/) { $sq += ($pos{$_} - $lastpos{$_}) ** 2; } return $sq; } while (<>) { if (m#\bG[01]\b#) { while (m#([XYZEF])(\d+(\.\d+)?)#gi) { $pos{uc $1} = $2; } if ( ( /X/ && /Y/ && (dist() >= $mindistsq) ) || (abs($pos{Z} - $lastpos{Z}) > $mindistz) || (!/X/ || !/Y/) ) { print; %lastpos = %pos; } elsif (($pos{F} - $lastpos{F}) != 0) { printf "G1 F%s\n", $pos{F}; $lastpos{F} = $pos{F}; } } else { if (m#\bG92\b#) { while (m#([XYZEF])(\d+(\.\d+)?)#gi) { $lastpos{uc $1} = $2; } } print; } } Slic3r-1.2.9/utils/post-processing/filament-weight.pl000077500000000000000000000016231254023100400225760ustar00rootroot00000000000000#!/usr/bin/perl -i # # Post-processing script for adding weight and cost of required # filament to G-code output. use strict; use warnings; # example densities, adjust according to filament specifications use constant PLA_P => 1.25; # g/cm3 use constant ABS_P => 1.05; # g/cm3 # example costs, adjust according to filament prices use constant PLA_PRICE => 0.05; # EUR/g use constant ABS_PRICE => 0.02; # EUR/g use constant CURRENCY => "EUR"; while (<>) { if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) { my $pla_weight = $2 * PLA_P; my $abs_weight = $2 * ABS_P; my $pla_costs = $pla_weight * PLA_PRICE; my $abs_costs = $abs_weight * ABS_PRICE; printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla_weight, $abs_weight; printf "; costs = %s %.2f (PLA), %s %.2f (ABS)\n", CURRENCY, $pla_costs, CURRENCY, $abs_costs; } else { print; } } Slic3r-1.2.9/utils/post-processing/flowrate.pl000077500000000000000000000024761254023100400213440ustar00rootroot00000000000000#!/usr/bin/perl -i # # Post-processing script for calculating flow rate for each move use strict; use warnings; use constant PI => 3.141592653589793238; my @filament_diameter = split /,/, $ENV{SLIC3R_FILAMENT_DIAMETER}; my $E = 0; my $T = 0; my ($X, $Y, $F); while (<>) { if (/^G1.*? F([0-9.]+)/) { $F = $1; } if (/^G1 X([0-9.]+) Y([0-9.]+).*? E([0-9.]+)/) { my ($x, $y, $e) = ($1, $2, $3); my $e_length = $e - $E; if ($e_length > 0 && defined $X && defined $Y) { my $dist = sqrt( (($x-$X)**2) + (($y-$Y)**2) ); if ($dist > 0) { my $mm_per_mm = $e_length / $dist; # dE/dXY my $mm3_per_mm = ($filament_diameter[$T] ** 2) * PI/4 * $mm_per_mm; my $vol_speed = $F/60 * $mm3_per_mm; my $comment = sprintf ' ; dXY = %.3fmm ; dE = %.5fmm ; dE/XY = %.5fmm/mm; volspeed = %.5fmm^3/sec', $dist, $e_length, $mm_per_mm, $vol_speed; s/(\R+)/$comment$1/; } } $E = $e; $X = $x; $Y = $y; } if (/^G1 X([0-9.]+) Y([0-9.]+)/) { $X = $1; $Y = $2; } if (/^G1.*? E([0-9.]+)/) { $E = $1; } if (/^G92 E0/) { $E = 0; } if (/^T(\d+)/) { $T = $1; } print; } __END__ Slic3r-1.2.9/utils/post-processing/prowl-notification.pl000077500000000000000000000011501254023100400233340ustar00rootroot00000000000000#!/usr/bin/perl # # Example post-processing script for sending a Prowl notification upon # completion. See http://www.prowlapp.com/ for more info. use strict; use warnings; use File::Basename qw(basename); use WebService::Prowl; # set your Prowl API key here my $apikey = ''; my $file = basename $ARGV[0]; my $prowl = WebService::Prowl->new(apikey => $apikey); my %options = (application => 'Slic3r', event =>'Slicing Done!', description => "$file was successfully generated"); printf STDERR "Error sending Prowl notification: %s\n", $prowl->error unless $prowl->add(%options); Slic3r-1.2.9/utils/post-processing/z-every-line.pl000077500000000000000000000011151254023100400220340ustar00rootroot00000000000000#!/usr/bin/perl -i use strict; use warnings; my $z = 0; # read stdin and any/all files passed as parameters one line at a time while (<>) { # if we find a Z word, save it $z = $1 if /Z\s*(\d+(\.\d+)?)/; # if we don't have Z, but we do have X and Y if (!/Z/ && /X/ && /Y/ && $z > 0) { # chop off the end of the line (incl. comments), saving chopped section in $1 s/\s*([\r\n\;\(].*)/" Z$z $1"/es; # print start of line, insert our Z value then re-add the chopped end of line # print "$_ Z$z $1"; } #else { # nothing interesting, print line as-is print or die $!; #} } Slic3r-1.2.9/utils/split_stl.pl000077500000000000000000000026701254023100400163730ustar00rootroot00000000000000#!/usr/bin/perl # This script splits a STL plate into individual files use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'ascii' => \$opt{ascii}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $model = Slic3r::Format::STL->read_file($ARGV[0]); my $basename = $ARGV[0]; $basename =~ s/\.stl$//i; my $part_count = 0; my $mesh = $model->objects->[0]->volumes->[0]->mesh; foreach my $new_mesh (@{$mesh->split}) { $new_mesh->repair; my $new_model = Slic3r::Model->new; $new_model ->add_object() ->add_volume(mesh => $new_mesh); $new_model->add_default_instances; my $output_file = sprintf '%s_%02d.stl', $basename, ++$part_count; printf "Writing to %s\n", basename($output_file); Slic3r::Format::STL->write_file($output_file, $new_model, binary => !$opt{ascii}); } } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: split_stl.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --ascii Generate ASCII STL files (default: binary) EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/stl-to-amf.pl000077500000000000000000000034741254023100400163440ustar00rootroot00000000000000#!/usr/bin/perl # This script converts a STL file to AMF use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'distinct-materials' => \$opt{distinct_materials}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my @models = map Slic3r::Format::STL->read_file($_), @ARGV; my $output_file = $ARGV[0]; $output_file =~ s/\.stl$/.amf.xml/i; my $new_model = Slic3r::Model->new; if ($opt{distinct_materials} && @models > 1) { my $new_object = $new_model->add_object; for my $m (0 .. $#models) { my $model = $models[$m]; $new_model->set_material($m, { Name => basename($ARGV[$m]) }); $new_object->add_volume( material_id => $m, facets => $model->objects->[0]->volumes->[0]->facets, vertices => $model->objects->[0]->vertices, ); } } else { foreach my $model (@models) { $new_model->add_object( vertices => $model->objects->[0]->vertices, )->add_volume( facets => $model->objects->[0]->volumes->[0]->facets, ); } } printf "Writing to %s\n", basename($output_file); Slic3r::Format::AMF->write_file($output_file, $new_model); } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ] --help Output this usage screen and exit --distinct-materials Assign each STL file to a different material EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/view-mesh.pl000066400000000000000000000035251254023100400162570ustar00rootroot00000000000000#!/usr/bin/perl # This script displays 3D preview of a mesh use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::GUI; use Slic3r::GUI::3DScene; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'cut=f' => \$opt{cut}, 'enable-moving' => \$opt{enable_moving}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $model = Slic3r::Model->read_from_file($ARGV[0]); # make sure all objects have at least one defined instance $model->add_default_instances; $_->center_around_origin for @{$model->objects}; # and align to Z = 0 my $app = Slic3r::ViewMesh->new; $app->{canvas}->enable_picking(1); $app->{canvas}->enable_moving($opt{enable_moving}); $app->{canvas}->load_object($model, 0); $app->{canvas}->set_auto_bed_shape; $app->{canvas}->zoom_to_volumes; $app->{canvas}->SetCuttingPlane($opt{cut}) if defined $opt{cut}; $app->MainLoop; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: view-mesh.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --cut Z Display the cutting plane at the given Z EOF exit ($exit_code || 0); } package Slic3r::ViewMesh; use Wx qw(:sizer); use base qw(Wx::App); sub OnInit { my $self = shift; my $frame = Wx::Frame->new(undef, -1, 'Mesh Viewer', [-1, -1], [500, 400]); my $panel = Wx::Panel->new($frame, -1); $self->{canvas} = Slic3r::GUI::3DScene->new($panel); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{canvas}, 1, wxEXPAND, 0); $panel->SetSizer($sizer); $sizer->SetSizeHints($panel); $frame->Show(1); } __END__ Slic3r-1.2.9/utils/view-toolpaths.pl000077500000000000000000000046361254023100400173470ustar00rootroot00000000000000#!/usr/bin/perl # This script displays 3D preview of a mesh use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::GUI; use Slic3r::GUI::3DScene; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'load=s' => \$opt{load}, '3D' => \$opt{d3}, 'duplicate=i' => \$opt{duplicate}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { # load model my $model = Slic3r::Model->read_from_file($ARGV[0]); # load config my $config = Slic3r::Config->new_from_defaults; if ($opt{load}) { $config->apply(Slic3r::Config->load($opt{load})); } # init print my $sprint = Slic3r::Print::Simple->new; $sprint->duplicate($opt{duplicate} // 1); $sprint->apply_config($config); $sprint->set_model($model); $sprint->process; # visualize toolpaths $Slic3r::ViewToolpaths::print = $sprint->_print; $Slic3r::ViewToolpaths::d3 = $opt{d3}; my $app = Slic3r::ViewToolpaths->new; $app->MainLoop; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: view-toolpaths.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --load CONFIG Loads the supplied config file EOF exit ($exit_code || 0); } package Slic3r::ViewToolpaths; use Wx qw(:sizer); use base qw(Wx::App Class::Accessor); our $print; our $d3; sub OnInit { my $self = shift; my $frame = Wx::Frame->new(undef, -1, 'Toolpaths', [-1, -1], [500, 500]); my $panel = Wx::Panel->new($frame, -1); my $canvas; if ($d3) { $canvas = Slic3r::GUI::3DScene->new($panel); $canvas->set_bed_shape($print->config->bed_shape); $canvas->load_print_toolpaths($print); foreach my $object (@{$print->objects}) { #$canvas->load_print_object_slices($object); $canvas->load_print_object_toolpaths($object); #$canvas->load_object($object->model_object); } $canvas->zoom_to_volumes; } else { $canvas = Slic3r::GUI::Plater::2DToolpaths->new($panel, $print); } my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($canvas, 1, wxEXPAND, 0); $panel->SetSizer($sizer); $frame->Show(1); } __END__ Slic3r-1.2.9/utils/wireframe.pl000066400000000000000000000140461254023100400163340ustar00rootroot00000000000000#!/usr/bin/perl # This script exports experimental G-code for wireframe printing # (inspired by the brilliant WirePrint concept) use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale X Y PI); my %opt = ( step_height => 5, nozzle_angle => 30, nozzle_width => 10, first_layer_height => 0.3, ); { my %options = ( 'help' => sub { usage() }, 'output|o=s' => \$opt{output_file}, 'step-height|h=f' => \$opt{step_height}, 'nozzle-angle|a=f' => \$opt{nozzle_angle}, 'nozzle-width|w=f' => \$opt{nozzle_width}, 'first-layer-height=f' => \$opt{first_layer_height}, ); GetOptions(%options) or usage(1); $opt{output_file} or usage(1); $ARGV[0] or usage(1); } { # load model my $model = Slic3r::Model->read_from_file($ARGV[0]); $model->add_default_instances; $model->center_instances_around_point(Slic3r::Pointf->new(100,100)); my $mesh = $model->mesh; $mesh->translate(0, 0, -$mesh->bounding_box->z_min); # get slices my @z = (); my $z_max = $mesh->bounding_box->z_max; for (my $z = $opt{first_layer_height}; $z <= $z_max; $z += $opt{step_height}) { push @z, $z; } my @slices = @{$mesh->slice(\@z)}; my $flow = Slic3r::Flow->new( width => 0.35, height => 0.35, nozzle_diameter => 0.35, bridge => 1, ); my $config = Slic3r::Config::Print->new; $config->set('gcode_comments', 1); open my $fh, '>', $opt{output_file}; my $gcodegen = Slic3r::GCode->new( enable_loop_clipping => 0, # better bonding ); $gcodegen->apply_print_config($config); $gcodegen->set_extruders([0]); print $fh $gcodegen->set_extruder(0); print $fh $gcodegen->writer->preamble; my $e = $gcodegen->writer->extruder->e_per_mm3 * $flow->mm3_per_mm; foreach my $layer_id (0..$#z) { my $z = $z[$layer_id]; foreach my $island (@{$slices[$layer_id]}) { foreach my $polygon (@$island) { if ($layer_id > 0) { # find the lower polygon that we want to connect to this one my $lower = $slices[$layer_id-1]->[0]->contour; # 't was easy, wasn't it? my $lower_z = $z[$layer_id-1]; { my @points = (); # keep all points with strong angles { my @pp = @$polygon; foreach my $i (0..$#pp) { push @points, $pp[$i-1] if abs($pp[$i-1]->ccw_angle($pp[$i-2], $pp[$i]) - PI) > PI/3; } } $polygon = Slic3r::Polygon->new(@points); } #$polygon = Slic3r::Polygon->new(@{$polygon->split_at_first_point->equally_spaced_points(scale $opt{nozzle_width})}); # find vertical lines my @vertical = (); foreach my $point (@{$polygon}) { push @vertical, Slic3r::Line->new($point->projection_onto_polygon($lower), $point); } next if !@vertical; my @points = (); foreach my $line (@vertical) { push @points, Slic3r::Pointf3->new( unscale($line->a->x), unscale($line->a->y), #)) $lower_z, ); push @points, Slic3r::Pointf3->new( unscale($line->b->x), unscale($line->b->y), #)) $z, ); } # reappend first point as destination of the last diagonal segment push @points, Slic3r::Pointf3->new( unscale($vertical[0]->a->x), unscale($vertical[0]->a->y), #)) $lower_z, ); # move to the position of the first vertical line print $fh $gcodegen->writer->travel_to_xyz(shift @points); # extrude segments foreach my $point (@points) { print $fh $gcodegen->writer->extrude_to_xyz($point, $e * $gcodegen->writer->get_position->distance_to($point)); } } } print $fh $gcodegen->writer->travel_to_z($z); foreach my $polygon (@$island) { #my $polyline = $polygon->split_at_vertex(Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1])); my $polyline = $polygon->split_at_first_point; print $fh $gcodegen->writer->travel_to_xy(Slic3r::Pointf->new_unscale(@{ $polyline->first_point }), "move to first contour point"); foreach my $line (@{$polyline->lines}) { my $point = Slic3r::Pointf->new_unscale(@{ $line->b }); print $fh $gcodegen->writer->extrude_to_xy($point, $e * unscale($line->length)); } } } } close $fh; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: wireframe.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --output, -o Write to the specified file --step-height, -h Use the specified step height --nozzle-angle, -a Max nozzle angle --nozzle-width, -w External nozzle diameter EOF exit ($exit_code || 0); } __END__ Slic3r-1.2.9/utils/zsh/000077500000000000000000000000001254023100400146155ustar00rootroot00000000000000Slic3r-1.2.9/utils/zsh/README.markdown000066400000000000000000000012541254023100400173200ustar00rootroot00000000000000# ZSH Completions for Slic3r To enable zsh(1) completions for Slic3r, add the following to your ``~/.zshrc`` file, replacing ``/path/to/Slic3r/`` with the actual path to your Slic3r directory: typeset -U fpath if [[ -d /path/to/Slic3r/utils/zsh/functions ]]; then fpath=(/path/to/Slic3r/utils/zsh/functions $fpath) fi autoload -Uz compinit compinit zstyle ':completion:*' verbose true zstyle ':completion:*:descriptions' format '%B%d%b' zstyle ':completion:*:messages' format '%d' zstyle ':completion:*:warnings' format 'No matches for %d' zstyle ':completion:*' group-name '%d' See the zshcompsys(1) man page for further details. Slic3r-1.2.9/utils/zsh/functions/000077500000000000000000000000001254023100400166255ustar00rootroot00000000000000Slic3r-1.2.9/utils/zsh/functions/_slic3r000066400000000000000000000232551254023100400201150ustar00rootroot00000000000000#compdef -P slic3r(|.pl|.exe) # # Slic3r completions configuration for zsh(1). # Currently undocumented options: # --debug, --gui, --ignore-nonexistent-config # --acceleration, --perimeter-acceleration, --infill-acceleration _arguments -S \ '(- *)--help[output usage screen and exit]' \ '(- *)--version[output the version of Slic3r and exit]' \ '--save[save configuration to file]:config output file:_files -g "*.(#i)ini(-.)"' \ '*--load[load configuration from file]:config input file:_files -g "*.(#i)ini(-.)"' \ '(--output -o)'{--output,-o}'[specify output file]:output file:_files -g "*.(#i)(gcode|svg)(-.)"' \ '(--threads -j)'{--threads,-j}'[specify number of threads to use]:number of threads' \ \ '--output-filename-format[specify output filename format]:output filename format' \ '*--post-process[specify post-processing script]:post-processing script file:_files' \ '--export-svg[export SVG containing slices instead of G-code]' \ '(--merge -m)'{--merge,-m}'[merge multiple input files into a single print]' \ \ '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ '--print-center[specify print center coordinates]:print center coordinates in mm,mm' \ '--z-offset[specify Z-axis offset]:Z-axis offset in mm' \ '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 machinekit no-extrusion)' \ '(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \ '--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \ '(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \ '(--gcode-comments --no-gcode-comments)'--{no-,}gcode-comments'[disable/enable verbose G-code comments]' \ \ '*--filament-diameter[specify raw filament diameter]:raw filament diameter in mm' \ '*--extrusion-multiplier[specify multiplier for amount of plastic extruded]:extrusion multiplier' \ '*--temperature[specify extrusion temperature]:extrusion temperature in Celsius' \ '*--first-layer-temperature[specify extrusion temperature for the first layer]:first layer extrusion temperature in Celsius' \ '--bed-temperature[specify heated bed temperature]:heated bed temperature in Celsius' \ '--first-layer-bed-temperature[specify heated bed temperature for the first layer]:first layer heated bed temperature in Celsius' \ \ '--perimeter-extruder[specify extruder to use for printing perimeters]:extruder number' \ '--infill-extruder[specify extruder to use for printing infill]:extruder number' \ '--support-material-extruder[specify extruder to use for printing support material]:extruder number' \ \ '--travel-speed[specify speed of non-print moves]:speed of non-print moves in mm/s' \ '--perimeter-speed[specify speed of print moves for perimeters]:speed of print moves for perimeters in mm/s' \ '--external-perimeter-speed[specify speed of print moves for external perimeters]:speed of print moves for external perimeters in mm/s or % of --perimeter-speed' \ '--small-perimeter-speed[specify speed of print moves for small perimeters]:speed of print moves for small perimeters in mm/s or % of --perimeter-speed' \ '--infill-speed[specify speed of infill print moves]:speed of infill print moves in mm/s' \ '--solid-infill-speed[specify speed of solid surface print moves]:speed of solid surface print moves in mm/s or % of --infill-speed' \ '--top-solid-infill-speed[specify speed of top surface print moves]:speed of top surface print moves in mm/s or % of --solid-infill-speed' \ '--bridge-speed[specify speed of bridge print moves]:speed of bridge print moves in mm/s' \ '--first-layer-speed[specify speed of bottom layer print moves]:speed of bottom layer print moves in mm/s or % of normal speeds' \ \ '--layer-height[specify layer height]:layer height in mm' \ '--first-layer-height[specify layer height for bottom layer]:layer height for bottom layer in mm or % of --layer-height' \ '--infill-every-layers[specify infill for every N layers]:N layers' \ \ '--perimeters[specify number of perimeters]:number of perimeters' \ '--solid-layers[specify number of solid layers to do for top/bottom surfaces]:number of layers for top/bottom surfaces' \ '--fill-density[specify infill density]:infill density in percent' \ '--fill-angle[specify infill angle]:infill angle in degrees' \ '--fill-pattern[specify pattern used for infill]:infill pattern:(rectilinear line concentric honeycomb hilbertcurve archimedeanchords octagramspiral)' \ '--solid-fill-pattern[specify pattern used for solid layers]:solid fill pattern:(rectilinear concentric hilbertcurve archimedeanchords octagramspiral)' \ '--start-gcode[load initial G-code from file]:start G-code file:_files -g "*.(#i)(gcode)(-.)"' \ '--end-gcode[load final G-code from file]:end G-code file:_files -g "*.(#i)(gcode)(-.)"' \ '--layer-gcode[load layer-change G-code from file]:layer-change G-code file:_files -g "*.(#i)(gcode)(-.)"' \ '(--support-material --no-support-material)'--{no-,}support-material'[disable/enable generation of support material for overhangs]' \ '--support-material-threshold[specify support material threshold]:maximum slope angle for generating support material' \ '--support-material-pattern[specify pattern used for support material]:support material pattern:(rectilinear honeycomb)' \ '--support-material-spacing[specify spacing between support material lines]:spacing between support material lines in mm' \ '--support-material-angle[specify support material angle]:support material angle in degrees' \ '(--randomize-start --no-randomize-start)'--{no-,}randomize-start'[disable/enable randomization of starting point across layers]' \ '(--extra-perimeters --no-extra-perimeters)'--{no-,}extra-perimeters'[disable/enable generation of extra perimeters when needed]' \ \ '--retract-length[specify filament retraction length when pausing extrusion]:filament retraction length in mm' \ '--retract-speed[specify filament retraction speed]:filament retraction speed in mm/s' \ '--retract-restart-extra[specify filament length to extrude for compensating retraction]: filament lenght in mm' \ '--retract-before-travel[specify minimum travel length for activating retraction]:minimum travel length for activating retraction in mm' \ '--retract-lift[specify Z-axis lift for use when retracting]:Z-axis lift in mm' \ \ '(--cooling --no-cooling)'--{no-,}cooling'[disable/enable fan and cooling control]' \ '--min-fan-speed[specify minimum fan speed]:minimum fan speed in percent' \ '--max-fan-speed[specify maximum fan speed]:maximum fan speed in percent' \ '--bridge-fan-speed[specify fan speed to use for bridging]:bridging fan speed in percent' \ '--fan-below-layer-time[specify maximum layer print time before activating fan]:maximum layer print time in seconds' \ '--slowdown-below-layer-time[specify maximum layer print time before slowing down printing]:maximum layer print time in seconds' \ '--min-print-speed[specify minimum print speed]:minimum print speed in mm/s' \ '--disable-fan-first-layers[specify number of bottom layers to print before activating fan]:number of bottom layers' \ '(--fan-always-on --no-fan-always-on)'--{no-,}fan-always-on'[disable/enable deactivation of fan]' \ \ '--skirts[specify number of skirts]:number of skirts' \ '--skirt-distance[specify distance between innermost skirt and object]:distance between innermost skirt and object in mm' \ '--skirt-height[specify number of skirt layers]:number of skirt layers' \ '--brim-width[specify brim width]:width of brim in mm' \ \ '--scale[specify object scaling factor]:object scaling factor in percent' \ '--rotate[specify object rotation angle]:object rotation angle in degrees' \ '(--duplicate-grid)--duplicate[specify number of duplicates for auto-arrange]:number of duplicates for auto-arrange' \ '(--duplicate-grid)--bed-size[specify bed size for auto-arrange]:bed size for auto-arrange in mm,mm' \ '(--duplicate --bed-size)--duplicate-grid[specify number of duplicates for grid arrangement]:number of duplicates for grid arrangement as x,y' \ '--duplicate-distance[specify distance between duplicates]:distance between duplicates in mm' \ \ '(--complete-objects --no-complete-objects)'--{no-,}complete-objects'[disable/enable completion of each object before starting a new one]' \ '--extruder-clearance-radius[specify radius above which extruder will not collide with anything]:radius in mm' \ '--extruder-clearance-height[specify maximum vertical extruder depth]:maximum vertical extruder depth in mm' \ \ '--notes[specify notes to be added as comments to the output file]:notes' \ \ '--extrusion-width[specify extrusion width]:extrusion width in mm or % of --layer-height' \ '--first-layer-extrusion-width[specify extrusion width for first layer]:first layer extrusion width in mm or % og --layer-height' \ '--perimeters-extrusion-width[specify extrusion width for perimeters]:perimeter extrusion width in mm or % of --layer-height' \ '--infill-extrusion-width[specify extrusion width for infill]:infill extrusion width in mm or % of --layer-height' \ '--support-material-extrusion-width[specify extrusion width for support material]:support material extrusion width in mm or % of --layer-height' \ '--bridge-flow-ratio[specify multiplier for extrusion when bridging]:bridge extrusion multiplier' \ \ '*:input file:_files -g "*.(#i)(stl|obj|amf|xml)(-.)"' # Local Variables: *** # mode:sh *** # End: *** Slic3r-1.2.9/var/000077500000000000000000000000001254023100400134415ustar00rootroot00000000000000Slic3r-1.2.9/var/Slic3r.icns000066400000000000000000007434421254023100400154740ustar00rootroot00000000000000icnsÇ"ic092 jP ‡ ftypjp2 jp2 Ojp2hihdrcolr"cdef1Ÿjp2cÿOÿQ2ÿR ÿ\@@HHPHHPHHPHHPHHPÿ 1<ÿS ÿ]@@HHPHHPHHPHHPHHPÿS ÿ]@@HHPHHPHHPHHPHHPÿS ÿ]@@HHPHHPHHPHHPHHPÿ“ß}æPäW —9ŽÕ7çŠÀbä‰P1iì•}”eInB9Æ®wE¢¢8¶X´!BfÒcPÕ;td~•E [åí9žÆÿ"o ‘»9IáÕs˜­¤X¥Žª `­Ï˜¼eí9GÛçàZÃé(~ó%Üè¥cÚ²Jµ")ξ73þ…}Tïœ}Yn¿ÞN%,óžÒÔ‹Ö¸Ÿ÷Ùíj¥à Ejvy ýï”LG̦ì_v¥ë(²æ×J=?QØ9ö4EÃi4 (=%œµ"n¬hkM^ýWÞ8pušRi„ÚUmêÙ8.ï]Þ¼¾^”Ëoß}æPäW —9ŽÕ7çŠÀbä‰P1iì•}”eInB9Æ®wE¢¢8¶X´!BfÒcPÕ;td~•E [åí9žÆÿ"o ‘»9IáÕs˜­¤X¥Žª `­Ï˜¼eí9GÛçàZÃé(~ó%Üè¥cÚ²Jµ")ξ73þ…}Tïœ}Yn¿ÞN%,óžÒÔ‹Ö¸Ÿ÷Ùíj¥™F’Ôí'«Ÿ(˜—wó]v¥ë(²æ×J=?SZíLzJŸõ2=æð1ôãæCÕ7¡o ˆ~«ï8@B6æ ÕVÞ éˆO.L{Ym)–ßß}¬PäÈ®‡[P*D÷¬â ·ƒ9o6UÁï;~ã©,é|zÛÖwIHbX¡¬®=¦Úêà/Âr/¤=º‘4Up/sC"†2ÔQ4>–ufVa8ø¹·E6I º´N툻—!Á_êF>×ø=R¬qGžý®IÎÌ ?8,œáo€·²z^öw<Þpv(*J­#dt<2ÇšôoÂ@™½Ñul¶ÓÊ”ÐïÓpªª‡ìÔŽlÖïjðÏ›ÁPÛïÁi;B9JÆ6èÄ¡Ë#Kß}èP‘œß:z¸QÁÂÍM5oëŸOðp+œ­vB6wåÇ«ÃkLÕÜ;À¸¥9•\a©ŸSJ³òzn-`wÇ+A ‰ÆÔÚ%•-×Ç3Ø« .˜0–°)U§×˜D ¤­?å¶S·«Œø2s~f¯Ç·¦•O B®r Lüïãh'ë©V˱Wç=¶àIŠ’uÃÒŠ!!baЦ‡'Ôñ­Àò×8A|LM{‰¦‹u-ßó¡%¯Ð›5Cl ývÃ+7îx1šs—T€I\®÷ø¿ÔL=¹ÐÈ\¶I}`”ïæäÊ?2‰x˜Ͼ­Ÿ}e?*Ô[îÏ-…­`8HÝ¥£rƒúPWÆ5H`µð)E{ñª£ðSϾ®Ÿ}e?*Ü[îÏ-…­`8HÝ¥£rƒúPWÆ5H`µð)EÂ}$øÁœ†¥Ý ªôP+Ð/ЇÅ`[ŸY\ÝØ—Á?GqËœ*MX©ÝÔUõ›¬†xÁÑIA#Ùï^n¯ž2ÉPµ-¬T£Ï)ý¡lNÏMô÷Hwë$[&F7ª„!š?ˆB3É}u’:å«yë:áVIË„"ÆEzï…zÊȧ;T×[¯~ºÀ9„%é|ßêß:˦&ý6«ûwllÖ(©Uï<^îÐKÉŒc0kÄ&ù‹øÚµi¾¨¯ÖÇÀß«C­Ý°;¼üÝLŸùŸWuË(’‘koWW7L8ê–÷‚±ƒƒÞSŸ.6Ô0V¾:r¾¥>)/ ¢ ôVž¦ Ë© Nn‹ÞѦT Xƒý§m›©Å·‚g:˜ÚÛf%¼FiF«èêÿÌòå°ÊàÇ$ þ´œp@ï°‹¬F›Á÷8±šØôLÕÎcgÜk[ë`ˆÆÀ¹—²1ÞËõ!‰y‡ØaÊ6´`ãŸ5xÄQ\ôw±@øéDx™Cv—åž„åRÇÚëc¬}ï ˆeùLŸl‘‘ÊEÙ`šŒú'a‰Õf}y´;VZýtuÛ­´ÿÄ>4Ú\µl!H\¤¤ÔýòÅïχÐbl@Z½…%¤Rœ$gÀ{o×H$D»™,œçÓõ6^5–€*¾~ew# V g[R{ÿ0ñ¿\`ÙÆŽ^à,?~ÙGOårûW ×»16¸®Ù…šÕšÍŠÝÏÉù;èÅØf£ç–X¢õ_@õ¹3,ÌãOÇæ8}¡×†Ši=Ɉ¿Ý©˜¨Ô¶F–@™=mÖo¦z‘´-:j Óèº YñÏ%F+ÕÍÙÝoaâîÐÿYy†Xi³êYõ}ŠªŠé‚ïÊ–’²ÚIð‘k²ÄXlð]×óxÖtØ’-ÑV.To»_™Q T­¬§÷Ë·PÓ³í#JN…Èù¬`Ñ‘èvJ­ #¹«¸ÿ€`ø%tµf„}øª"½=@/”ƒzvÝ<Ͼ˜Ÿ}9}2m<ÊÍ÷+F¾¨¨i&\ŠåqÚK€’©ŠI%+MõJâNO{œó|¤¨bu-ÚÏ^±^FµÊ‡£@× x&ÛHm[d& âz¤EñØ37ƒjÔ*CpvÚÀxŒTõº„p¬ Êyr†•"ìŠ$IúvÊ Žn¶ïÝxÚóRåuéKnˆçŠ_>yÊ{éðÁ1%g¢¶ó;3;#C$å Œqh.áÿ)Rä¥öPÙø–¨ã<WáËOôï­Àew㤵T†—˯‰Gk%OÕ:åÚɯ//u &ûM¹à€ŒËN_a°ˆR©IÛvø®Ì/_Ö 0‰î‰l_Pï‚— 6ÆȤ3iËEd>ŸC]¶C{ˆÁÀ†ôöBÂ#¯1:Ð>äC±u >#ÒveTAì,$A½»2EÊW|L©Aâ2%Å€rã¹7$sZ–ñfå5æÁ;Úäê…Œz_}Ôc¦ø×¬_Í¢¨Ò“×kr/á øåS Tþ^îÌ íôš/íêzí WÊ"`êµ7«¼äŽAE‡ qI!ôU½ïw×\_ôòÆaÄ76ÙÅÆ\t…BIùŒgŸHÏÃq§á¼3óÞ ³^Ú@!Ú™ÇL*LÚh… ~`ÕJ`E%úøðGé?+‹eHú›[DAâíMgÐkå¡pdNCàäQ5—w°ƒd*/VÕ¾i̓¸jÚm fDaZ.ë3oËåz¦uß õÅ4žÞufœÙ‰‹O¯è_4ù%îwØ=²¡äßÚ+OIÓ†W›nò§Ä¾Ü_ªk3Ä({Ñ/óü${Ãqîj- ÔÒAç§ö© Û†4-¤ýdŽÑ ˜¬hã‘–!#±4_/’FݲSNU‚.¤D×܉'6wgxÎ"ö,`iºR»–[{È,Þ¿ÉB«¿3~"É9E“Ys$Œm ~p›Më.æËǷżÙt`ñ{¶·1¢;dúwiS§°¦wÌ ùÖ¢X0„¢­ ‡¬+7z“„3󙽨ÿB|ü»µ¢ 2ƒk'ªdæ–væëÒŽ8Tn ñ•&iÿ*Ó‚ÕÛÐXJÈüöúö¯¬@€,»Ôƒ÷:Í‚éáaé‰$äž7>Q(VC#f)¥Ò©Ò¯yžBo2ãз“ipÛ\“W8Hœö’YùZÙ˜¬›3ruˆ~5²±#[ãîèíVì‡\¼)(b QYJ.œ,5:â[ËÅÒk#Ú„ðáéqò|ŽC‘0PâæH¹A±äƒÔ o]UŒ°‘ØvÂc3§–âA½äÃs-«haG؇½@/³?¶œ)ÚI¡ÐÓ tv¿S!ÙE9‰ZìóÌ&KX¢´µ¡û:!)ºyÁù7éoj£d‹rÑ7•Ç+˜vì1 ò!¾xÐ;ÁÝ‚²þ=¡:ÂAu3©šÏ|D*B—ýÉ®Úw£ýe_-·Õƒòð}1ý>[Úºº›^Ãjw¢´5j Ø3;¬£àßÌïu 841;¼-~ᣔ,:kXñû€|*ƒN™ž&ÕËÎSnÌ>”¤c±Õ.* üŒÕ9íÅ 'p/½Qé5J&ª œY„ã15Sgaº$AB°é@Ót-jhHíRºÃ®ÉǬÅãÝÕi`”üÅ3ü–7›R¨Ê”¤zc3éeHtµ“—[Ç@™¦-L½LÿkîAÛÑ-·«·ˆÆ_áÝþË'd]£Ïpó¢ø’ª"LÆ¡ =4WóZ»mæÌ.NJ± ê¸ï>Ø¢Ž“y2ú7·–å—êëÞÁú´"·^ÎñY/©NÏxˆ^)¢CûîŒ2³W3\°Õð„H¶0zïXtU=Ç¥_…q»ôÕ^Ä ‹âHÅðo}âf²*eCX} ŸÕ«¶^Ó›aú_¥¡žÿ‚$£Ü½?Çi¡¡’|Zç(«U¥DŸýÞUs?Ÿ®W -›:jåÞœ²dœn°DŠ›‡·nOd‚ðs[¨kÜIJ¹p˜âÈ!–‡ó–>m¹mÑU’ÛÎû"gÉ0ð†Í %–IuÈ“~MÌlsÁnÂË«m&ûë\KrݰªÎÔ…u†éxRŽªÁ¯GîÃV×ö% ð'ÆëÃÚ8Mæ·¿=ÕZ‰ëÏJ"ú„²³Ð4îzó%"³Fj˜òËÞmî¸5GÁ±¸-+FÝÚœ‹áö Aί¡^BËÅÞ„/úxA0‹ðär¶·ÃÖ—C=¿ÀFoùƈB1„`¥2q+Üö| ¨P":a8ij@<³ #%žSµaä‚J‚½è™¸§¯\yŸ$¾5:±%Êî¡L-þÃÚWk´g93ƒ—™oÕ™³óV2hX ²±`i`0XááàYBÁ­ñIKºvæM~áü#ò¨@«T@ù¦KîÃHOÿ>Œ#ؾéqì¹ 3L§àLrPŽÏÃqgá¼SóÞ ³^Ú@!Ú™ÇL*LÚh… ~`ÕJ`E%úøðGé?+‹eHú›[DAâíMgÐkå¡pdNCàäQ5—w°ƒd*/VÕ¾i̓¸jÚm fDaZ.ë3FGò÷•mý‰ðV-¼Êц¾àWÅ¥FÁ,—)R‘m27æšÊ¤ÓÒtá–.…k=6Þ0þ[ðÜ­ŠÈÞý¹ìŸ ºN­\Ú¢y#Ççúê’+¦U¸Õe6»²ßÔ^ ®O3[E.áÎf­$r2Ä$m¿ :‰%ˆî +¥éT¦ytRƒ…UN–ªäu“0D]fU¥ÙÕŽS VaÝ.>K›mìÉ„#Q¡rW÷_ ,c9Y‡Z® [.2ißq žŠø72Âç1|‘aõ¦Õye6L…³dÿv»Ï©Ÿ¾•ôí(ü’ȈI†¦{é–Omöï`ïxMìJÙZ™6w }•uß²±ÁøyìY<…jå ÐFä'j¶IÖjÉíY­O㈔ðÔO;‹šTÈ%S!f%mºßhšZý\A/—Ó³ô1BÝMîm« Aø `³mæïÁÙÈp ‰peä#ˆ~5²±#[ãîèíVì‡\¼)(b QYJ.œ,5:â[ËÅÒk#Ú„ðáéqò|ŽC‘0PâæH¹A±äƒÔ o]UŒ°‘ØvÂc3§–âA½äÃs-«haG؇½@/³?¶œ)ÚI¡ÐÓ tv¿S!ÙE9‰ZìóÌ&KX¢´µ¡û:!)ºyÁù7éoj£d‹rÑ7•Ç+˜vìƒ$C|ñ wƒºeü{Bu„‚êgS5žø0ˆP°€ =$d­l´‰ƒBO¤á¬¥ÿS彫«©µì6§z+CWö ”Ô3½ëñÉñ1¹ñ>¶ÂŠçÇ‘ ÞæÒÝÑ?Êa_Õ!S yoÎ=ÎÞšd™uëÖ'-…Gs“µ£h 2Î,¦ –‚^KÎwOl6Û¼a@4´ñöl¿ EäÚ\öáÕ¢žQºh„<à6ÌÛ6ó@€=îâÓʬp‹Égš%"?â1*51sÉ;Û"ú¬©ãŽv¡|±¤À.‚¥áäE`—ªJÝ'Î"ÙhЃ‹[~CY÷žr÷*WyJHZCâƒo0+LÆ"^¥’ÁcV*ÉaÈ Í¹fz=ëÓZlûŽº¶ÛõwlÞÜ[i§™ \qú´!]/gy=?©NÏxˆ^)¢CûîŒ2³W3\°Õð„H¶0zïXtU=Ç¥_…q»ôÕ^Ä ‹âHÅðo}âf`°°n;»¸tºÕÛ/iÍx/h[É…³rœêÆâ/zbÚIîNÚû¡pö p㛨¯€$¯]”ºñ(óè”Å#6 ^ჾe—ãÎòórH{ã§š¿¬r®I5tì§Ã°ÞÆeâg%iûeýjF¶6•Œ¿uÂÕS”ˆPf-°-×LíÂöíÿP—¬"U¡¬qß^‰PŠ?·ÿ~Ú5&©îÅ.ˆd±ó\Š*Ã<¿%ÍrŸá†¶¢ò!ûI!Ï p4Ê¢ñ #óâQ>+ò7ø¨”LSÑ!Õí@0¦é~ùüuî}W µ˜ßs °t'´}æva[4}ÊþûžŽ¡~¼ÍSh"r„—ÇÛ«Qöj\~Ú³`þ¶ÁæHE’›_€E;n&YÝJ®seƒÈ¶{ÿfa/æV{ ÞBÁpfQÂV,CJF´Oó½HŠ@!I­2› È;{²„ë;tyz&Ü—•×âp°†Ó³ÛÈyT€»ÅöÈÇB¶[8ַΠ-lU“3ò¥ ›†ÀÖž÷õPßKÖ` o³§š>´[9w¹ãNÜ:Œ3‰{[)Ã$P,ïUbÈÿrxwÍþ$…v 6^°ê¤©8“î6šõ8]þÉh Wm‰õÒ³¸rÔ¡ç[>{4Òc)D~il+}Ve¹‰I©Ë@‘tJWG-–”;dÛ”óN9°?SâY9»ääËÒeü„k¡Q‹@±Iôúç´½‚à_~>àJ÷2¹ã»Ê6üÀÿg»t…ßCÇ¢b g$rà »ó|¨XüšrpÞë¨Ä­¥ìJbû¦ÑÙ¸;Lžî‚çüJÞ«ÙËǽqžh7Q@T""<šÐKû+`Ù1+TßȶjvÙ&8¥èPd¢¥mB%#=› òkߥ6§ `ÝØAyV Ò\ÉAo/vÚ»è3Â#þ&|PI¶žò¥gºŸ6üR契©ê2]çRãÙ ^Ù¢òiÓhšwŽ™KŸÒgxoQ-ë½Pœ\à ò¥èi)åЯ˜,Ú3Ÿ¨]Xº=Ém†v'×RnC¡ä] `áÈ[h[ÓhòÄ2ã¬Ü*T Y€t믟ÙJ‚áK|+9oàzÍÚð¦L]u­i•„ßAŠS‘«=Áa=Ϧ6‰\¦mÖx¶oywÒ–´_ÿ=:‰¼˜L?:UϦü~s o¬œgg!j$Iá-$P¸ dPs@zEš›ø&ðzœî˜M$²sª‡yVA9îº@~¬Y/nç>M5.›[®;¢Ñ-|ýGQÕM.'v‘d3óVA¡o7¦|…Ú|½&Ü.„þUÒÎÒ3Y¾Þ 3OŒQ¶ó4ioFë#ÿ}”¸9¯d0s“|Ì7Æ¡`ŠÈj\k­%\×ýÞ—͆åà²ÍÎlOmcÍ;Ò¶D\nMqŽ1ѾÒöµ^<Ï$ŽuÉõžè¸ûXuñD)¡p^oB¶P™·/øÕ 7aæý”s<6ÌpÑ¥ÐÂ{2…k&Ñ—`+C=Ñ’tÎQÿv~CÆ×þ`èäƒ £e§…cûÚã™™T¥>ÊuÆ’ä½e´/Îid$„¬fàÒnùoÛù¢á59ê¸Ã„íãJø¹ˆÃÈbþÐØß»¬F»D7uݤÓ[%.Oö5µ0’F9L ¦ÇO"å9,í›êVÔÀÆ©r<ÍÊÂORv¶ td­r4ÄnÌ¢/Ññ탛÷ò×9/lÞïf­è¬š®aãXŸØi_»°·ù3XMßF \{¥(d>×ÍØ´^g¢Ì‰•Ðý™Î!ø“¬Á£¦°=È0(À‹o{2á»57Šìâ3lN܇Pñ-uÃÔ5óy8m¶ ˆ¸~¶ob}”£è}m3Id“Ú‚˜°’É`2 *ÙÏDäïÉ|ÉFo”×5‰Ûá¡,Ôç°öos¥ íÉsïF¹ ¯lA­!AX9)Ð!¸°0¤œë@DÒ˜ÑÙQXÿ¡wÝj›©„B”Œâƒ€B–3r°ä9÷tø$èE¤&ë˜r”0A‰5õõ8Yaî0ºNàIr¢¹öRÎýD„€mÉh!tk<îõ>ƒ!´S¶å•òõd#áïùs0EÏe´˜mâh×^ì#&D‘É)]ŠƒT]DößÍ‹½x†p. áÖ=¢Z±NÓ`»öS ‡-×I›þÿ:±sù©‚UdêÙè\> ¡ @Â4Iµ-¨ã u=VLÆiÊ{ð̬iÿBìí™÷ÏÛ±Ág$§“yùcø:\Û6[»…œqŽ/ãó_ú²©EÎ œ6U­Ü»>ÓØÚï§Ä»e²ÝØëPè€ÜîJxwµ› MV }úTµ6 ÄÄACÝ ã(JoBáýÖÐw¥<|]9cvšÁÅ×EúèSH^Æþ+_4$N'c¨µw€­B°§¯¦<ä *B:ökrH ¼u$²³ðÙƒƒ†C¢€ƾH¹þÙâfßóm5¤››ÙXšæÓĸS¬ZúŸx~|¦³ÌÀK¼ d‘»æt0O¬4è $‰›D[nåû¯»‚  Tö©’‘ÿ|ÎoK…¥r‘[˜_Ðà…‰²¬ ÙnÃàKoV"ÍG`q3ݤú%Ë v¨îy¨¸2"]W<ÆÙúkj/‡ZÙ~ 0úQ{z> ^fª(ZRFN¸siôŒ Cì’2ˆÏÃÒö~3óõ:ãY–Ž›Uß™»¡ÃH_í‹Ô;¦ÝfDSùŠ@¯Ž¶ï "úQËEcç!‹Ù‰tBé" Ù2ûV̽N¤i«=‰Þóº¥ÙˆJ€y¼ŽÄ}¶¤5›°ïŸ_wU'ÄÆ^Ôå/_ž®©RX:M•ÔUI^±á„ÉC}¦œøL ¢ t„|¡ç6€ßÑè%§ß°„)Ëö$AáäÂ0eèôn[b·s M~–a©ÔôçÛ§•˜2ùÌh0¼'¬ü,ý©÷bgRz¡ñ˜L{“5é[Uš÷t<ÜaPÖYº¿Môæ烂á*¸´Ú+òA½PúHÒlåŒòíOÉâ{%•,‰ÖüÞidq²³•µü~êKpl9¶ûë?²mS°/¥®¶W /À-ü¥<ª1³™‚1§-3V–5ÞˆÖ\l4ƒG†JÒ­Á`I¹BÜL-nR÷¯A ÚD&Kn7÷Hã ßÝ`êÚ:)¢«7²gÍQ§™ŽlÕí2¸$—V7•^- ù·#ub¹Â¼"'ERP>éܬŸn€› Nm?¹÷ÓÓqUŸøKYÒóx¦^ê„Ø°Ù/3 ˆ£ÎŽiYѺž}oK&‘%ó KÅv‰8Oß<”ÑHðÅDèX}'­L {˜OÁƒ/Vpg˨PAþvK \ܦ/Æ~¢SÆŒW|LU{ /mјÖ >hT‹y6µ‘á¡é€ê‰•E¸‡NRíâÀ¢„Ýö‚ÞPBÃZb‰Iºïh=SÔñ¶•nõhþ›rsõŠF<+g¶ki(‰ö%›ÉÚ‰ó« /Y;P=}~ý¯F˜ÜÁÑ.%H”±ÒŽì”¦,(ô_0Y-«‡ê×°ÍE+±P‹®™r(¡^½§“%ÏXá:™¯Å•Ûù`6µ+f-·hfÔØ*ø ©Ü±$(R¾®IÛze`×÷È üd‹H˜—ÿN@XãFò¦s¶r«e³Ç“˜„H¿Hmýë§ÍXÇ\åˆÕÄjÎåï*¤tÕÈ«‚ªÊ„ibi ¡Ÿ=R턉|©±Ž©¥ç žKéY¼–c.ê ò)Dõ6 ½¼M¡}9+¢8Gñ$%Q ¹.Qc4öÎM€-P˜ªávŸijüX þ¢iò¾`q| )„A×Úƒ!1¾"PÒCtÞòwG.\ÝóWä¢Ça*£ÊJQ/ïxàSΈo­;è=Ÿ;sdAÄÏ…§3‚3K)T¹0À‡Ã¬ü ‚;¦4h—vi ì+ÌŠ°~$ØÂ›´¶•­äúBz®ð0àÌD•3å é8•g>¿ê W¯l+IN¦TÍÀ¡¯ôŒcNÇÍê£Å­Î7PDÁ55Æ Žôu$넪¼ËHä“ Úi¿8ÕßϾæDÑ0Dex»ÙSa(•Ó\†¸‹dÝÔÍÔß–¶õ™¥'ñ·h(ZÙÞ— .RmØ1ŽIî²èÓÕÊTtßN˜ô5ÕŠ }58ågZ­‘‡®øb¸é&Âö“‘&ÿY"zÀbÛìw·úú¢lƆ½UW%üP“*6®„ê'H¦ïYë³{à6)µ‰;Ú½–iMÎV¯/F¹XÆOVÒ%„ë³Id©¿büî§€ö…`ýŸüšÅËum8yY‚çÑûLh¶©8ün(î]Ä ÀÚïEÓzuJœ7ÐÈ+—‹@™o}ßÜ<‹Ž&@J Ÿ9ë `n}J£&®`"OnÇÞbò¸%ŽÆ`ºóÄùÉØjq“2mOK™žó¿Ê6qã묪OËÊMWÅ€92­[·zèQ)‚üU¼ÙÑ Öo”Ï/<7”í ²j;ÇHÍŽPÛwÌ[¸N"¯ãU0ýaÝùy-ùö"^W±ÙÿhmG ûå'È•‡ “ßu´«\$ H'ÃÖâõdµ.z ¯'Ø` ž¬‰€Ã…'é5Ñ0ï»ðÝ?B:Ý Œ7]€m¶õí«(Ì£|âÏRç Kò‚ó©@2ÐÀPû(1Horšqæ÷Іæ¡>cI&%lªh ª=h¿6Xhß3ÌÐ'ø_tV¢|$U»¥Õx Ô"wFí,šÕP)Û·á§rv óNf6ìž@OY4kw^i> ½Õê‘%‰(ËϳWsD8Ük¥Îå!ÄYîm³9×ÃÂR ŒÖ&èKû«h ®ÚÚÊ.×Osˆ8=#ÜÇ ¯5G‰e*-;’Í×ѬŸ_!çzJ f™9ü¢¡Ž^"z¶yñ¨ct5 ï»Ò 9ëÆe¬Í!ùŠVU›.§Â×+{:çì2‘²¥t9_©xéØ#Âçäe#¿Î^ÚÐÛÝqÚcOBã ‡M'èEéc«Z-7~ÂyÈG9è²o¾ß÷R•xxä¬èÃ$€’àÞ-d¢(þWã[²+zåm°u¡êdDÙe¿{s£yPucŽÑÈ c}À4òi~ôqcÌ;ø…æÂvQ)p63åÁØÖ†–­ —å»9kkÚgòÿF^S'rôѱ®æ¹µìÂýñ¸¾M¾5ýê¡ÕEé,d Í›v°¤÷5eál8ªÌ‰:IJ‘¢lØ/ôsü­N¢>¹ìß(Xt¾ÍM‘"µ·ŠáˆU\kþ M÷·PJ¶¬3YÅô­é…éT(+>€ÃÕ 4CaÜQPšî°øjH_ô~ph¶þµõ7ß ë€m¹¥¢ÿ,Ìfðim(û¯*è^S@fÿuœ.ç/‰;õ[û‘{±.#Q[âWÕÄÎz'Ðʧ sQ­ºªhü.©QF«Ééÿqvê_Þ±~E(ÁD(åÙãÜÈ„<Lôí õFùLãzÞÜá‘Àó aH³™òñ®-æ0û1x"±™*Û"__Á>o¹xØþ54x9LÿLF•Êñ·â|+oTÚ–8íÐÆ§ÿ _ˆæ´EDŽA” ãh5*X×Ý1;¼¸äÞåY!Ñ|QìÎq_ Zê îcï\½!nÞ9[$w®5\-ìôAê…4c1Á­âÛý¸å삗•5?¢*¹øÛƒk\ä=wÝÆaRtûè²Âü°Ã§­ãéPZñOƒ³KjônþP‚cKf¬Îx}a‚Ý@Aûáý™5ê;“ásã_ÒÚ2ýOÿ:æP®þ/þð #û­™ÖÊŒiS‰ÍL¼Ö¶C¥ÿ8…6 jqo¨wÆËU„ªT…‰ý/$þ]zgäñëÌ-Žú¦€®ˆ²/}ÔeóèílòÚÿs Cì™ÔTÂÛb¾ätôÔõjg m„Í}E©Ë9à.€«Ë¤Îç—YšÄhzcGTÃåK“éoÄ^iÎýüÈ ^µ>Õi·7;µ†IVBîœÉ±8tÓY3¿ !ËXÓa"å2E;O¬ ×r²$Gr»y¬L—ÖÉ Ã#°n7~!þyR†dÖ4§¢KøŠ¾¸³$àõ¯.½'ÏÃÒÚ~œóóõ2ãY–Ž›Uß™»¡ÃH_í‹Ô;¦ÝfDSùŠ@¯Ž¶ï "úQËEcç!‹Ù‰tBé" Ù2ûV̽N¤i«=‰Þóº¥ÙˆJ€y¼ŽÄ}¶¤5›°ïŸ_wU'ÄÆ^Ôå/_ž®©RX:M•ÔUI^±á„ÉC}¦œøL ¢ t„|¡ç6€ßÑè%§ß°„)Ëö$AáäÂ0eèôn[b·s M~–a†àD†¼ Z_Ê€_ROƾAy«3Ä',ƒåþ÷‚¡Á,ÝLO³ÅAÓ½€Ì=iá ¾=ƒH/µÑ¸Ÿë¤‘HY,NuÈ8¶ˆ¾…}FªÊÿ7Gç>ùgDVÀq`OÍæ‘6Gµ®Ì ŒŒ´i;®bHT™ň]èŒÅæÏãƒ*YªÛ¥ïêgS´Ë®š`.‚zýíJ‡L»ç:hD]³êKðÛ¨cº çÙ¢*”N•XG¦¹YÃhçüã³êk·7û¥¬z¼¨|Ê…ø7”VŠÈtzÊÇŒY2ƒË–´À3ç¡ùe`¸,­ˆcáÁÛâK†wigNÎòô´¹Ž+vÜ4¿’;²ë!”úð´ƒ°Tð|ùŽ®ôz'+¬„€ƒ&`¦CRóÖ”§.Ý»ï¤Ø®áž¦ý÷_vÔG÷iHþ7 œW›|£÷Oà¥Cá) 9h8³?t©Á%ß—ï…yñ¦h¦¢rÔ•T¹\{I¡p•hr6…xéçp¦*]†!a¼tVxø›<¯6rHª‡šñÎz,V8ÉGÌ b8ÛœäÇm‡a†¸n9Kz*Êug¿,©@«=+ VOÔá†ÖA¥OøïÓ@·­oˆÍ΋´¼·âKÞfÜìã‰‚Æ tV,ú8êbÇ=œ4ºë}&:ÅC`ùCž +ÜGêõ£þ.ðZËu4:ŠU^bìÆÓX>Ædî*×{®êü3ORÈá€íPí=zñÍÏ ŸÊ`tã¼Ò9‚Žd`© ¾„Ñú-š–šy€E_ILÕ^oè`j¡§¦j”$+ÐçÅD&ç‘þÒí*Æ­VÜi»Âó:÷-Û÷ÿ)¡,û‹UVùgì²™èðî1ØüJ¦áà£þ‰±·,ºRÙ5MÐJNßžëFxCjÛj´ž™^œO¾î›ÇáôU"5 š#8j±âþö™vo·×¥$¬êà ’£ÌÃ"(ó£šVI |q¾³¯—‰æG´$)£– øýéÉo6ܶçMj î§¸){üÞ sÊÅTοgû|ó•Ñ䆿»¸ãÎPÄúP•—K$Õå‹ÿg+-ïØµ‹ay>ÓZ»Trï—¹â9×ac- Æ/£8M†¢Ð÷a¡Ÿ[ BWz®XñèËð‹‹ÙÂð@®Ðµ€bçž9âÃÉÚ‰ó« /Y;P=}~ý¯F˜ÜÁÑ.%H”±ÒŽì”¦,(ô_0Y-«‡ê×°ÍE+±P‹®™r(¡^½§“%ÏXá:™¯Å•Ûù`6µ+f-·hfÔØ*ø ©Ü±$(R¾®IÛze`×÷È üd‹H˜—ÿN@XãFò¦s¶r«e³Ç“˜„H¿Hmýë§ÍXÇ\åˆÕÄjÎåï*¤tÕÈ«‚ªÊ„ibi ¡Ÿ=R턉|©±Ž©¥ç žKéY¹4+V|ž"ˆ¨oc¦+u-’ûE„eõæ¦Á飑‰å2äÑCÆDð\Š®;s­ëøm¬\-’°”J°«D{?süæpñ1ëÂT1á"õÅIf€‰djÐïÜ„HæÍŒ!Xƒd¥þðްÅèè±ÎÅb6³ôm¤£3áiÌÆ`€ ÒºÙˆCa9¨AnÆA~Ö*fc„D·ÚëôâTÚüw+âóº&z ô„×Løoú›_åH®XõÚè‡uû{ÝÏeöȤTÆìÞ5 WÏ12™ÕÈ4ûTç†"‹p€´ïÁjšN`k.í'¼'CÈóåý ¶ÆŠ»ßYNfúì×l›º™º›òÖÞ}Ÿ„ÞØ2o¤UôÍæ¾â€¸­H‚ø:áseq ú„J@û_²Ë2šMðõÞÿ[yâ¦óH©P“Wo/K ¡r D½? ±*ænè\r:ÍÕµ„ÓÜ´•Ýåäž<&(F#óX§¹ÒÛ®‹Üîº ê:Ö´‹ÉÅyZ¯dy~N˜ê,‡Í##Ô\³€+c±Ì· ‡ñÁjÈ wú l.ðúxHH.[®'îŽì ꇅë¶É SørASİa¾mP¶ç­Àrç¯\¡–xcÎNÒ6ÁÞk ’bÖhŽ´«r >„´”9ì'°U|$ [ÃÁôÍúÝq”ëÐ^‡Æ]›¸ÀÀ‰4[Çmå2Ö]}qg§‡Šå`Ï»¾«0<{´ȸÉ}µ€ÊþJ…71÷-’ë™ÐS›»1ÙE áW‰ ­˜î[C&$Á>O•Ì€@ÃËèwÁ.Ùì¸ßŽ í½ý]à 7ïq].ù£ŸXÀp)7*ÔéóÓ%‹ƒcE)a`¦¸b*Ф“q*? F¤à Îo㨌ÎÂ’sÆó]Ö}^íY™ß?f«þ&$Ä-UÁJ+ÑÅgâZ?¥ò1Á¸¡ˆOìÑ¿°©emσ÷úx¼RÖœOq5®7N][Il#b[eð¡Å@B\ør?iÕ'Å;”SYí³ÐÏÖŒnVi‰°V­›Œâ%îm.÷Ü<‹Ž&@J Ÿ9ë `n}J£&®`"OnÇÞbò¸%ŽÆ`ºóÄùÉØjq“2mOK™žó¿Ê6qã묪OËÊMWÅ€92­[·zèQ)‚üU¼ÙÑ Öo”Ï/<7”í ²j;ÇHÍŽPÛwÌ[¸N"¯ãU0ýaÝùy-ùö"^W±ÙÿhmG!£3GåBà®ö˜x Hör”ç].®àÈi€,qDŠI.z‡?ˆÄWa1­¾`rÃj·Ø¸`µE±²Hâ½Ö™FùÅž¥Ï@—ä'ÅßR€e¡€¢ ‡4ü7ųaˆoF^v‰ûòçš•°±/>EÌñGNÃ)”Á!‰ÖÈ–ë£\••:þ€}¦ á»r%PoÍrþàÕÈß|U¸×.ä®gVü,€§‡"ÄÆ– •À¤Ì…Þì ,möPs¥Þ£](Öw)"ÏsmšÊ‹Úϯٻ4°ç<ˆÒ•H¹_§¥yVÙ^ `¥b”íAéD¦9CÁfåÛ/ê¬z“¾ ‹´ÝH»aµ± f„6Zn-’ÓCÔÓ¸? ª ¦…ËD|.3áÆ0h>¬˜ì:ÈàòäÏz¬¼éI"ZD`—ï0ÕÑu;gÎî9Pݺvrê°­ƒÀtÐ~„^–:µ¢ØÓwê×[0¨Y=ÐËD-zE©X¢P:‡¾28wÆÊd…;âêCSëÁóJ9w‹LÊT?äÆ{…”{©ÏÆZ.~ÇÕê\&t¡M ÿnyLÂtfîã6I-¤¬±zþ bÆV”!Ól›¬J03µ—’Œu§aVB {¦lò.\#º4¦Õ2Ðx`]qºßp“©ôT2“CÙ­Xmï¢æK‚d+Ý×S3!q°âhyg÷!xùW¶±]Ü*ÛšBÔ‰J]NÚ©óØÈ9îiÏ«gÒ·¦¥P ¬úT#s²ÒÚû tÈ/Ìî†áéuÑÕj^ù(9³I9tO[]‰Ií22©1ɋ܎NË"Âü"Öô:þä^ìKˆÔ@`ÌÅfÏdx8AAöÒ¤p@5Å·jJ–£„Ô¥aÝ8)FwZ½ÒØdˆ¾âGà M ȾV©º¢Tc xôý7˜ÎûBøE o•§»[Ä7‚íGØ›ï›üQ>Ak—Ò‡“XÉýN›Ëïy^:<;I‰Ü½_yÒ^T¥¾‘|CžÀ¯Pn3¤É¦),GDýÎåæ3zÛV}¿ä(°¯§Íº.;8c|˜)ÕÒ{HÜU¨Ð¶ ÷ ×ÁʲÉ['åÐí1šEFYñê@*ÄôüsÅ”/D>˜Ê‡V¨âMÀ¿µ¸á·XGZÚ½tïï ôוªâˬ-’Ъ¹Ø˜i*‰éÌ#Ö‚«‰‡¡ex%§÷f¤ÒÕ2ªèÄ’/=©Ê‡¼Ún¿É³=ˆŽW£×GºÌ)Þ®íö•†µ%äUâu¤ßJ­tt¿äôÿVÈR4s`×<ÀdPaÏ»UÓ,¨èç+Åþ¿§¦D8mêÕx™ë‘>¢µÃ•Ô\8ÿ{›3…6Âf€¾¢Ôåœð@UåÒgrÿ}-J¯ä ¬F´ùèfådÁß×'½ ¯ ªi‘…ž­-}lOÙ¿þÌm“ðx†zÒô"‡ä­0ò’êÓ vŠunŸPqt/ BjOÜàƒ1—a‘ÉûψžT¡ŸÙ'@õ©èE’1‘,Q¾]ˆá5/+dŸÏú©øw3‡`ä?ÛЦ<ø3M“ä“›eá£ßÍÔpbB¶AÃåm>2»tõW_#µÚ¸º<°—ˆuéê–šà—Ã}v¶I±BJ·ÌZ{.ÉQ1 `ÝŸÕ ²"Ýìõ4tÑ­á&‡ä‡×°‰9<«7½ªb?†¥…FR—„wäÖ|YÖ¿ðSõ#¯ÊãÍð<ÿv€@5ÿ~¼÷‡Ø®výI*ÓËRÑ·÷£BÍÓ)vK„\xiɳ1uú˜T&šš©è–Ša£FáÂö¾‰Å9 oHè‘wš¯c©SßTœ…Õ¾òÄ<˜YZþGr8=DŠ'5{r9\±ËIí-†*–©Ü5¢‚™„ÝèÈT=9'ôÏʲ‡©16&ÊqÄ”ƒWV‘YÖ”y磽ÎQm’ÊÁ#äð—s³¯_ná” È™ùn?•jpd¦ýaÊVÚ5^™._¦H©{k÷~ÕÑß2$ä­*BWUHÓ³H^_¿€Ÿ^A‡ô}ìô‘î³…Ýž /|ª;‡ø"eÜÞ¯”@Õ§¥‡îMÿz-µT|Y“QÚÝA0téh(ƒ)€Ìp,°+a R[Í“Üé?#^¾±¾ð\þHÁþ‹ Ç Óà†è?þFe«ÏÉYUxÌ7ü;Õcõ¬K?IƒÆž.ÉùàuÁQ÷~”兀4G¥ÑdÏìÌAíµm›ãŸðt]ƒj; 6µ×ªfÕé©§>e>ËûéC÷íRšl=¿õ¸–†kÙ¿ ÁTÖÜXV>ôoj†çþ50 ‰ Ú@¨ð*ê}æÎ}ò. Èø&zðfò®­ùp_ùÛalS‚]ü¿¾Ö]Ëò†ý•ÏÖYLˆ)ߎw£`òP…Ôð¶ì0ž°LZï³ñ¢®ƒ½ ~70m`ÿxÈ M¯!Omç€3gZO„ˆÚÛ Ëí­ùvæS4±r ÿ½wì«ñï!Á‡#^t¥häaL=£Hô­ôð,®^gܵát+MR™…¹Šú¹nÙÚR Ê—mÝDÜA¯ë‘/ž9Òöiܹëp£¨êJ¸LåwqfO™¤T‚uje¼$.¬{=×;™4þüvš~™½C5QmˆüdÙ¬sf—ЕÃÇŠ–˜OñÛ1¿9:€`åHÐÌ¢­DùMDLo›(š<ƒfÒǬõí ^×Î ˜ l¬Fú÷1üò`}`VŽó}Wwø½ä&¥0[Ë›ð˲©VéЌۢ~ÿ#jflzò¬À!5: :VLèôØ 覽Û=_“%.Óm cÎ"Ϥ†ýÈV"ðE,™,½³óUžî4Wåzû<¸m‡æ0wáGÎÂ:TæQÚjO§¦‚ù~ü/”oõm;´X~Ç#ÙçÉ\8—ƒÔ:¥QœÁÅÜ=Dü»û¬Ó+Ç µi_!µâröá)—è);VxM ¦0Ol 6ƒ°!KŽtž8p~¥$ùínÔ÷Â`S=>kì»oøÐº )¤»Øo} òË;fû3p5¡ßGb¼)òuO³áêûxð=%˜dÍÆ‹Xy|Ó&z\º:übÞˆu€&láö„wÅ£ÚYMýÈùM(€š®ƒQ£|¢æ?<îz&^ºÇ:Ó~)ìܦ¸J'ÊäÎKÀ…€]oß;àéSƒ‘-ˆõXœJ˜4L³`rò·sAøÌ3fÍKÝ&úÄÓ Ý@¾t L²˜ÆŠé›Hz©ƒœË`òÌÑ/ï âôN”×àÁLHôÍt(L\òG/®ÅE´`4…O´ÞÀÈÜ•Îc›QÞõœ;Ôq´î0‹`wQj:q¬•`ô4Ø÷*~­ÏqbÜ‘1`!•v\̸ ùœhõ;á;Gë'tŸH©x%ÆåŸKø²E¡ºùD:¬<‡ó6Ö1Nìï$¥Ÿ|´ÄÊ ¶œX¥?&Ú(#&wBY@á«8Sh¾U¼V*ïèSÎ÷®Ž‡taôÆnº…l^]_nVD²ôUCp£©±<ò6>q1”1ÃòYޘ녩xP3{˜ébï9âˆ3@I/N[¿¾'ÂpàÃä¨¿Ö à©8Eú'Õw±÷힨¿íÚ/Þ+éÌnsë„´¹ð¹¨»{§~y1ç¤+j=x÷{mjª…ÍçýCÃ}Š…°ÆdÌSeÛñ¼ˆ(“5ÏÊxzjfEªø¤{±#Cä2‰Õïé†P,rŒGÁž4TÇk Í}ŒçF‘DY’NÃu¶ªtãÊRÀíæ9Q6›G)¼å1 }˜²†ž‡—‘¿ûQÄŠ¿GÍ·9SM8h©3È«Ü UÃËwtlíχ³ÚsOB͛ڳ.§Lõé·éQ;KQNHuB¹`ö_›¶ažgðKþ 7²¬d”ølR:Bû2)ƒÉ³­ãûæÏº! …lyä'Œ)5úî¢FM;ùË`×0¯uÖÄhm?{–ß·ä åÄ‹óÂÂý|ÄíVOxÔ> ›½à—‹T|Ù&Jˆ'ËEà‚K%%Êè’ªšW«=¡ÞæŸý øø\V‹ÿÉüüÚO|®Ìx0v² §—GˆNÐ.}D¤m/Yü;lÉæëˆB€0¸ 2»³Içü÷S2ö3Šó!»f†J…»'¸Ú ÛÅá¯ØÍ_è½{32Ú ÆŸ‰I’õsõ^]“jsšØÕ1Ú= þ”î}íR!3ÎÀ¢,ïë6OX×ÈRKDòš #¨ïpœýÍ/JÞBµë“É]à@ÜLO¯¢Ô@5¨®2}±&{[Îê“-‰ÿ#îë1‚n~u³‘<ûƧtÉ ¸Ùëµümg]’v\³ÁT«Ù×1Ç6ëýJpà€†YÀ,>îå/Sg¿»,ñ·]lmÇÚ^=Â(šÔ¥ŸêÍ‹…̰:sb Aúo ½Éýóui·½ý*—;¤0Sê#ZOÁL$YÍ*3w•àSRï‰@ÙëQ~ZmZeŠ´WüC)ÒÿJg¯Z±~ù5¼ÃxƒL±ÙÆW}Éš7Ö}›îéÃ¥®°j”=ž®KpÅ:ȺNØrÐä<çC׬§“\K±¿^ Ç)^š1h"?,jtšê °ôã*Ö%P&›ê— SÊq­I :Û”\õÂTqä?ã_¾ª?"¤ÂvVÄ6l´,œ†õâ/ýÀšáYJËÞúPhLÌDäwæà­M;iZ¸Å/y–)0Œ>uˆ±Ç,÷¤íé'¹¯f"¦±™ÚaŸ­:Ä–Jßfâðòï™õ\þOà(‹‚àÑň˜/X¼£Ã\ˆÔKWÕzÉ{Í«Ø ¦¤Á„Ñ·0úcÌÂôß’¶R:ßž4í.ãoˆQ‰©»$“WaœŽ´ÆO“æçÅéŒh_gß®æmbn€©&/">MtÓì$I|Ï£r»×ˆãó%°ZVÊ•m*îéªø:KûÁ£ÓçjtuLLÌܯ‚‚3âZ™‡H³I°JˆtnÌY+}ßVù¤ÌÝ¢}2³á.‡IÿgQAò„û·++¼ˆÐy R„ÆvKFQÔ,ª.çèmB0™[~Hú’^°/ ›Êq^ý [ ;v \¹å‹õNÑÿp#i—0{Ë­­Äm?ϯWTß+7ì«ïÕ(¶7|ÑÄÒ'`x,Í)@$°?óK{r‹Í‚ËrÊÍå€Õg[Ö¾ 3I—¡ÄÏÃ¯Ùøvo‡_€àUC1Q”ÐåÆi©A± žË¼Ç*:°:\„ D'ê¥ãæ¿L¬ 9Ur°eð%ˇÞ]žc 'ç@ïxžÐªðª–5ˆ£J¼¼ò¹u~‡vÆxü*’‰Û(Ú¹gfš!ñŠ=CJ“¬Š½™ S Y³ÿ¨?¸A°e1›W‰V†NÞÉb ;å„'SÖJŸ¥ñ“@CQãë¼&‘Ò÷¼õ{´Ï ¡”|à qÖ>›úY¦"õ,™múGd3~Ôä,xÃ|Ùé#XZŠ%€ÜÄ,T!J>vøœ/úgªoººî"ì-…oëyµí9iìœÞ ©øxl/ F †ÿ+¡ÀA“¼ JŨ;ÿgexÔÉ7Ùc®]3“gl·/1ª*Á ¾Ñú‚£ïìCê¸+oÒGÛ¯„ßäR¤™• .>òt7 G ðöOIp@èÄUËÖChú4—FÞÉ•‰4yÃoÊ®ÿ5ð‚_òÀŠQeY¼½"×XzÌÉ'dyÂ}ºðjZ½Ç3{Hèßà a™>áh=ðôõ"‚Ï¡Ý2e¬Íó„²ÁáAt·ÿZûŸqI뀎Æó åAc‡’æc‹Z<;J\P…Ù=šÇäp4©p§Éòctwè|²w]¿ÕÏý÷ÊCŽ–•Ö§ .´,bx@4ñîO{¿,õ¯ÙX¯£—kP޵èɲòuQ0ÿ/}U6+:‚fÉŠá’`<”øãà>ƒe¢Eóæ[œs „$?„ FT÷0Û¼æYÏ·t®qc’åÕÑË~¡Õ¯w4nÏÀýoY¶òô$´˜™¼js‹q³:Œ¾Ç²Â‚ÇEc®½ƒQP/•ã3#Ú—S[lJ„ N†0bXJ‘€‡¡Jû_©ËBŠ,^整·ª` 0…$dý}Œøtê3c”Øo¼ —7Ú–ÏÇÆ”úA7®+í@¯Foƒ¥Þ5ì;ŒfGûë³°a¯µRSm⌸;‘ðžÂ†Ý_–žÊ™Ã.! á8îš /RÃ÷öqÆ8ÂÂHH“X˜97TˆsÀð66ú÷LU„ «3n‰¥ 'ñ«êwÙsû±ð¾®Éiå9°xÉž7éV?éýEêzäÍu{²æ˜l‡Dƒl\Åj1bŽ˜É¤r<^2@7*«Ev¦EúZˆœ­$›ñûKï¬ï2€ 7þÏÔýØ…X2:^OJt‹ém(D²e̦¼¯*.àëŸÙDØÏuu KÕàöʦV^Ã)<ÍŠ0%YUMÆ­žž˜Ú‰ŽH[Jèñbò‘&5€ öUæŒ3êqÉËsü "%ز´róÇ-.L©G!JEâ9’b" 64uçÔ\yLO^f…]Åù®˜'³'UgT"ÈA™VUö`Ò%5–p†Ïª /^Lðpþ̌Ѽï× ‡jÏòwì3 Q?ísã‘ýfÆz=u%R„½ýÔ圴•ˆ4‚&Óì~ƒ”ñÃí44–·øùêY"×\Ù£ õ¼–`ó'u¡Þ¦ Ý04õ›¶Ëž>y³„+§y]ÂÌ|#< <¡WrÖÅ?²Uí,q |“ùÅ2…Ùñv˜EåÃñ^.8/jàˆ¶„ÎÄ7)ÑõJžJ›ÂoRmµãA)è§±P7ðT#èOŠôÙÐ:‘†pø©OCª¬*àí ,ŸÀ4@/ㆺÄAЭšt`‡ÀµU•^ðÅßëõf'¨ÎÎì½: C/&&C*¸e'ö,Ú²/ÉFˆÌƒtéÊŸ‰kÕ†mµ>)dÒ‰ð=y5Vhè]ﺑýK‚Ã;pùðd(K]í›Øˆ³}ò º´üVÛ áÏe±æÇÆ< |K»Ð˨ ¶Z[«<Ÿ Ì·3¢ñáÂsV“n½¢¢@˜:Û¤i>RÂrìžxÃÔƒ˜ øß?uN<„èé ficJŠ¡sXŒjýYâ¢`A75’³æïX;û /¹‡ô  º-7É0'¥œ @szÙô …3µ§]•méCfÒ ;Qш+š†é¾2Þ¨ìÙì[]–Q•PCGãRXaóy™#;uÄ`@ý6^ ŒË¿Ša”Ý%؉œ1æÇâày§KrtªóB"U³¡L8º4E!çCòäkªÁýþmì1P‚ïÊù‚Ä‹§y²=¶2°òà´žÐ}gÑP'ùµ£!˜{Š\',βö*xCsAaáÆï@ gln¼ÅZO.9›iµE~¿º>¸NØØ»=…㜽$ –wV6Ÿ ž”GÑÏŒðò@ ˆæê9MtÀ}­qˆØ.‡HœÏ ß‚ªåŒªXµ{Îss„5Ç]ÿd:†-öòVxÜýÔŽN½ºï̽̕P¶»„·RBc6%:ÛùeYŠâÂÌ“æŒVRÅ@M /³±\|Ò~HÑdŽuTÊÄ|¨yMWefæmps'扢)쀊W…ßù!ÔÏÄgB釶rt¦¨èF¾+$b‡~·î1äåx)½¾™CÅC¢oœ”´BA9–®4ˆîô»27ÞDŽwÖ¾ñ®+l±#tÒ«¤àýLç÷s2êêæwt‹ Èz‹ô¡µ¨$Ã<>…ŽãªÕ`«Àµtñ,ûH~È÷‹•¦9gaÜímà•ùµmI‹dmCJÓR1çðì·øwãü;ÕŸWMÙü; þ¿ÃÐ6}]-cøw ü=ÿCÑûz§ñFØd®è·jËÄ#,øÒlIØ’{¦ ñc$ xIôL”2VíÀ‚î˜ÊÞ?âK[ÂF¸Ûâ¤À¸ÐýŒø{ÚÓQ¶W‘x­G ¾Å½#ÇQB‹ÒO7 Óóî ©¡•Ö3‘Ñ3]˜1u°?X7¿öl Þ%*àyŽÔçûDdkú žhiá~ªk‚­í ÙO^^ tÌ‘cÖêcÜç°cÞR“.«dáñ§ûMð³Ä {'P÷ô뫊˜³÷…nd=8›M‘’‰4‹E|Q—Bóþ+æ2d·]âÝ„†<Ži÷._ZØúèyúù \/rF¤NÝ:”J [ ³B˜^Dl¦w+Éé)²ª•ØLBg{߈­–…î¦ÕËD™<)W¯2ñÈ»ÕY/ƒUÊ…“‰Qš—­K¥j-%c²ë7÷¥sn-UªÔµˆÂcÝê`õ²«.#V@ñB\rø/ŠwcE}ˆ"š´§ÌŒðqúP)}€JÒÓ3ïƒÓ†Î¯}\eР¦Ä{¨×ãã&̤Ê(,©3çÛp=†‡sÛÌ2¡á6ZV}:]ر@5ìÑjƒtXc6·Wóµ-Áeœøµ#DD?/¢¶ìÏÉ……+‡®ôš)áïÞô=§Š:Ñ#ñ¸³·?1‹ ýÁ€Ô=QÞÓôðèè 0ô^ÎqÛ$±R{üª‡¾v•òµÝ‹÷·AV¸mxIßÛ·”_i¾1*Ú–’¤mZe·K®%1ÓOÈòAÿ ß‚Ýrÿ'ìºl8šÃŠ7¬8œåXÆK“ípÍ3‹‹Ûë6_þÓe¶e€wã}íÅð¥Ü­gŽÌ– ’ ÐûóÒ»o?|‘mLàSY’m\¹s×ÕýÍ’¯®/Ðfê‹ÿ4òä?8Í•=/Ö3Ú¨™~ô`±#rø¯ïÞì—=f‰ÄÍê‹õö›çŸªÈbCV¶‡µ!§dKd“¬‰‰ŸKo·²9ÛÐí²T0+˜à(¼CË–ï;b–°çĪ­×î“@ç” $0f&ø¡è3ʽèT`Ð’wãÒôäÙj}xãbð #*`‘û˜ò~P÷¾hI‡ ÔS,EJEAæÁ`¬¿Åbâ¹ ’¦IGÆü%ÒFœ©bjÄFX„w0_ñQÑŪ´ >òº²‘%Å1i7<}/æ´‰y ƒdhzYÿ¦¼«Y:ÄÓŠÓ³”2·ð­{Ì"LËÃ÷h¦p­ŠM-sSÙ<ÛYq`ûýÅS@çQLTf²°!½üŠoø¿#†‰=ƒ”²kÞú²¬O- žrÓÀFбyZI<qVc-‰®€°¯&ߢŒ$Çñh`rY†+à s‰ˆ¼7/‘pÈàRê¡y¨Í]w ϺÏðhÆ~«´9)M†’˜Vè5׆¢ €Ô D cTuw:¨ÂjU —ˆ®ÿT׎:›•§Y ÀgA~Ôr4ÊœQ^kþûž0…ô˜_!dJpû ïJ¸:²: 5d&gSUö¿”ÙlÀ¦F¡ô×9-¬kgTåB2{çÒ ÇxÁ ¡ÿ÷~”“Ä,ƒ$Àu1Ɖ®M“u'¡± 6r—¹;W4 DAΓí8²”@ëFS­Ê¬[PçÊuo'^^"+z?n[Çzÿv[|[aþP‚â"¦8&¹—Ý4âj ã>¸oÒ(ºöþ_+)íGJG…Î{¼;œz͵¸¶‡o×-<Ëu6)Ý®°±ü5ôt\Dá7 ¥m¡Þeù “ÖeúE[R$è¿ÇÂu'±88ÝüÝ%‹Ê<å2ÖÚJ-À#)`ðpø|ËÖrT–AÑ÷]ÁÊQÓ÷hZÒÇXüUtä3ÐÏP¨ Êß|GU æý³rÓ=ÑÞe&Ü0‡+)G=©Ùcýˆw©¡Bá 5_XŸI!ôÞüg NÃ=Jà$àŽ„õ3kÚ¼'iÈÜ^[ÞƒÀe¦7€Z¼3¹«w˜ÿÍ2o܇ðÝ,­Z€J)VË#SÚv–¬Ë9@AiÙ•ºÞ™ñ…²Z`NYäÅ.KùÍEÈjJ’Þþ £Ó Ç'ÎÙæOïÝx,Q”3žÀ¬f[Ãål.´,TXÏ}zû©¶Û™šo´°Ô0ë2 *îì‚&ÜÁ6€‚4å¨üÙ¡Q}v:,á‹ ¼²Ñ?Ñù†'#tWãj 9þé’ÁƒÃ5$2d›ó¢øf„µ—D°Ý:8¢1Xü gX“¤ùE —=H¿N–Ó^ŒÅ¥+ṟ;Wð)AÃ…Yý"S(Ñ|ô;Ëê$æ’•ƒëŽŠä4ÛÔ‹ˆ‡ø£E¨ù7!ì1€ŸÔ5W^š0+Ê›_ùMædØf‡âlGš ®‹Ì¡Ä€DTïÍ­pUÛ)‘„\ê>/‡ Ûp0¸ÕtáØ¨±Úgf®VÞñDo[iia0ßužß¦´â`€™¨ P 7µ÷žµì'(Л lå9 ‡‚6‰\8 .¥ÓÃgp}nÃøµŸJ~tßIq½áo+6 ­ïèE‚:­þ')Hž¼¬ÄÔ÷éž"|Ÿš-GC¨¬Ì¿+ø}³›çvÜ6ç]zCž"›Ac;+1^úô˜â?ñÜ]·[þ ÅzbãcA ".«4Õþ§¨#Vàï6 ö1¾#øÉ‚¬*xî—Óµñ°F‘<‚òÇAÉ•âø¤ *ˆ?˜®F˜Hò™«J¾ßuª!ÈU£4%— ÀpÃ9°£”‹Øª‘\â•%#ð†=ÀÎËg-iã賫ÚÔĉÐö^ç%%O™\qä`Áhö¿YŒ]ûì™Ý=#-¾N 䃀§B?5Áwt‘á´ õºÅ„g¡ZÂR0øí ¶ÂÊê>Ò‡£VþÜœiýHœŽ{ÏögŒÍ }¥–VþtÏ`¿êÝp?÷¿ºc!¼?”ÃÐbk¶*Þ9ŸóŒÚO‡?pÍžÿDQz7CÁO‰I°ðqç[i~ãŠ@°8Kà€QEˆg%¹$Ú„GŸ/ßæÅž\«<Ѿãð¯Y™ÛÅ9Wê ðxkA’(7X7h+eúDÛ Õ| 3øýg‚”=™½­ñ@Y"ô³Þe5Ü:[\¼G ÆQ%„;¸2€ø-'F»Ÿ•¶›>çÖòÂçˆcTV ¦ŒÔ£–ž‹6 Eæ—£+ ¦Ô²*ò†˜çO L"8b=*䬅چhwP¢ña"*™*êå±G4Z…ýAãI®Ò, –+¾w6)N®ÑïÜŽg*;EãÃóOhx.ÆMožfq¼’›ãœ97²ª×m¬f$º#ïK?lÓémöÂLêÆl³Fù¨L0zU‹pÅ€„6YµmÒ/½]:×XÿÇ*ä‰Ìš‚´nôÕV}(h1üÖLSÛ‹sa ä¶¡—K|þŽ˜V ¥ Í€ªâ¼~²ç­»Qvþ½¥ÚÔGv}ÍŒ†Ím«í&Pu•»Ñ›h;òt¥ì‹dæÐ·{¬µÈ ûnÝm²Ó§³gšÊî°4€š$©cœ£1Σr~DÂÊê\¿v¥uøº²M™Æ1Ó8ž$’ «¶Íd óþIÙ©éöÙ÷ïÊbË1y*ån÷Õ#š5}JE Ç–èNANg½ºúß{ppŠõùXB™ðaVÃtÅõQÞ0¶xÁå^Ãí³¡ënýŒÈž¼‰ÔÅ “d¸çÙqÃ(3ë–Z¿µáÊ}8H^Ÿ Œ{åñ–d­­v}CŸ¹SŽ€{(*,uãöÊQN@Ò`e><86š[ ÌQKIÎá:r6¯™¨†Íø݃NM¾@R¢È‚¾`“R3¹G$L° ’?qbébRDÉ>¸‘„‘6¬àuÁGrWN· ஋¤Üõ‚¦Û¾Ñî:wË Ê›¸YbÙ—o/Fxb—”&UôITò³Û ‘£3±ufj’ÚŽ1H­ßFíê»:†ù'””3©©õ T=M :‘Þb—át2 °KBãÒ:iÑBOiK¥Ù‘N»¾¦Í¼ÒPTlV6m#Š>‘…ÒØ’ÁVU¢gHz ’˜h»€Þ‹±äü½0ò¦† ço€Y½D<î!ÀºãÿÇT·þ+’»Ÿa•“—:\}V­I7£+\ÅUZgx‚´îyŸ G¼}Œ3öÖÖR…­£tl(†‚ÚÒ3-W”@_Ž#û^gúžŸ¤¢O³7zè0®¯zg©P톿üÁ!$ ’Ô±b=,¬ C–SHüåJFA;í4AÅÆ’רúјRFRÒEÖ´²éæ“èüê#2ž¶B˜©š\Þ'‡Ä0È6‹ëcØy£œ¨þšRápó×cž³6¹×Á¬q ³5¢ð‚ò…öR£K%8ñ¹ÜÂ,€ú³ù¸¨5ÊYéqýõbØj^LQœ>ù#\'=n&8|ø=n<¯ÌÝfl§Ô­hƒ‡˜ :ø]²Ä_>|ï6+?u@ϱž³•LAÓlêð+®â:œ§R»°Ã² ‹`æ”k_mû¨`|~:rè?ª×¯_³ÃÈÚѵyðNw¿SNÆ!&;¯Ï¿P_J׫¦}4A‰ˆ×ï1‰J0=»öš ó•üÛôA¦áfaßÙ—Eo·Š™>ü’Ž ÔyÏ›Èâ?Àlñ«ëŸ˜&Óê,0˜sÊO”&^%”¯ý~L9Fè‡ ”öé³ÁYî7b sìž—SÜJM‚#Îuá8°\;Pú™²ò§ƒ ‘ªšHRq VÒQ˜Å¡ŠÉéàG¦#Ê;VW馗È÷ÞO_Ì|E~`I„áDÃ`¾¬I&Œ|s”x4¸{‹šþU–C {૱ÛöÄqS*^ûÿéW %~ú—«õ ©ê0ïO‡Yâî¬amP£¬{(.Ú¡S3 jÛ?#è^ O1–â¶á…°Á!Py g±•Wª€æp½o÷Æ@’›ëŸ²Ú¶&Àˆð Ðè” Ù…;AÙKë ïoYz½2H•¢t¶|Ž!¼`Š/!€-³$æ¹nÅ0Ã̳q²òÑ^0/‡Ùˆ]Œ›·"å®Ñºo¨µ$É·-1Úöëͺ iô À(ºy”bBÄ-ò"*‰4™*å7¶t¹N;YÖÖÛ…ÜýÒS\Ú•#ô_-™sÿ+E°hÞ³i« Ç£I×ͲˆÈA¡Uî j$½ ·¯…G7Ç Aï¹Y \Ù’Ó:¼"qÈA=Zü†8ê;i&ùz|p„¥†Cù°ö–‡*+ÝêµßOÜq.|ÉšÎ\Q?4o£ÀÔO­N.0ÕÌ^õÏÇàG©‡Ä%*Ôø4ö3¹r†Ä+j T¼8q6Òàç9à¬ob*ñöðr²jVŽÑEºüôGg~3t ›'ˆÿ^žoƒöŠõÜ!£á‡ŽHÞ}-'\»Êð“&‰î†kÃ.IaÄú…6Z¤æf™±óÀµVrØïi€î[Òä;T‡M’m`µç¥Öf¨ø%n‰Ã‡›´¸ç<™ @ŠÏ\hÌÁcÜg–#øÑ„êøÖ¸çÙN1ŽuFÏGen‡|ß6 HS”E·ã>ûNfLG*4_ÕÃpqËÀ^>Ôï¡Q !N €û1~¬þèõÁ¾A 25™›i–¸¨ÒaÞ¯NÞpUt$äQ÷³Äޝm¨$Ÿ÷€7ɉþSéÄÿ9¿¨W¥¿HóG(åÃa©x홥àVÑõX_™¼Åö«ìMrÖ¤¿Q’TzÅ_¨z„ˆ¯­k˜’•ÛÈrJ§7¹m…Ö‹4fð/ÍþÕa`ËÑý©*‹LYµ( G¢;Ñ#&É® é~’tÅŠ žÏ}쟽Ê?¥Ø8–7&wˆÂOÙžÄ@ຽ “-3'¤hl¬Ì°ƒãÉÆ’¢±°œÊÏò’½skê«ÙõBŠçÑÖ¢Š.ˆŠ~#Ü6‰Zq_3"+\«Âo‡^Â ì ¨—ç ` Ó»ˆ¥@†rŠâpq7-°¶Ž²Î­í.ÒŠœV|)"!åF§Å:R%§¿…2Îáά¿µ¼Ì¸áõ]ü*<´G7þ‹râÙN$âF ðõY#ê!瞨Æò;×P]WÇÒ¶Àî™…Ð2K»Ž'„{JÆA¨ŸiØ‚ÞöË$Å~ º "®ÿB'tå©W¶rft+@ ÷èàæWéS<'õ´¦ÊDš³÷"ÓÝ~ß uþ±ÚÙðwBâ» Ðdh©F¬x‘ïÕKK1‹ ñÉ/·âG´EÓ3~ȤƒˆòuºaËn‡OåNl„ãO¸g°í»„¿„-»jiõI<—sÚŒÃ! ©È›f·Æ˰ž"ïQ‡bûÍ­FF…UšȪ͹㻜h†£ý*Y\(_ >Q6KË6€¸¼m·oJâçˆH§K䜪›T«.½{Àݾ|NíÁ8”ÈÇ#q¸%Ìmñâ=Cö‰xeRV–øß–z2)D3TnL_ I¡¥>[꿽Þm"ÿBQ^× ?xÛPU1{\€-m’)¨ï¡\Í»ƒØw@a¨Ø`E¦¥Ö¯…J œ¼h’)N1Æï_öPÖ<„˜>¼ ³òóJ3gÏ.̘ ½ƒ6Hƒ6¸K¬ „áÙõŠ(H⸷_· ü”¿D:LXÝdŒ„¥çÚ`Ùÿ-¢8¤‰4@µ€¡ P'2}Îó šŒ ¼'éR¢½¡oõ ÷ˆ»„.uWƒ¿•3 ODŠ\nS¾ÛPú8 ÓͶR¨KY-± G¡i9)둎n¾`õnØÙ+,:™´2d™/®/¡AÆÑ¤Þ¯‚ƒ¢Çååá@½½”0»x¢Û*=ÙU!&¹Ù£“ZäÐS8H‘ $ÏmF>Ð}´? ¨œ†65ñ¨æ4eÆ3kßpÒОUózŒ@OS‘9’ŸQHWoí=ÊŽèN”£]oºŸñ”Õ¿ãÓÈíùu8ù“È_(†4¨Z›x/ ÜP”ˆªÙë ðã(hÃ\Ó-Û¡™–NVK:õþcÛzù%UCEe¥Ô¢?Bç8JD™ÍÉ$fæ5å7d‡ý'}Åžce=¾œ*ÔýBʘu¿^³Åaa€®öÞž»NHUqfžÓíte%@6¦,]y¦†ëh=š‹^ÌÓyš‚"ë\YQ±YÌ7Ï¿ðE0÷ÂFM¦ FÍÜÔÍø.¥¨¦¶“Ï Š æÏ¸Kjˆ@1aÒŒŽN§¦Š±…}Õa‚\·kmðâ{^ú<ƨl©Û{h7œ˜1–èŒcpBØGat÷ËËVh>—„›>Û–Es‹\:uÇî†6Ìoí~.s4­Üëk¹=Ål:ižç'š‡Ì]<k‹ŸªãtÈðR¦ìÖUI>_Xæ9ä·E{ç#§`å–0¯k¾BrÅsJ|¼Ûçì—‹o'l,»8¬¼ißÚ]DÌ?,rGÆ÷: u‘W5t†¢ÎUÑ}§·©WУ0c._ÄÈäâú§èlJòJÖÊG‹°fÉ8qTçÒº¢[G‚°kðï2DcGEU¦ˆF‡¼gG‘Õü&Çm9ìgÑrL‚fz?…6饔˜ccÁÉ…‡†Õ“àG/J…@—ò?R×5“h¨™Rkø¹ìý:¶É]ÝUÂ@d¹ äŽû,/ÇbõKЏŸe³}ì¡È”[+š½AC¦!/©rŠd›Ä¼ßüCåjwÁ›šä÷‚Ò ॲª—¦ý˜ZPÉ€÷yfðØÅ׿j Á±'¤fp)_Ý qêuj‘îƒWe @5ø*LÙP#Ääèë–Nu‹Vø#lP\ç`J2v¨¿wµ~¶Ü½W¸ÎKv—ºSö ­µÌš×JNbµ(¹i&áTkµ ‘?õƒH<äÕ—@PO¹¯óÌáô[”"Ó³µÌµ;âg7:\ä+$<I™Úqê3—¿é·XÓ¸‘æ–ÜWd>ÛIU þ”pdåìŽÕ›Â>ÄCLGN¡\€!ëå‚+8¤Ùòs_®•¥±tq‹3\A*"˜žÛp¥£ 9`<¹!”©uºÞBò8^Ý m„ü³MÖîÿ iã£9¸í) ­eKœ?$Uœ@Ä šÜUæEuÑ:Ù)ƒ%)±Ò굋µ[½»°™8 Éô‰2@r+2ÛÐüÏç5aŒEî]e ß­2‹Íd!ok/ j\øñJ+²'ÿl',éä5„\GüìÆ¢óÊÔ7‘#VÂãböô –”Ú´Œ¹­­ÖÃS5á‹hm8Ý”ÔÅØ²µ(ÞW$‘å»Èó½V¿ Ùï* ü©Áí1صó1°¬ê{{XVçªNÐ[lëö®ŽÈ†XÍõÅ7ÿœc#ˆŠW—W¹ªÚœÄ©¼/@y ¤²û“YDe&öGF(ÔØ#~ô'c<Ôu1ÜÉ`BÞš ¶ÙŠ(‘vbÕl©ø“×+¬eAJ\ÒÈèÕ|WÊ3gÓcÊú´Æ+2å±|ÙVž29ºœbå!§%PRú}m• ‚0ÕpçX6 ŠÐ,Þ2¯Á}áIá7ÙLÇaGÓ¯™Y`5‚B=Xäø'{Ò+’_¹§>RÂ9ZZ³Ÿ®|÷ȲU‘Ñl!"1µ–µ,T–²\šl_”± sf,åÎn.âªæÛžföÜpAj,äcM5ÖRC]·g‡3¯…Þ¥ï&¯Šß‘fœ=ÚŒ‹êþá1‰â¿µ¸ ›gžšhŽA[Éaôݼ|ùI“»ÞøÓÃJ‡‰”(ˆ›ƒgùÅøŒ™HWm<k°N>êÙ´*Ã7¹IÖVƒZ`¥÷›Kæt#>Üm ±)zVhxåð’P¬‹Ÿµsm Y{€¼qFMβ6"/ÞK8øßØdY2¡Y2ëE«Ó›¦ïŒ£¯aëyE\OkKq§[|·=$VÞÑ.xÍÇë¼ôò   þ´|#Yl™\·Fî3N¹íŠfžšhÜæE)»'Ù>®g[ª=`‚úiJÂÎ÷Ô'==µƒÓ9€r4q"‹ä®! <{%$T÷èa]Ö3L™¾ˆJ#h4î4 ˆ ‹u*ÏÈêU«{A×tõðp~«§=ºœ¯7oëùWÜ£f f¬ oîþŠ }‚ͱ{;túLhúlBnºë­°ÄÍÊÉx€ …´?ξQq‡Øc6³„®*2AXœ¸lÍ껽øéÈ`r;ÿ è9}‚Œ"`’e£‘ÙÏB œª2s|©´ô£-‰ÇªÜ¨ Â/¨ÍR@ð­Ì#Þ®œ•†¢è“n ³ÜqÇÏÒí±¨šFoÌL_vÜ2—®²,¤Š4®È‹¨¹…/·"iW*ÃÔ¨Ð-Â[MT>\R]dìвÁ.2P…‘+çÈm:—¾«¬5¹å¨ª]…ŽwŸÅN>å[÷÷yº .ˆ  µúM€.f=ñË—¿MêÉ–"îïà¶'»~úý²“A,e× Ü—~U£^·.$õ™ð´êWôhÊ LÎ-0‚‰½÷iþ—áÏ;™<ê™Va³š‡ªöqç°;蟩€_Ö#™«âð±Ul=S®;_×ìÔ`Îûp&¼¸Le‰C:*4/ú<û'ä¢GTÞ^\&õŽ×Š®¬ìíïÕ”¤ÄŠ)TQ©œ9pij|Ákùióka…\x æ·&çÉôŸÑÐJÖÑPþrIºQšËØOAP²¯.xbf&+#:Fñ ¨…š]PÁR¾NwaalŠpq:&tF“aŠ‹&¦vs™^É8’Mèè͘0\ hÁ>ëfsU³…”’0\äNÿG+]nI+%ûX¨·õ:éÕÈóÎ *`MV —à rþ)²m¼­J1„°›0JùíùšR9Y§ƒç¦“[¬èŽ(¿]´.3&m)UÅ XVdpdˆöf“†…»‰FØJ¾`šŒn/„n úrO"b„b¥úÝG‰@™«aH[ucM_Û‚‘Ž$ä Ñ‘ý$ÎЪçŒ.~År%R›•csj”ÒbJ ;D“OZÓÙúýCÔK|Z%]Ðí7 ¹Tâs«ÖÖ›1ÐMsfÁ8RKå gÜ£ÿzp¡°Jòµs)" Ü.#Naÿ2'ÐÙ61¹†•°ò*cnŒäVXFtÂÅô¤½Ï|……HӃǂ¤Ä1K¡ïæÚ›Î|‘Qòž×€Î]þgº»FÍ9ÉïÆ3œZâ³t€]eù*¿ÖZ[œYÎö„J¨XzŽ-0ŒöÐøgk Ë/%.¨Sêë–ú#³•'©žäWKÚ@$< g÷©ŠÃ…ÚN}ƒÙçðì¯øwáü;ÕŸWMÙü; þ_ÃÐ:}]*cøwü=¿‡¢öõJñFØd®è·jËÄ#,øÒlIØ’{¦ ñc$ xIôL”2VíÀ‚î˜ÊÞ?âK[ÂF¸Ûâ¤À¸ÐýŒø{ÚÓQ¶W‘x­G ¾Å½#ÇQB‹ÒO7 Óóî ©¡•Ö3‘Ñ3]˜1u°?X7¿öl Þ%*àyŽÔçûDdkú žhiá~ªk‚­í ÙO^^ tÌ‘cÖêcÜç°cÞR“.«dáñ§ûMð³Ä {'P÷ô뫊˜³÷…nd=8›M‘’‰4‹E|Q—Bóþ+æ2d·]âÝ„†<Ž!ÿH™|ûžBgÞ²]õ‡´;K]Û§jºae@+_“ZwÛº‡ÚÏšÁ Ã$áDH­‹Îüé>Ó—.‘­ž¼ËÇ"ïUd¾ W*N%Dj^µ.•¨´•ŽË¬ßäBæZ48õ(É=ˆÿ-:l§êÐÊwd,ÌŒ¿ŒQû¿†£ÅÐ¥x)‹3Å÷nßÔG"&€tÑP§ûž­ ÷&¨&ÕJ¨®—|±Þ¶¦ ö8éèí(4Þäã†_Öæ?dX›¤&µµ^7ºgn­ðz‰³^è„z£­|VÇäÄz° ³¯Éj˜¼$@$ Äyá/%P*àWgÃ×Uw»¢ @JÉ8ÀŸàWÑTÑkJLÚWróÁmrþÞ¯7T”<´Ž ¬“ØÚ ô ÅÔ±è3÷­ìUøýÀŽNy¿@½ÀºÖÛJ%H²»rUhÍœ¢Bì*a¡io±éö–,‡u‰BA!¾Y/ëÛUj0·dɽ`Ñ­V}¨*ê*toÏ—$®c·œ»JÂkûA¨ ½Ö’Iר€Â6ÛG†õâ :ÈdBÉfÙ™H§NÂOÌNÌNµ Òœ•4•—0çA€"ð,Œüø3¢ˆ}ªÚ8 ¨™~ô`±#rø¯ïÞì—=f‰ÄÍê‹õö›çŸ¬–š/ópó¤OHß‘²ƒ2Ê-Sx˜-*ñu'œ«•¹÷IžÁ-2sf#Eî%HÔÛeªñ Y©TI&6Ö$™L7n"\#èÍôAó¤éÙq80dãÒôäÙj}xãbð #*`‘û˜ò~P÷¾hI‡ ÔS,EJEAæÁ`¬¿Åbâ¹ ’¦IGÆü%ÒFœ©bjÄFX„w0_ñQÑŪ´ >òº²‘%Å1i7<}/æ´‰y ƒdhzYÿ¦¼«Y:ÄÓŠÓ³”2·ð­{Ì"LËÃ÷h¦p­ŠM)Ú# Q­–ŠžAh®ÙCCŽþNeE€_ÿv~Пb0à HùÚ™¼Œ¤ÎZcØ ô†Â– i$ðiÅQ¡·NaÝ(ûVºâå2—sÞÉÀ«ëU8I¹íõ§k}„1ž¿¡Ò™1çÖî"5¿¤£uÆØ®Á'T=Œ9ãW˜öC)—/«–Êqeá¼ƨêîuXåÎØ”ëgû'm¡‡1‹ä~˜.›/]ð]:¿tÛÊ[KϬø%dSøòà+³½~Epuã—°T΂^ÍVCt×ÕhÑ[Óô¸ê$®ÞKÒØ-óˆ4wpŽqäãá»±ù°b†Ëñ 9:ïaat"ÙKߪð b<+Ä}‘êÚ`³°Á³BŒ~¨ê{GPC$?œy®W–">^—¥Q K˜ØI·î];!¥5¨Fûq0–oY'{WFå¾–¤¾q,FÒJç—·õ”4ª~K¼Zì‚]ª‰ð]ÚÈï&gèL$ª [ €ý;²TkNKI>‰·+‹B!+Ç„WQ[¯T$®FÅn‘Ñ\«´Å…խû-z*Ÿ1&à=ˆŒ‹'¾‚‡›,Ò¥ñ‡1¿ÚÚ’Ó2jM ïÃ"®H2,Œß’î9Òg!DÙ*H¬=¡Æ®ÿyH‹Ò;Òjn_°••º!©Îl!ìª/^g¼A¦‘(Áew±­yúCþ§z‡±¦‰Ç.Ï÷ÑÈ..ƒÝ[)yZ`»¾'qK?©mÌ,´?tà¸W I¦½$b{ME£X¶¥«%ç0¶q¦>Æ]ß~£åÁý2ÂòT–)8] ˜-(ýƒæK¡‡pU˜åðkìÜ,Öw%Á²nKÑH‡›hÂcÊôö„“€‡!T ‡ÊgÍÒ\ëÖI˜©ÀçCß™ã&?ô ÷õ-¨X?3†6¹-Ç!üâ4U–lÉkªe—ê´° 9¶Î'QR7Vö—óNvt{áX»[’/ _ðÔfý"x½l;lÒ•¨Ëh&R+¯‹À )3„ª!±ƒíÇÇ•à‹ïj¾ÅPPÊ?k“›³`iã"RL­”g†µ™²)mšž‹íÒ^-­cv7“çfÊúê—Èí½™@eÑ#HNò±»¥˜IÃÕƒ]5ƒGà©ó—V¤åÊ!‡"7.•9h ±f§±¼ /­Sů^Z$“€ÒÜòUK‚yiÐò{Çàå =®±òûFïåªÒª‡ø÷ÈçxW¦Q¢ùç±Zø$-`\iÊèt13¡ªw ÚAU J•Ä:•ߣíä2Ò¥íÚìËžÑø´:õ·ïª3 …º4Zb¡äÝqøÊع•ï#|¡tZ¢cM^ár'"S†ëjýBöºÄb¤+o *hm^ZõÖY'쿆›Ø,c‡[íÎ !’½XB©›Ç?FMÅGƒƒVÚü%2êáøÏÌ<Ñaú qV'Ü–š·¯mR'=(„æ{³'mTU\Îcµ*þLÑOÜ£â}Μ5ˆT<·J~f8÷O†ý¶µæ^r q3ÿPqèƒÎ‡Ü¨™\ÁÆO!»*{uT1Æ"Oñl²Îya¥÷˜Æ&\ )V}º Cd3¼ÙpÿM 0»ý¦ e;ï  ‡ššmÄJ†{iì;¾´$É®÷P´}3-»d›‘Âdá<ÅEc⥞0UöÙF>ˆf³G’¹s6ïŽäíöQåÆXj/`¾ð©Û_Cu^’tBâÑkƒ“Kù¡ç/“«U|T:ï­{¿}“;¢§¤e·ÉØA¼‹ÝlYn†,Xôsk%ºîw˜§’R·×ƒêº>A=&o0÷åwʺ]m Œfèš6‚0NØêXgN ŸR¹VŸ˜ä;8³VÖhKuÒú¤Sé±”ÃÐbk¶*Þ9ŸóŒÚO‡?pÍžÿDQz7CÁO‰I°ðqç[i~ãŠ@°8Kà€QEˆg%¹$Ú„GŸ/ßæÅž\«<Ѿãð¯Y™ÛÅ9Wê ðxkA’(7X7h+eúDÛ Õ| 3øýg‚”=™½­ñ@Y"ô³Þe5Ü:[\¼G ÆQ%„;¸2€ø-'F»Ÿ•¶›>çÖòÂçˆcTV ¦ŒÔ£–ž‹6 Eæ—£+ ¦Ô²*ò†˜çO L"8b=*䬈.¡bô£I _ß— VÝÀwðw±Þá_–6k!Jg¬G²­žxg Õ/­dÌ>qzç»ÈŽjI‡Lµíñ©ÈÚpjxp!=*ÜÁˆD{û"}ÈO‹qn§ÃÆ8Ä㤄8Ó…ÝÀ¤–Å!HÌ–¦›ñjph¬QȾ´Fƒbóä„Á˜Óg Ój*¸ ŽbAâÓ™öòð'*Â'}FÞÑ“‚ø¯€Ü½gÈ%!¢å{KG­D(v*Ý'6HRoÄãƒñ„±%VÊ"dÞ,Û6Ï9|JŽ1—tðÔçDÕª6†¼"K#œa¿¤,gosˬ‚‹ =×KI{Pôw°;sn†I›r¿Ì+öDöo½¬“æ ¸¢ —~ÕvIXž$’ «¶Íd óþIÙ©éöÙ÷ïÊbË1y*ê­v X¢–wþ©)ÀDw¦¥nê„3U‘â¶Ñï´e­TW^ðC)…ẃRî·“H1Þl†‡|!ËÉËp„¦ŠÀ²ZÃHpI•޲9Ì:„sˆœ6Ï n¾ìlÇñ–d­­v}CŸ¹SŽ€{(*,uãöÊQN@Ò`e><86š[ ÌQKIÎá:r6¯™¨†Íø݃NM¾@R¢È‚¾`“R3¹G$L° ’?qbébRDÉ>¸‘„‘6¬àuÁGrWN· ஋¤Üõ‚¦Û¾Ñî:wË Ê›¸YbÙ—o/Fxb—”&UôITò³ÍÄ£O¬gl‚TãKj¦$ Aª+DI³ ¯—'™ó÷¥Ãá‘™!RçC×w¹„¼Fžyš5 ˆÁ±@¯«MUÅfÙæî0ÞOƒ$9ôÇ7³[n¯?“"ýíÒ’q[$9 £˜p‘žËô5C|¿ÑA“UiÿIC`ØYÙÄs9Tayt4 *)Șð…{2wZ$[xÄ2™ïÇÐHu‘=7P!B#mò¤lÇ}ÑóÉf›Â†)îÆW¦ìá"ܹ¯P€‰ð¡s°M¾9y)ØÏzðˆ?ŽZÜÆM«Þ¨r4– PÎLP6F§Eº57×L†›1l–?dßO›G¢XA.Vx%Ü<£tì] n:*ƒNXлe…çfü#çøÕš jë~öHb¢Ç¡ßXC]8QGÞ‡~ñ¦NåÓ–ó3«oË~h×½T/9d¦!mBDCi^ñ SE†Õ:mx‘—¨+§ˆáÒ¢2Ñõ;*™üùÞ–g O©{øHmŠ8-ú»w™:»X½Fô4§wV®6]/®íº*;>¦Â°ÎĈ”¿05CÁ3ÚÂ$H¥èa.Ÿìï}`Ö ÓÑÇM£êŒ’p gA5V&à_8hÖ$HnuiÓq¶ŸÞnáÉID’JHÉKZÒuDíiPØÇYu>ðt mظ-O^´'H`ã úÈHPÃþyQŸ9‹üÙH]Nâ¦Â^Å™v?}*è”V:† “Vç©–ƒü]ÛÎ_+be°)™™àö}ç_f– «ÀË“¤WsÚ}8²óBâYú?º¥>3úgŒÚ2-ßA7¹×õŠ¡¾¥Ç•vé<Äçí¼z™°n¯;ñ@«–6³)bX„@Ô4ŠHä„} õFoU[´ë ÿ8dKƒŽU ånÃþFŸ`LŠ÷ BK¤š#Ò_]^—ÒNqu–%K×#Áx}M›w¨*°º„쇕[ôgöÄsk YJÀ‹];7ŸÖ'œ'WtÖùTÍØ>pxzÆQk4XÂ6Î{ÙžnZ¦ó³kP\ß\»Â³tÐâv…kº_ÿ`wÃÈ`Ùüzs$Ž-xû6gíÊóuaÒðJ›¬¯(Éè{e£ÕÜ++©ÉhçÒçÚ(Xvô< û#Ð÷¡×æ‡0½Ã¨Táè3 C¨å¶tèÞáÅÖÐ郯5—©†ÙW±Ÿ5 [2ÐlW+úãÆäñ;ºJÞð¿ï0曑UA1·šÅ OóŒdEõ±ì<ÑÎTM)p¸y¹¸Ù³Õƒ„ H%¿„üm;w…w´ÛlÉ“‰ˆvB[™­ñÐN0ôÑðözâ‰ÎÅŸÒ°)Xã_QÜb‹—†×(”WªèÈâC¸áFªöµ·¨l¯ ÷@’†ÔkÜ”äéÛ¾38õv ô*f+i àW„qrÂ\÷Š©c ©•Â;#·¹êà«9Œ.G¯µ³†,`wzLwëæ>Ÿ{á·£’úL K*rØŠÎeðf|–ÚY½AZ[)5ÃãAn›™±™$CŠmJ4ôH½øŒ¿V,ïiñ ‡§PØNšÍ›¶g®¤uÔ¤]'ó’Ãj©©Á˜Ëzy'6.úžV¯W…dO[pú•!@ùÂŽ±åÓ@Æö̰¦ÿEnF¯ZBÆ ©="ä=¶ž¶ÇpX¨U.¼²õãHÖ¡ ”BN‘F¦¿z*s…O\ƒ¾œb¬¸$G$ ±xY¡Ï’ãNFòÈÁ¡äOú)~®Í‘{v9ƒ©¥ÊtÔs“ÓR•ÄÕ5‰‹ãð=ZõkæåŒer"¯ëHégÄS,.‰a@'¯àÚc!TcÂXÍ&q+ÓlŸéW %~ú—«õ ©ê0ïO‡Yâî¬amP£¬{(.Ú¡S3 jÛ?#è^ O1–â¶á…°Á!Py g±•Wª€æp½o÷Æ@’›ëŸ²Ú¶&Àˆð Ðè” Ù…;AÙKë ïoYz½2H•¢t¶|1ÈJs…ÂŒ:Ôã†,f¥ ïƒrÑýG æ~CäÄ"%оGÎ{Fé¾¢Ô“$RÜ´ÇkÛ¯6èRàÖò¹F¼¯Ù]8Ú FàÛxpTËêƒbÅïk: z$êd©{Ë¢{ˆø7Žê9fxi·4µÀ‹ð5¥É×ͲˆÈA¡Uî j%òM¼¶9)|,zîú} ©‹jœ2Ì*Ä@Ÿ¤£„7ïªQ·Wë]hcÛâ}UÃeÜè3ðÙîXá+æˆwЮ[–<^-Bgrƒ3š¤$Æ¥A„|ï£åŒ6x£?žâÆèBÿ!Rr5„CÅÎj«m’Ë•W;k%Vp@§:› ³?ìF=¶è‘°9.ðìómÐ!»&O¯x¡†?€Û¤]ë·ó­ºbµ‘ÁxwRU†0Û}Š&¯ïÄ2Í‹ $WÞÛ–›º˜£CÁ(æÛ»àŠ&ÅÈQ7ôÛŸ}þK# „[ ÏÚ«±íƒÚÞ:ÏK ˆ9ê:Že9øǺÑë„ØHãLS¢Ì`¥¿’ñÜj{°•ÎÛ½fu„ù:ÕuÿŽÊÝù¾l<@ÔW@á:Iû1I«7!¨dDm¬÷H=cÅɼì@}²öB¶iX– §,:]£Ÿ9HùR bÈðóÔè¿JG„na¸Õnûâ5¯µ²—%gàõÈñlÌ€¨Ó5pø­‚JªQ=Pe¿Þ× 'VÕõPNªn N‡§8hŸk7-S€¹jÁãHäÉÙj¡ˆ6¡ÁrbÌø’q¶sß’¸™þg•.©p†sÿLÁ!ý³–®5n£®Hãc= ©gtêm…º7ž}„Í…:3ü¸`KfÍÚeà ÅÚ¨æ÷»8Ò>,ŒŒ6µ™½ý–ãu´‘º7Â/™n£Ô‡¦WË2“¹†ÏõŒ•Q„]Ð~‡¢3 ŽŒ•õêÁ›Â¡‚Lí$/õÔ´9^Oµ°„ÔØëFeZ ²ÇÔCÏ=Q䎱Xú…õò\ÒŠ/§á^C\<³fÔ…/ÝmÝ/kpΙÎzÆ…ÐR+¨Ú`ØÙñb~À*¢V:Ðg®úù9h¢,qÆõ´¦ÊDš³÷"ÓÝ~ß uþ±ÚÙðwBâ» Ðdh©F¬x‘ïÕKK1‹ ñÉ/·âG´EÓ3~ȤƒˆòuºaËn‡OåNl„ãO¸g°í»„¿„-»jiõI<—sÚŒÃ! ©È›f·Æ˰ž"ïQ‡bûÍ­FF…UšȪ͹㻜h†£ý*Y\(_ >Q6KË6€¸¼m·oJâçˆH§K䜪›T«.½{Àݾ|'çK®Ê¨£Èm^,Þ.ªµ÷u~àùÃá«-8†6«qó„Å(¨ƒ£=¯6¦Š»lÓÞZ˜>¡¸ó–12Äs{Õsz|m’¦3Õ <; Ž›'¤sòÞD‰Å ñïˆwÙ‘b«Å0O•OÑè lj—'qÊ7ýY7vÔB—`Ãf®0ž‡&©ï¼hrü¯"0ô]Mƒú ‹Ø;à Óï"×ÚÚãšèõ´KðiÚ8æ|ͤ§ÿ)ÿKš¸ôÝã¥ly¯j$XÊê0ßè'žîz<¼HLü^X³=B{i¬ú‰ªŠÅ‰ÀjDºr;›ØäMœóˆLáñÓ(àÍÙ!¶­~šÐi–ˆU&±D{æJjµô3Û&§ü‰Oÿtͤ'Œ!“Ö’} 6&õ|íÐ&8÷// íì¡…ÛÅÙQîÈC©ËRH5t;h˜@2f³Í‚AJ¨—é° Ø«f¯rYûçfnµW¿úÂ.nËœl¦ÔîgЉ‡ÖØL̰ßUËè¶þÿ‹>;ñ”Õ¿ã Ž që,6¢‡-LEN=ó¸#ä[m"ôŒô7)­›(;2']ü`êG¸™˜Ýi—rÌJdûá‹q3)åÔ¨Ôj’i¸?W/Ù™øt„±kî¥A‹$7³}(¡²ðçJÚÄÁHË(QÆüÁ ™0û£žÁ­Ve_ˆžØñ¹Ê‘õ«ˆ7\žýí>£)–œ‹‚TÈgkB3êé{`Õʼ…û•áp¼ 1mâ]=}WuêÈ¥ÿIÖÄ6ìÍó{Ÿ?܃}ââ±IX#ynâ9¹é²©W|M"ʺç¨dËAQßp»2ÛõÄÍnPê£4NJ³Ühi]‚}¥m Êt.§Æ4ý@ml.ø úÌÁ.ôFÁÊ;Ê­i¦Ùæ‚×ùÚ¢ô_Ë݇9—VZÒbè_o—1'Áß–ˆ·û¶{Ñ«RFŒƒc¢€õ&úø­3_ÛcуÆ'»R&Å —¾§KËwŽ•öb\t þÖØ0^⵸܃³ü/@ÛZˆûvíB1í÷¢ì>Θ ùÖÒ×/eÃ:.úƒ{TdQBð–9`ü`|S>§‹¸ß .*gãËl}é—Çh;5ûÕ- TQ!x¥XL^Ãlš¹–žý†³ë"óÉ'=€©ÔÔ<ùÃÉ¡„Öh“Ù΃ñh·“ò²gгD"?)Ÿã?IÉbPêH8ßO™ºTEG”›|@³»QžÑdqf>Á¡ÿW~s¨uoiyß½‘­Þ¿~‰EW?¢ו½óNÿƒ>U~Ãùâ›k´.d*œzZíâxÛL¦²P. 3Á‡S €i* 8šÃ"€‡v-ÎÙŒÙHN´Ç>ébŽ·ö¢êiÚLEoƒ½È‰‚ëKз­bµž²öДˆªÙë ðâï ©ìi* &¨ÞÒÍ2¹”gÚ «Æ5ƒ¥+_“…Í|ž7 §¢bAùüžZûDþêò ˆ?ŸM¼w^ë£$nld¥ÿ‚r´óšË_}È*ùÀ-=… ÇT©ÛT‡ kp t<«Eõν€Šï¬ŠO¥óGµæÉ-råèèÜÉ—Ô"qöX¨Bµ]_¡¡Çð]KìŽhÀë u€¡8Ÿ¹ˆic²Š}¼IŒƒ'£X¥|Ç—ÖR~YžºRÙ(Ç ïÜ’ûâŸÆ%ü®è²D¼ß`6ÅYbZIÂ:„Æ“l¸«ÁÝ肨ê a‘€ˆÔPiR™º¢>ñ /ÎñL¬=«—:ýžéiºÇ·Ý Ðç;£´}}ȸ b¦àFÙöþ°aÀs]Ý¡ÕÅÈ¿ðœ-ŒöB™Ì>vÕ»<'nƒþÕê&¨ FóÁ0×[^AA`µ/¬®|¶ŽÅÁ¥„.~ö€E ¶ †ÙÕÄ–dLã™ZEððf0sÙË j‡ì˜ôÎ!þIa^3CÇɰä|e¯ €JÎMûª²õ† ó%¹äØ(V¾sùMk€Ú–‹·Ï·d¸Ù4˜4"PH ³Ê§áaRÄ{Ïîp5^×( Ñ/O[&dÀ¶:_ö2­S[s‹Ùóè Ÿ…¸k*Z¥㑬Š?Úƒ)3½r=W0VGçK.J³¹j¿¬`!Tª4ðFÝ-yíMWÍ:«(—#iº±ñÙ(¨i»B/8P^•ÈMn2ž†]ósw¡WÖ¤‰tˆçzEˆGóqIjc- ” lc¹2áD*ŽIf(쪖ξo§öò@ÓïoRœ¯t³ê­ß‰¸œ)½]1ÿcæwŸút%R‚[ ¿ó{WëmËÕ{RJ\ \B>åæ"HŽC´û“àÂXb7“£gºæZR.¥ée¸ßX{¾.ÆV’Š,¡š«´Áëü#’l“ åäg‰1•]« Ûmëh/ð°ü8èú·dyÚçýš©ÃzY®µ™TȤ'œ21Œ7Èÿ4ðM¿ å‚+8¤Ùòs_®•¥±tq‹3\A*"˜žÛp¥£ 9`<¹!”©uºÞBò8^Ý m„ü³MÖîÿ iã£9¸í) ­eKœ?$Uœ@Ä šÜUæEuÑ:Ù)ƒ%)±Ò굋µ[½»°šï<ïÈ>” cu÷]ºÝ.wrI\Jö}ºGjàTu”C<+!ã@©çÙ}—~VÐöõÇŠ‡žÕôvIqÁó²ÒñžV¡¼‰¶i£”î‡*p#èJõdðì9’.w–$+/¹=W+rg?ÐXé[m[KÌ©ÙLhW¹ôD@•¤ï2ù¸7Ú`M”=%KE¦¢ñä Z¯^‹Ò1D7qqŠ­FéÌ;]M£Ÿ)u ä3>EA Æñ¶ê=ÐFu%=[q;Þㆯ@®…6¯uúŸ¹á i…p-Kí k1JO›¨ Ï…,+ï0î:Ý:ü3n«¼‰·ùÀ‡›[o/yþR ƒc†›O'¬šwwñÅÊ _O­²¤F¯7¹ÇP~(á@TÙw>™ú0ûXûùܨd,Á‚Aÿ&ß3Ž-Ø&Ú¸ë›ÿò[«?yy(¼õ;HÚrªòò|=⃑¥ŒŽ5<%3A!Ek(lžX5¾,ºFµ†¼+ cÜ6kç†Âáù(ã€O9au¾¡ën§Kd~¶ù ײDO&ø.É[…p§x@„÷±Èä.—LUc|ÄÏó¹'7i$›y¢šZsÑei€ü”Ú\o«'¼˜Åésî,"î_–Ñí{y?©OVn f‹¦¨ÅpTí­2W;gFºD—zŠ*[U¼^¸”uiWw§;Sø#^a%X*f"~žq§†˜RÂrÿ) 5–j½gõ#¥×ãÕPòÁqom" íä“—H•'¶èÞHÿS»rÒ®{EµHdITçj[_›~pТÇ(?nîàLë^»žï‰î•ˆi™xC˜¢3cªœþý(Wåyée…™¬ïƒ– ¬Eƒ£ ¥Åð¯A»×Gh¥ëdÖÝ3Ï7„jöÏa %ûÇS]ïÕ,·P‹|h|LÉq>ûiÎi߈Ä(ƒöˆ]Ž×çˆiú±ê@¡?«î”w_»ð–5×]†ýÏïÝ6_šEx)ösUŸK¿Y(JýbàA˜É[Ó!­=c›lÞщ §ƒòü.„!B5ÇŸKÛjÄY/iWo#ûЇsOl<&y"ü½¥æÙ?ÚÏž/«:Á}KLp³ '~ƒE®²vhY`—(BzмâÞóýì˜^ßÖÙÞ³ØÃW“TRGžë¿ ~xaS²i²l¶“ÌLÓ÷ê—F!…ûü:ñË—¿MêÉ–"îïà¶'»~úý²“A,e× Ü—~U£^·.$õ™ð´êWôhÊ LÎ-0‚‰½÷iþ—áÏ;™<ê™Va³š‡ªöqç°;蟩€_Ö#™«âð±Ul=S®;_×ìÔ`Îûp&¼¸Le‰C:*4/ú<û'ä¢GTÞ^\&õŽ×Š®®Nc¾Š…qjžÅ}{‘ç1ôU­E„Áòéê~Ú€7Pgì@Ž{˜0 ÷M%÷ìÔ¿øÏ„ᎆ@¸¬-ÒÃVQKQ&&bb²0#¡ÔošˆY¥Õ cl>šÍp%˘‡A¸tܪÀÜm™ÖÓ€•Þ5²ÙØË·!öÐH6C2÷kï3nav”# £Ü—Íß$N¸,ze`§2Ø%çÁÒ($~kå õ·Öq ¸U\¯> ibTº9Ú‘\Δ½ã™ù.ÿxEÿO•dª×¥¼¤I¸IÝ~œØ¸~Z²Êæ¯Ð]Úßÿ7D¼gº·}ÿ!‚&+ȸU=Â¨îÆ©ÚRš^ó´@¾˜½CØɈ‘è¬*±•7™zéÁĽ´äOÄ;æÂñbqhîˆõV`‘Í1¢—¿ÃÂ’3Ç…Ø_ùµ2›@cÅe8™ ‡,B"„ìÿ,ÿ]À’cÊ/”“,t~fÝUʾ ò”¿åµRTº º1}¥áÐîLð  =+?‰zB<í€WÐ FU–þêUu^m=#zAò ‚ØæBÊ YMnˆÿQµ7œù"£å=¯ÿN]þgº»FÍ9ÉïÆ3œZâ³t€]eù*¿ÖZ[ž* ”ømjÁ¶ÆÂG“Årž’@$(°´:Ã.UÙP[™&±O¬ûrž 1z1M!kw7©E¾î_\]¡8¿çðí }]…GÏèV}[í¿Ÿ`1õvsŸÐä'wñü;nŸWmSêéŽ>F`ñFØZ»ýé¯#¦v“s²ž+–”clP´SdC ͸¾ú˜õ•GTФ8û¢LóЛµ‘ÝKž¶Kߨ¸§T;fT ˆ Ù²uÇ?'äOŸT±ÿ߃ñdMl'²{ãbløLETò Ñ“æqä4¼ŽWfSfR6±Ù‡É=aб³MiúSÞ_3êä(}§hþö ðÆæûÅ™ý= î\]µo´\&>A ÆŸ ™‹#›Ó£œ­-‹¯!¼vëC«×ˆJ”N#'Vó”Ɇ¤Y½_…/‘4ÓndxÒ!¾âmyüð9ù0›:tèD ”™*ÓJ† Þ2æl³Ü„9EÄSÚôºž¯ÍÖuÓlßôš¿¬mB /œ`ÛÑTVî©8Ñ{f1“b ÁD"¸ãþ…ø7&ãrKèûÈ2&³¼Â‡’­Á±@eöƒ3©2oU—HŸk$pSí¸=ª.8›u]kÞŠÒy‰“’=˲×Òß“"Y«jt§ù¶=GÖºï Pq{Õu„FîÞË4’»óÀcœà)ض] ðëGÔ:^Þ*Œ£Të”FÓ6Ž” RãM«µsœ–ovI“MV‡HNñŸäX"—,¬ ÚÎåùÖLÌa÷ʺ“&Ÿ±G0#|”#@ö&ƒÅ@wcJšª%?ͳ¬ ÎÙeEÁŸ‘–d¯Žl—É>¼E›0ê º +Úî´é'RaŠ“×OôõdÐEôŽCÃkýSQH”[öMþýÊ<]°ø¤[6æm–üÂ) ªÐp7DBñÍÜ(YŠ${%­2 ×j‹&ñ 2Ï\l(ÍÒñYÈÿ¯R˜ÕL‡Ú ,f$g1µ=K¯ ùsf}Ы¨Ø@à{m¾¨Ã¤èL-‡7s‹’åeb{}_M_êž³æaÃÄdà§9s * ÷jú! m§Å7P#&xÚFà׸(v*ª¯óùÆ”È#«ÔéÅl¤/Ê•åqì¡Ùdµšyƒ L ú”wÞwŽö€¬~yÃø?¾0¦Cn•4Û&°1àU¾~iã"¸YšÒƒ’\'¨¢¯KÝ+¯ Rß9ÚTØÆše™HÚâ¦h¶û~'õXF—ÛÖ¨æ¬áåf¬ÃÖÂôSrn[ÿCQ9Âï&5—ééÇ Ìeú t9Wù_žbèüå¥'¡‰âe“Ø|y,æ¡vmûÀi»õµ~Q/¦©õ6ÞܦmI1k.ÙÕɽ{+á]ŸZC׌d#rÃ(ñ¤FŠ  PZæ]«mç¹y³. 'úQã‘o†ÿ6õ*§{]·Ô.Ãq½Z»á-󡉕 %ÕŒ\}«¿é¬¾§{w›ø>²2§½Ó‚ ý‹[Á˜œw!AÔ…‘'Ú :õÎNh í5|ï;ÓaXë þ0ã•ÚZ®’NÚÖ>³NôXµŠ)qØð`<°_]L{ç6ƒsjÿ1¨‰¹Î×HC“7ô)bŸ>´ ÷´÷í­nÏÍ#Ou›î˜|~ǘI@#P&BÚV%îѽ£Se…¼ãqZÏú+éÿissáú6º4Jܪ¹4Kð‰6‡œšñ–a–E1&쪽ºlª‘å[ºóä{Â>Çî[À9@`šîÄ`a]¡Õ¶š¶›KC®$e#Foâ¡~ ÌÞÝ•nZEs¤2„ô9ÀHҜХH@F•”UkйIˆìË¡Ãà¤#MõÚ‘ŒM°ðŠéÔÀYðš çŽßaÖï(‚m_NÆ)‹/Ljoh»­Akrjsž¼ Œ|;ðùpp‹fj”11Ñ…»ÀÞ,ÎàenŒä<ÔB] $RKqIú÷½h«ØÄPòyŸÅâC O£"Å»ûJ÷;ý‚–Öª3›hæG·{#p™“’EUhI`Ê¢{§¼ÝˆWϰq=Ð[hŒiTZ}fÛ65$2…«ìkÌÖà¦Öþ*Àjåž"GtõÂP` k £˜žœÅ|3¾žÓhã9 ¾ÝØÄÍŠD½¤*¦ñAÞ‘s8löq ›n )n¿%™<þØý?@_)²tßB%äÏüX‘*´;Å©¶¹þ>MˆHëðΖå¾x›p›-`£ö{íH䙹×ÉL ¯•Mæ7€SÓ•%>O¥ö .Øvа·v·•¦¾H®&mçÓ)ƒþGÝ1„6•ð³³zj¢*“gx¤Å£y–ö|Ô©«C8„æ.¤I0,œc÷èBÚúZƒm ²?-éÍù¬ñö ºr&$íøÞ³vKÀ‰ÓbpSÔõt·†PýÅíªpŠð—gâc9¥eX‚ìR‹q3´Gåâ6(å)¹TÞ¼ç­/yÿRÿò2ÀÖèG Að‚Œg0¿”Gu—>‚ÝØ¬Œ¤Z£á/DÚ‡MêecD¬4¶RÇX}ž0®ãµ%°ÆDïsÌWÕ«æ|Àyd>û³·¢~ȪšÁè4è\åÂbÈ3 à{#ÀKØm6¼ÅÏ~æÚ£ZŠ'Þ5¦_Àð?ZÜ•ÁXÒÙävjôð­G²‚Ìš²_@qù2ŽÒ¢RKÙŽXòj7P÷Fˆ5•Ѐ;s⾂<¹~¶k¼´µ^1ÉrcAa#œ …ÕÁ!ùÒ—Ãõsvâ]Ø]™˜é/)}Òµ³4^MsA­“ƒ²Rœ‘jfØË8MjÊ9’üµë¡/öÌ^oZ~RF¡„BA;Ê;¢{“Õ†·F`ŠD6*‘ÕónvØê®s}ÂqÜÊ¢u/Gç[‰ÛÑÝ!Z<ÇöÊ“ ­–SÊ¥7gf›îÖŒÀk7ï øO?‚M¡ö­zžllù¹ez”¨(õ ¾žîXö)5㯦·Ô“ Ìî‚Ö‹«~¬N÷¨•`¯Š'sÅWP÷86æ#ÞJbÅ{Ä!–Î ¨j™Ný@SÈœ¡© R‘Gä̵û^ÏÀ-\ÃOˆ[©Á%Ú0*ÕºúPª|òKÀçlà+4¼mâ¨/ÎÏõ0N´‚/kÃÁ냸%§àÿ>A1±JT…V‡Oñ–of–T—°²NpŽ–T̬³ú…Ü’ªCË«–/FsÎùý°Žál´ïI o.öçŒât$²°Ò,µþ3”Æ&¸OxU«ú°¶ 4m¤Ò1u¥óKV¶Ýæô7MK’¤0r)èd':àÀlk©|€}%C¦lŽÆÑp¹Äæ–ï > –m‰íçݬÃMé·E$ÆÝ±ùPëïrMÄq~ò|ç‚d åÛÇ05ÿv fÙ­ÈkÆ“ù*‰WôÞ÷…3;-#Ùä @œÄQ˜¯ÝÏ¡Cg|ègo40©ÌGú5¿¿‘¯_¹J¬ãˆ°PÛxýüÄGÞEt÷)35sÚY8?¶@ú:Á/ÙRë9Úþ*FEBÁ¬ù3©Úå zЕ|/_Ó×sf3Ȥúùd‹ŸÎþEUôweU”i”<ÆÖçN–v«9[Fw0æ€'Wî¾Éêã €äšðh‡WìÇækKo'zìØ´ºœŠU þRù᥹&€¢¾ãXw™»®û}KÎ$È{ÿB6¥œ4Brtv€GÞV  3Q!´´IÅ5cL7©NŠÀå-iÕïOÛ;˜¾Ÿ™Ç J°L²ÃŸ2ܽ.a—K‡`rÍA“©Ôz›Q0ªAC©ÙWÞ”_œj,ÀáijÓGSŠ%PLYò¿ì¼æ9Þ©ñe½F¿ß´ªl6¹(ým‡/bó’­!Ž(­sÜK•›ÂÁPAŒ7ÖÇ ^s¬Âçž8ް½@]^lÒãõ”5}޾gcâ͸5m©®¾ƒYðe·ÃíMº²#aݳzÞ¿„X t1“Ô)Ébƒ,¯›ÿ|Á  Ý¥B6ƒ•™ŸS¼dÐn}ÊEŒC)¶•‘Í eÿe,^5 ’8“œþ,'Óù”×máFUöQ"-Sîo ÿ^æ^ Bþ<?•{¡Ñ ¢—0Ýv@…È6‚sÞ¸}Ÿ¦/|Çô³±÷ÈÖᜋ *ö¬ä XœÂù&–OÏNÏŸ æû@©ÓÃHW{¤iÃõ‰¯Ù¤A¤H9rci`4Ýóó•µ?Ýö€¬ \$ê&±23XœÊñcˆ¶Hàkßid]^h²MÜÄöÆEha’÷p›³YuYÂ.~±çÒaŵjÎEÊÆÂ7âš×Ü‚OeP~Ä}<×ÙýWèK$WGÄÂjç¥chj» >ú(I¯šö"%2óåÅ¢@ iÕõÚÏH$»­v{ú¸“w¥ÈÛX ©*h¦Õ‚ „Œw[a !MÐAðER1 Õ6"ѹ‚Eº‘d~[¼£—O»÷“k±~6@ Ê3|J€ö–|‚ûÐ3ÁG#M²€„»h÷þg»Es3;±ÌJaQk$Šz'm(Þ2ódYTrÿrŽagrÖ öÈAl™fN&D/øÙöL…W2™zË÷ñÂfÕëûQEâ!´#C¿‹; ÝÄNŠnrdœŒÊ„`ü`‚3*Zþ¾y ±—yX,áò±Çñhï8i´e,º<¼›•>«ÜÊáÑÇ [7~¥#-Ü÷Áne^\öóîÝÛø•œ±éiÜ ‚¡Œº—»wXRkñŽõ£/½iÄ“JÌÍ%5ïôµ†•1|ï>ÃdzÑhÚÕ½³1YF”ö܆:MžìŽ5²à‚ñZ6ó.ޤÆ+œM2my•ñœZ¼Å¼-Ëôô&ó„=ëK7ª_JµTÁ*;¹C}ÁÜö¶*ÇiQYɦqÉõgÍäöNRÍÅÕ-è ½¯R!mT+"†z–5©§‘­o…R–U|þÁBe«c<¢¦o,䇚 $V<|œ‹©›Ka¦®'P_nÚúÎCãj R®·Ä°ƒ_<ûØôZ‚­"Ì¢í'r\žÀ£’$ámYÉáq!€önNc]pU·Ÿ”Ó'‰WdîTZ´òX2‚úÛ΄5ÑUÌvç·GÉy¼Èüèzø3zDÀ¬üK&^ô!lú EÖÒ€Xfa ¦öD°šqo©5êú›ØõÍ(ýAJ¿Vô“ 4’UÈî0”d7!ù²ylîF–н’ƒå ±ÜÈñ½”‰Ì'0 ÷뮕wߨ.<—KoÕ&Ô‰EóçUSˆøÐªèô©ùúȆ³£QÁ¿Ï‹zõb­ e•H÷a,ÆX¾'–´5K³ þã0rà<‡-TùKoA½C?övf£3Ac‘£Å¶2NôÏeSŸ‹²L unjžè_%ný1(h*KØÏ‘,Ìí6 ÓDßÕ6.w+¯±…Ѫ¶XÄÞ÷, Ä6$›÷Q†R‚)NUÓn¹ƒ7Ï0· ›q WÚ5jË¿ÎHŽJ{ÉI*[ÈÜiÉmóá¹°á– T€²P!R­¹™;=\QQ4ó~|î Ã:Û’» ×wcT2ÖŒã ý…… ÒF!K†—SØÓþ(³J,*¼>Õ·©”žÚ&+Ô•%3hÜù% :¿xƒ6ÎUbØ@ô(^Ü …¾us1=|šÌ0ß4;a[:³êVSÍÄ_ëÑFWGëS|ÑÕ/vz‘ìü:ù5ÊwQ’Ë +«}]i€cï—ϹjõÈR^r–3ßE]žÑD¨zRÓn!åß[¿^N†]c³©Ða•¬\<ð*â±Úly´žW/¾¨™$çHñJ,í£ÿk¬¿Û¤Eä ]'óÈÖd°K)%ˆ1þyü'·jªöŒ¤wª|DöÀùòúZ¹æ®ð@æX¾x¾ªÎøWâÑõG€}¹ËÌÞ -ô5ÄyP¸Ë:?6\#+*¥U«Z­Ìÿ)´ œý SÌt¶ù]$PKp…OŸ¬us§)E½B¯0DYž§ÓÁ¡½TÏZ–‡#úÈ„,³DÎ,}îˆ$l.ƒ¬×Úø*ߟpÍâ2kÖÚ{¯EÑ“\By¬´¼ãŠqaM`·¹C¡a‰h•_|.~ilZFþ=©ÒÔgºžöm.JŽu²¬ãuN¤!ÖZìŸÓb’t*xkt2ÄM}6 *GI²P®o,ãŒfÑÃH9d:{Á„xaV^%Ì^"ò¦ÐÚµ­‘L!e<ç7[ä!C¶æÚÁ¶;~ṯéÄÿ;k³V’{m»ßåNêÐ-¤n5ӆ×è味Aªò”_áßìÇ“¿ýŸéÏ’?XHÛÖ†ÅÊ¡tn¯‰¹ M‚ò­wêI³¥(Úö‡gÓíX…FÑx.Cù}û« xozb3ÙÈÀ°‰`Kxo%]ï’56Ø®¾$±€Ožy®aFT˜?ð›_·æ)Þ>º*äà\TòõD|?õ,Yh=ÇaæKÓlÕ‘ƒ,tu*2üæ‰( 'aey1ýy–Gˆ ¦ò•°0íä {š.?µÃÑÌ}ñ”ÕǵÒKaغ˜ìëÅ>>(ÝIÛ¦1ùãTG™Î,ð·§æˆÌŽš(-/¸¯ó{àŽNœœ?+x]ÒoµéÙð—±«ÃÆÖƒö¦Ù§Y¾»ˆ<”?Ó ð§qÈãke´È,Œ×ô¼÷ Õ9;xBÒgMÑ"¦~5ë&Ä«Ðz`Y‘Ø^Óç R+ØWèñ8ö’WÇùñ_ìö¤" >0ð™/ªG ^í Ú¢Úuø{ž¸pïRÌrVü>¿$¦êþ¼x|-©A#%0\¡€Z„оU8a5ÐɸÅt¾ÈòWù…5 Ð‹1)}ܵÜFSL\`lñ¾JÆ'Ó·„9ו¢дW~±çØÕb<})R¬ýÅŸBSTžÚ™ß½Æ3ÿG)Ð0esCfoJ®I²,nysÑ®‡k+q—‰«N¶uäõ²]’/p:I5ñ=¼j5~lÁ ± jØ7¶;VíÎèŽ åØ+¼_¿Œ ÆÔgØh¥ÄÂŽ=*h‘¹oÃ̪¬ÀN£ÄÓÓ ý ðá.÷öd¨±Eì] ¹\nĄܚBJɾCÇÜ3mÊ5Ô D„-ìêû ½JàÁ ‡ªcîF6ÉþµÐÈäL®+¤úT}Ø¥ƒ¶ÓsŸäo ƒJ ¦ÿH:LëAèa'9t»<øð|VD)#ÉI‚ˆX½žšœß^-^ðYà•î;¨Ê>W]ßå´½ãË|r—.jU -ï…^3‚ÂØAû¸d@ÒuPkÿäÐÀÜR/%áª4ot~#¯¾Á0³UþW{Ó_«¸Šx$‹|Ãüļ]¨oÙù£6šz‡‰SÂï¾ wñ’ÝÍÛ @MÏÌk/W!}DÓöoœ¢´©™¦t]L¬‹ô꺫Åý`¡è¾F¦ªóÉÅšŠíc™5Í!Œul=ž–/ ˉKŽ7¸ 5#-|òVÒsë&G¿ó†Cíâ… a­¢Ù ß,JÊ“ ž96£Záq÷?Þ3g< D¾žkß©6™†µ26ÁôÙ¦e0URö£T>P;ø‰b%–›I:¯qEþS¹«-Mð"’ü±øŠšsÃoìgOÜàÈØ>Äú²gú´W;dzÏÔ/Ì·´Óê\Ò…–KƒÍibÁF*!*cð¦H„¹µÃó3€ZÏÏššÑô±m'N{µ™æ*7¡ŸöƒëòýþHòáªU+¸9Ìšï‰æI#çÿ5pŠöŸ)¯>š_jû¤f‹õ IE9ƒÄ£³öZ< ;’ÔNGCj¹vyxm oAj´Z4Ȭ™{7P^·NF÷ÇÁM{Ë*t[ä%£üû*B° '𹫨eÔ=‰Ç]Ö32 `ÿF5ëûZÕ};À”>0г¦ãøý±Yv™ ýÝðþݤD0J g ŠÜU˜vg¹Tçø™"ûª>`2§# Ju#QªlÜ^J,³ÑÑK‡ ®¨oÇݧ‰ª4=]M¿ =ñLÎ~Á0ƒèBá„'$Ľ Áù>tÿ5¨N ÷¥—ƒo ×q[H£ m ]Ÿ—ƒÊa3Z Û–"¥g–¹Þy;FÊŒ9Áã<§]f4Õ;µÎ®Ã&Œ÷Xm½‘Ýøi‡ñæTË)¥+tLkµaÃp­Ú»Î ×1x`ùÆÓM;ÉÖq!9ÎóžÝþ3X »C^; ¹'56Ìj¦}N¸ô9AyØí&n¯á"€ÞyR</7uNö5µVTQfÉijr;í¶¬Ä} ¹$o·[óÈ>‡Mº”Ð lt´Yâ¥ÃÕ”úí(ƪyjÝL…·¹p®NßiJÿ3pŸ¦IC€íW¯Mß©Ð5¸éHò%É&Äà~€ܽݱ˜âI­äW2õÛ›ûã;éûÍ-4ì?芜evE,}ŒÉY1V¹QîÇÓÂn­Ht‡úJÐQÚNyOS‹,è)Q¼£1$qJÄó^tæ+ž¼ºü†»Õ]–vãÃì&k–¿ƒ#˜Ñ~w÷Ðr§hö˜‡ÖGx/ÛÁyfe:ìªîÚØíŠêãÉK3KÅœÜã¬lÆA&¿¡ê-E¿´Ë>x¶¿‹ÿIÛ³tKýf¡{–Òö&BÆ4Ôe=¡VlÏ™X>¥/4ëç%ñ¹´ʵ ©ÕÊ.É6n•ëÉ;¹FÏŽÀT¾ƒ–"É=7‘Åa¡Ñøi6^AÝ?-2¸»:ÁjѪ{7#…4«FcÙ¿íœiê<‘™ ¸¼ãúÿõI Ÿ@I¸f†`Œ²Ð<·%ï¢RÊómj£+q:dÏëaúnÄ´?Óyìðeu¹iÖ%˃*—&‘kU K:îSH .g-UAÛÙSåì$®¡¨¸l©Y¦ ¨ÁŒh‡¤£Œx«ü|Ka,3… vÝC9Hƒ˜”ÈïµDDáuWa@5ò¦K}Þ”WÔ¯ò¤¼ÙÏ¥bÿj8p'd1k )r‰Y‘dûùÏe¾ÕÏüE Ì7ºo˜ba è XøŠ÷“$Ò¬•îËÖšú&ñsÒ•§<.d®ñ0Óÿ5d¬MÐlÀˆÞ€Fw–SE,¾%WРSvxDfLob“7]L„Ù25N°ª}¬À%ôíÀêL,Cd£„óÑÈn¯Q´†É-ßÄu5=—=ÃÚÒãøŽåK—àÀ­ôX9ž\ „ZM[»,7ù†À§›~%2ú>!Õƒ»Þ}¬Ó?§Ÿ@–•ÏpÕqÝ‹z/.[ñô‘ŵ‘чh@c¦øŸ~o€¼õè-ò‚ãê¶®žÑN ¡îžUÒ]]dNß®¹Ñ[†ÇË0¾S5£Û;_ШLÍtKÉ!; ¸¢P}8·:äßy9ñ¥Š½¯ÌÜÓ¼üÛú×GÛqäVëÄúÈ{I*pèVNb›)=<ãæyš®b-Êc‚qUˆe§?/fÕ^ösP•ÔÀÏwõx3˜€;!×Ç»(èj¿L…)b¸ J+F“é R¢äúˆˆ?„„SºŽ4;:é¨FÊ_Pø—TNše÷mpcß¾ Äú¹9k I.$›d.]Ê­Èÿ±dZí4Ö‚—ÖB€ÅG³Å‡÷ pˆhŒ3±"'¿¾nê±[^õpI§uïI¼EJ€ÓOËñ´’Ín+XV>„ÕW艕cK­¸@œ Éëæ+šn‰ËÉÐÎVgot= ¦oa{'kŠ.ªAå1MTÆî¬ýTÏàD¾K*Z‡Å{œ4ùm¹wJ^ó !íµ«Ï&äÿ.Qå žd•É3dmÆö'mtÜÀ·ø³êÏ™bgjkfOµ£Æ«R ôÍõ¾Û/aib´Ò[‹©VIÓ¨–ðŠND†qs”dÐMCÇõê}w3û¥ëI^Bo- /# –†îë]O•²ßéR‹9\Î)µPù"$k´^XÏuôs6Ó†a˜AG¶Ïì’ÿLêç¡v$4ôÓç5yw>¤‡,Ó#›ê¥½á<91¨9cÐvu½mÙÀ»š@`=ÍÐ&(×·yĨx—ÓñnT×i£ kl`Ó»\–‰¢ °Á8®õ–“(ÔÏÑ»ØïV@ŸôLýг›ò„U˜grcǶ¸m?“MŠÁ’îBu¿ÞäØauÊ~øøj:{ÑÌzd4âGqW&ÖšìK£X†õ .йÝãöhwíèöôûtã/²û¾Þ…ÿC©»íÕü_eõ>®‚ÿC§[íÒå£&ŠôKÌ–à]Æ3ŠFtAåC-ë,\µµoÔý¸—¼È"!C]_þ¿ØäoÓÕåýß v²{·‰ŽU—Fjèè°¢hÁ¼õ ‰ö;¸ÿHfN öÏ1 Cœ•{Y BnÑO«^x|`(É*à–0âãðå”Ã>æ ·ÞKÔqc|zfÛ¶ðñ?õðýÑP®ê˜Óc~)T#ùˆZÎ62 Qš™ñ,=Õ…‰Gg½ý = ¦%¹óAƒOZqÊ8NœÍ‹®ÔÙe‘ãaGñÀsé»sïxp]“yNgm)qôzŸÜ ’.Yõ•¹ü£êÅJë²ýz j¬ÊÖ¬÷÷‘ÚÅè^Ÿ1^øz]A?wŽ(AƒS¶Þ‹üU,pè„RP:õ±²«9äî@TÌþìá4^ùüÀ1Ñ]ʘ5ùKò2æ‡Á;ó<_Ï¢¸­9fbúÊá+å7ññSwøüÑú þ^ƒø¹Æ÷³Únä‘3ï²S„D*nËìV,¯‘,¢ñœnª(ì¼ÔVwr¾ªø±I*×Ò¸l%Ÿ. ¤@nQ¨±"‰å*x€æW!cÃ$ #,Ép Ì¢ÿ7ìý£;ÚWiSÛ‰ ½p‹š/«I*‹lk3ó[‘HÃÆí”m¡;``ãÚñ§/„žúî8R4…øjJý¾ªxß`¹Dó’aMEƒ0åÐp 7 ·ñ[Ä…Ü÷aç"MMš„ëEy@ÃÕsÄû:p©rtGv@»YùrÎ9ñ*°FasY¸)å›È;gÂþ§ÐùJ4—·Ü üœL&0 p3ã¸#†œœÆî¸!Æ#ŒCÑnš“,×O™?Ïÿ-AõXÃõ¾×1”ãÓy$œq\‰œn@Eµ1‰»¢Ö蘆£A¡5”&ŸñŸ[áµ–…iÄ}ë°k\§;e<§F¦ç9ϲÞÝ`âL¬ÓBo#®Ù³¶—4 Û£ÁÙÆ6 d&Ÿ—EB² ãb6¶¡½?ccš-­Ó˜å"RÆPùj!¥Nj‡zöðñÌ »¢dÒ´ãqŒ¤ºlÏ~ÐZæAüù¹Z½0ÿ„§÷ªo½úí¢E†T[´deŠŠqîÛR-^ñ~O–+AX}¶@€GÊf~PÜŠR¸Óbë£b:‹ *K‹Ë;U¥À:¦°sãå%{×D‹\ä&ml×ÿ@TCl¢Ÿ”/²{lë¾®˜µF¾Ç:‚î¤çØ–YSùÌGH&XÞ÷ø¼4qãâ^hAÖÑ-æ¦J±-V_vœíØBº¬j¶a&t“U6QO*Ö”<|aãR|UGG'áõgŽqÎÖñqièNþÞªSÁ¨¡w¦~VxŠ,\ìæoi¸úÍ®fq/#eCkáD¤PáQ1ô)u.ÉV_nE"fÒaƵ MøŒðu¡½9çÏ#Vœ¼AÊäÀ2ÙY›ÝŸL4TU·äHï)ƒõ ÷„# ×CÁäcNÊJ†H(ÒþÒªš“!ÿp^?ôZè)Ž Ÿß­Ì÷ÆÉ"_bhÈŽèlmdz˜9å•pÒW¤ã¹‹c#gîøË]ù>µOÚ×b¢óêÏ–å.Ì?Õ蔦ƒ¦¦nÉr_ÔöIì/ù¸³#iã:`€újÍùHñ˜õ“ç‡jœŸº0‘.ô5ÁûñÚ)± êX%”ÓTUó­Ò.O‚^(Õ6¯–Âà½o–o850¯ÿ'rL"0oÊ(|þt8ÀY<)öoU‘.-Ýâù3¼ŒÓ\žW¢sH‹-ÕÖDŠB›“Ý8¿o^1J¬~®ªbHyë ¦À¨ÅæiŒ ç߈o¼ k¨ ö¿ès4QCrZh­ çOó¥ž(j%i®(>%ÿPe+’ ¸c[JÒZÞ>Ð,åA_O†“ã2Cæ´Jϰ ÇGU#í q­äŒy˜‰ ãó#&ÉÔSï#š¥,‹­qºa<¸Ú}!|!x˜µùyÜíB…—W˜š¦x%ÇB#ÓÌwÊåd:wàÿg5rï¸wØó)ðêH@øç¶¥š&= ŽøbcÃnR¸Ç9¥¿sõËgE¦šývÉî8øâYaÓËV` ã×tKº_z–ñ`fêE²Ù–òPÒg)´@íqÜížg¿ã™î—ì ÿO"L-¡Üø,õIîYÈ™¢ZÑÛ.}6°îìÝR¦…c“WVŒÐ|5í¹„õ1eiÕ¾vS/^”{>$`·lü]úMºƒ=ÃßÙ@y Òç²JP Ú#÷{ìµx]Ÿ¶Ú] ^¶CoÄkK‡Ú¹3g'±m²1áÒSK¤qå圹‡þ†Ð€L±×ˆ 7êêïÇîG±*ÐU`DŽZƒn çÕ=ćr0Ü´Û¬½Œ¶R£°÷<;Žz|àú’²ÏÿE€Hê£$ì³×ÙŠÉø3‡G'bLìýÓ€è­2Rz££@ 5LE¿)±ŽnJ’Ýtà7ºÓÄ?¯`¿ºÐÜg§áÊw¨²ñôKýk¼R‡kÄ| ß ¶6R™ÌÆ96±—™lô’ÓmÆ‹ËwqCªî¬y,Öõnêé8òëE÷M¼—¤¸¨°ÚÍ7­5eZiDæÓ*çœqÝ•)?Ìô?Í€ì{®kCœÄbXÏt=º ¨ßÌQ$>ÔI;=>?¸Îþ›û0FôÑO:\M¶z7 ´Õôüb~E3m±Ò¸`´‘¤ñò_†¡»TvD*n —^Qz6Áq@šBdóãg+}LºÍ,S¸9Fó >¶8óC›NR]ëË–|0ûÝ`be_˜|ÇM‰øº<#â ë¡°‰ÇbB ñ*4±Ä>bò(ÖÂàçrP„4ïmƒØÄùj´ñ¹0GA~83ù¡^ØŠC •ÅÆuTÁ¸£hZQS•»9³%Ǫ6ìHS“æ8 ¶·W¹¤£Μ“H¤Ö‹Û¸tÁ |\g¶éÞ” ŒX˜OÕN>úñûùÂZúw¨»ÈìCâ‚÷˜¡¶bSúª5°pMô~n‡l¨îs„„ïB¾(ºÈ¹åÎþdüìD’a+ŸÝšçaÚªšmês¸ÿpá„KöÊB¸–|Â`?0ä÷'ƒ»¿tŶÁvõ0¤t@ÉþDídj'’B¿«*’ýzHåÌ—8PuÑÓöàcË{õË Š ÆéQ++6œùm ¼ÿ|­ PwàXSàÔC~Ýûˆb–à7ÇDl×2\eÓŒtµ‡¾Uv•\“š'«ü¯Mò”+d߬ò½—õÄÙÅáÉuÖÛS`óeFsÕž™33J÷j—ZT Ö’Ç]Íx ŸÜVEó¯/ž8ÐOmï˜$-U„ß M± Ë5Ú˜8ÂÓàZÅDPóØg™HméölÜ$&ôþ¬lPÌn,X,•xèêèd­+ÝVJ³Áº:úT’É„ýíß³cÐ~´; Í(ºØmníàìŠyê‹Ï)¿®å‚ª¯œÛE$ÿO:®êÓR¦0ô ) KS.ðÀ`MУã…ñÀ5rõip×[H·$âü# Öž9KûhÍ&(.5òÐôZMüL]K)—jrá{§%ߨEâç[nwWÕ§öÓX¬±§ÌÎ rÿ kÅ1¡- Î…V¯’"`}cµJP^f»a«DNïÁH¼^Ñ+r{T{"¦ç ˜O³%ž·é –EÊšŸú‘L¡„Ü68™óþ&a¯¤o’Ÿ•¸·x+1¶™"äÚåÅÇjF„ô}S‚73¿O§y~¦“ ©1¯·Æ€bdÓWú›dI‘½êxÖÐàY’½=ÿ*ùÔ<Š6Ã%Ò±ÉDu§Í¯N ©šÝaźä½tv¢°´0øù¥]·}AÖÆdýP+… F¼¶Áð:36ø³%¹–€‘·½ö (jöàÆËlâ "‡øLJÂMwâr³bÅì.^27¼À!³±VJÇŽÖ›êÌ>G´¢gÒX Á•œ‘Vtq(Qu@‹!E@4k:ºu[äæò’{$Ð-×[ÇZ[ñŽâ!ä1g€!ø¬vŸU·pªNr(Áˆ'BX«S„´Õc¥:t{½¼{ÅE ך:xáܱÌbúa|ܹ–?ãx„2ÿ%<ÏÊáBÖ<7ŠXŸCRl8¡ã*GêÝC„ά*»ºî¡±¢P‡™1nzá·)UŠ4¡/ÇhÑè'=ëèJ@+Å*+¤Pot$Ç­å7ÁÐ)¾TåÞËWÌ f÷â%Àû“p,¦¢’0¥›|úK⛈ëiT|g*dt¬‚Wm;øA˜;Þ9 yÈÅ´ÏcñZÂòm‚÷•eÉÕáòÛãB†ðo·Ñ 0õ J‰Eâ1|çÂexËhF½àd[kÅ«o©t -E¥7 ’t¾¸‡Ú†SK [?ÞI«=Is·`’ Ÿñ¥Žò@Yåü%T÷ ©µK³Ü;ÓÈœôëK>ò‚¡Þ€­z(°5Gœõ ¹ÛGs Ö«ÝE".w_]ê“`&R#õ¹²ø7@lkl½{™ÏÕË›m’w-^Ne„UžÌì3Ëm„4 ;º‚_SHEa 6Ô$Ÿýl@R˜ˆ*_èN Ÿh¿t·gY,òžõùtÑž)\øU¨½~Ôô}Z<«å@lt%Ë8P8Ž9>w €a·ý†A X&øö¥H°¿ÖV0EyÎCÅ¿ó}´Dú»`ï·²oáÒ/ðèÞ‡ëÒ}]à 7ÿC['ÕÝ®‡ÃõÑûu«øuO>­Ïý½_JngÕAßohÝöö‰ü:VþÁðýmßopŸÃK¿á¬êîÿðýq~­AþSO« ŸÕчê#ã}´%öößoiä?áÑlÙðï“øiGü5’û{íÐø~È¿n¨ÿSsêÞÏÛÑÿv€÷؈aÁqµËí°~8Ë‘©ir<§ Zl}ÎÁH5‹5*#ÿ[PgEn$~™L–‚?{˜1úÀRü9q 1Çèæé4_;áȺêjd9z.z|Ò©ðÅnxú‹zfvr*¦Áð{Þ0³û{)sï%ÞÊKSØ›B;ôšÙ˜5ιÚäúIñMm¹oI7ª ¢¸  ¥OÍÅ#Uà1€³Ù ²6иý·„ZtXƒÂW(4l)Óu Ó¹ÓŽ‘Ü$±«4\«½úÜJrh矤¼à³wD…óÄ·¾+Þ¡x¶i n§H+’«¯Z¶€x¡´ßÊýàÑj!|…D€àø/{I>8bžûtÈýáö“Ù2Mj矈ÕoY~õ ªyWÖQ t~~Vn[Û˜ŸÅò±âyØ„i   ޏH£#Y¡*Äc^¬ËŘ40z÷ð·M±SÁcô»Îf*¨ É]yê—ŠëBt›õÖÁ(^æéÞw¥Ç…Ž/š·J÷ÔÆ€©ûgØÒáó80 S]bT>ÓïžW íj3lì“'+YSCíò†ÖpBíf±nG&íÁ)R7)±<톒ùøxÈq?-PÎ$߸–õt#’1rCÄ,É*«ºz`®ªh« ͹¢î¹±°#(+î‡eëù¶ö_Ó‡¨áB}œlr¯Îι¬"™¤ïѰjGÉü˜–DÃÀ±¤/Ø; _U’„âÂâ˃KÄÔÎä#£aϱõÓþ(ûêsy~iMð1$õ5ªÉ­¢Ãld¹Ã{ÿKǪ0péoÀõnܘ‚ë&OlÚÁ±šºŠŒûºÐ˜Ûew˜8n75pf«í,V®ÌçYðJZ!ÁöëÐŒhÄÐ~Z¥,I-C5Al‚GþÐ0ðK§- ýZ²ÿscañÒ%nÞqñ–œ)XšgWêÏbÂ;Ô$`RÄ–mGºÞlÄaÑ ‰ƒÊ‡*ÙêûÁ“Bô"$ÀÔëM¬ýÐe¦lɘT~(+ vVØ–rEr„› û»ÌL7 OüqÝ:X=Noéáê¼ÃÄä¢c¢D F·cÖý:2z` °ãÐŒùD"=ëÚÏ:M‡”XmU`Þ 5LHzÌÐI×ú`s  ð­±(}óÓAâ´pcÌœüWª|krVÔoB¸îC`,Z¨•YÒÐ^Š¿s«eqR7„#±pçúñŸ»Êˆ¸™úÿ{©¼?ïc×hämŒÃù²9´¥¬Ô§Vå¡eü“ï;7Ïšqç*en»Q—*ÿ9鈨Y["Ýá·f.È6àÓªm’ÞP åÑ6ƒ ¶¥a’! cÖO¸¼«x1ë~Ý\ã¡ì(9“…4Í”¨: >e˜>£` d¦7,í#bæg½†s!@Ìv{Üé%“jyþáUã„ýhýÕ’V–¹QõaYÛg´ôûßd“´ê8ƒ„ƒJ8Sq‚ã–ʺçûƒÇ˜Ì«ùNf8 YÐɦ1rKÕ Àèšö ß/E°ž-óNûÏâc0œ g eüL·“QêžÂ"IKáy‰¯ø8u'…4 ý”¡Í@™&ÔpH¸PÃ¥`ä¤\ÒáEÅýÂA߸¨Xåÿ •IÚ÷Ï1Ð÷q¾Ã > ¤ªÛ]É CÛ‰ôXã?W6<<Ï© Lçnkòz +ª¢‚ˆ’hÚ<€¸¦JÍÖ:ûõý “õ’¨"dÎ[ÅýßZ)²òÂzxüýyÜÉ’'jC“aÇÁã‡zˆ±âÏ<»µ$‰!-Ô:å ¤z«ðŽŸÉ•ëN€ŠoÛsÅ8J\´™wdðü{Ä¿óvZOÚÎÕZhÜ)QgßåôA "C&ÚTŸG),TÙRseV>åÏ–©dYâÍQxö-›ÅÂ% ù"þÚyv޼å3ZöÔ•L‹ƒîgKõ±ów|h,†·¼Sª©#VûLA‹AU'› Ó^œÚä=²^¿£Ñýá´—faS}¤e¤“SYŠTÚ@õ0r'ˆ²_r?Êæ&GgÑP¨øü ÑÓ³Ý*é“ÚáGÈ–³•]Æ›ý7}¦ËóŽáä[÷Ε½õŒâ 1xëÓëEƒ¢‡c>Z¢AÐúxË»M:òá ¶áºJôõý?Ûùû–ˆæ XÃ{!ÏëàŽO‹ô :GõEE-ïÅCC,!Qö'SbKøÐ8²T¥Ù$ÂPD›ËþûÌ®ŽP@Â!/ Î"‡|ÚØã.^ô! 7ekûæ0ÝÌãv$Öaþ32R}ܳSŽ:Û°çœéå ÛAP»í}Å6 g¿±nÆr[ê¦ÔT1t½ãDK5]n#õŸü´øCôG·?Ÿ|¿„¥¶’ýHÃA’_K.C>ÚDj[Aµ^W_\7F„âå ⑽†T2` z¨K×"IÒø&Ù8¨ÀGá–s"©‡ ø¥·6”¦%ŽœÿE%e-ú`sv›H1ØãÀ›Ï¨Öš1Åkúß\áF&W\W¸Ýs\a»NòZ &VBúFÐÀkZ5a^3˾4ûr<%Jý6E­ngÒ6ä;=BÐ^å9Ô§–6éJ©0©}.äfý³è²¸ŸÍuµ P›µ½fÙ&¶Þ±‘j€o=®Âói©ëz2„+IÌD’|XIig”0É wüÞH×_eäjSÁëäcO ’K%ÓDЯ;;5ÇývQªÖ HØ©’•gl‡÷ 7‚HNE¸Ê “Ô:*¦J-Žö®³;Tå8ÅN’=ÈÄD?ÓÏbÌl·2è„$Œ…OÄšN®¡Çí¶¸0÷Ì!;$› AÉüÓ$…ú²Æ8ô÷ê´ž¨gI5/UÁÖìZƒzÎ@ˆÐS’×˜×Ø 7ë!àïc)8T}Q °›š?l;¹ŽØ"@wŒ?à2{×Ä%0îŸcëTv{éû {Èš83¬\¤œð‡ÚE“ú(u«™ŸšƒZ‰¿1 ôÜ¥‡E¢ì15¡Hín=豟(ù1èÕ«µ—gæ SÌç>ÖZ¢ý˜ÅZ™nÒ(©¦ƒc<+{Ö 7>ÉäÚ^×$b}㢕\ ÞˆrçøLÕSáÄ3Ìy‚ªåyUÚ!#îa{*ʦA^hãL¬}ƒƒÈ„Biî­­Ì¿Ðñ’rD‹9•‚ã¶§ôB*[Ÿµ;k™PÈžcÏîfx6 /ã/Í"åæP.zšÈØ•6@¨FVW­p7ÒÎ9Ö#‡Wª ñc£SsÑê^ŸeÔ9rÚ´6„‰÷Ì5ÄýŸÔ`<ôK¹ÒGéØrû^²&ô˜ðš@冼øQ(„þ®iHgŸúFWˆÍ$õÐW—FoFÚ‘°‡¥ävüË$Ú ÒŠv¾Æ¾~å&íæ¬WóqàGBe/]rÊŽh&¿­6?žÄSwŒJ­Mx±Ëd… ÷…ÈË »K8U_ê5áŞأBJ…óíM=žP¨Y|Su£ö–F’_õmU)uqwd?Qm1àmâ7õ5H_vN;‡ÓòŸ4{˜¤7c]'Ç/pêhà‡ÐTß„¬(³xDÞ' Ï<ÿ*)}¯¼9ü J\óá+y\CY&Á2“ÜF¤üì$c ƒH’ø-<'ä“ü³q1®VÙ*eX{°t8m¥µî@’qgŽèÅFƒ(ÝnZ,RlbæfkœV·¥ÑâF8˜úã—=š( 5Ž!.Ά Uö]ƒìë²¾º•‰ÊpøôÈöÂ]<¡*º)hÆŒs¾$h=˜gÁߘëïQ ¥Â%YTÁt…ˆÿ ‰ ”m.¾Ñ„ñ¨G¦:¿¼ëZØËÀ+TËVïÐÿÙžh¸­ÊÑÁ$Š¥Í4gë91OàI¨3‰Ðô“K(âòžˆî‡ÝFÕ†‰@ta4å³â”/Lîƒ5»éŽ3›`7qæJ^%}Á=Þ¡ä“mdž”¦¤ð¯-ÓF˜hK|Ïòïœ!€>Ù˜2À˜Zƒ[—üE>1ô­ùá솿îsKþ; ÆU¢mš WQ7Ä®kÅ‘ó\I”^™ŠÆ?sÒsµ¿öˆÄ”_ÀB5+Êß[aK]‘a$ÿEuëŽT¿±‡°¬Ù"6í.)²þb÷0–·ù?™Ë,Þ›‘FÊ`kcíéÜ~[-…!S‘"Y›?¸%ÜG98IÑ9Ò†"©Øt9:–¬¡‹°u^Šò7Ö‡œ‚ô÷çúCrã‘zìP‘'Øô¦¼Qí‡ËlBøòþÉy‹‘iéhÈë¤ÙbF4¹‰à—Y§R~¸”§^C(é¦xd«Íu+¯úð+‰t¢Ó•œ€B|rߢÅgÇ}Am⊴ýQôOUÞ=-ô­ç¡ÿrV6)Øß?nÔ÷ý£lç–g{ö™IአÂÑŸvX5+é¬}/0"ËþBFš/'ûÍõrš« <æø‹ÀÜMêmšŠŽT›QŸ”;Mªâêñ$DõbÖJóLl§„ÕÏLÏ…~4Z…>œ`¿‘Ï¥a?éáÑ{Äí&à˜‘èߌZPÉ{+ þW­ñý–q«"åÚ/µ€uˆóûßÉ ùߌ’_‡à£'ü‚f«Àâá È:U‘×zt²Q1¿3öÖæ"UýBªïåã)ÌPÃÿxêŒ-¯aë’ò¤#Ý!¡*\ý†å[÷g”0Ö^àÅ%Ù‡††€Û¢Y ;Ý·dôÄÎ âàüB—Œ´³íq¢‘P¦#œ½üÞ—ÞòÛÞ Ã!ËÊéxÃ[3òWçqç–Ë£˜œ)¿Ô¿öœ…C¹.úOE+6+âwdOÉÀ§À?ÏéÒÎP}NTr«sh{À%kÙI5èFè‘G Ù+Îü%VÙâÖ .q­¶àãiÄÂS׸ÃÅAS6ŽµÒ«êM¼ºtÙ'º5ËÁà1âŽÕ P k’š›¦U†c¢ÉÞKÂPçoÆù/ÎÐëØÂ‡™/v·S–óF ?œ‰ó€çdj(´¤õù åíY((ÎØ=¿çW…瘜¶Gš³ûýåˆKøgL=žbi¡g/¸>H#yBk: cÞ£7™6ÞañŽÎf]ð$NfŠúîü…q¢ë¾H‘¢<&žU‡ÉŒ\‹¿ÞçC­!‚¾!ðg8¼&L u…ý”<Ù8ÐN¦NÄCv#î1À0tÌŽ¿c’ÁÚø6N@(‚/#÷à¶šv¡¥lÛ}t¬,+€½_Ц㡇®©ë¢†q·íÆœ˜yØD[Ï7`/z,\oíë_v@*OHà`¡`~ãÙ<‡4„aáÞ±p© ƒëç ­» )%d±Ž}Ø$¥”»ŒµÌS¦ä¬@j¹Ýl’5!ÆÑkUÙ’_òXÞÇ'yxK`MH­–U÷þ8E’Då@³øÿ[„uNrÅ /#ŒÅêÆÁð>æ\ú8Ö ²–Ò@OG-.Xæ;~>—bXžóˆ¥moCýZ=ýÙ\;ŽüaLÍ8 ã­ïþ=Áo!‚q÷I‚óv<+€ 2ɘýømJUÕ–¶tž^WBÏ òË‚ží2ï¼¥Âô¿äŸrw³ˆSž5sµKŠù þ»½'•u&n{©EÝø ­öêVW?½Iqì|å.ü2š¥z[1½‚‚Ú`:¶£nB²–•s¨¥ôªœ.Ê–" éKU„tÇ|Tb|€ ãYÏKRŠGÏ¥\Ë Ý«–Év¾Æ&Q͸á>SïZ¨ÃóŸyê+e÷QØæ.‚XíFb¡wÞUÏlw;&‡‚O—DÀ³¨ë¸'Ô)qF8®õCjÌÕ7O¢/˜Ô”:¦î|¨|æÒ~îÎý(Zý“­¥‡¼s¦÷U9صµfÙ7pL²N\8a³Nሞ °qàç¢SÝA’i Œ‹kÍñ &eK]¥>ì´} æâ3=ÑÑF§ê‘ãC%ȾA.2EŒ?L¾ó/è"ÀίT$ÔçïÚ¿4zßzèrvEDP09$y\‹Éüõ;@FUAOp‘îÓtgïðhJƇ­ïç%’õ4tâ/ÂU·ÃíÞV<-p]b£¯K@+²Ò3 ]}4£mÙºù?Ò’×~¨?oè“åjêæ¹üùuEåqï}ÂpYÛ_ðIa¸ßFÇêpíÊVm ®’æ=t.&žb¸…£ŠÈ™då{*§³Ýºá;Åzžl”sþÀxÑ?êIÞÌ Ë§|ßÈâNâäQü ã¤Tn^KUú„Û/¨‰'‘üÿQyž5ý r175*³û_–•ØTªËJù왵§B䤀8ºžMxºÎ{ ,:kÄ„ioÃ1ñbút{¿ר¢Àf7ðÊÇ­Þ›æÔ·…öëD2Ÿu'Ó0A ]묢R÷‚™Ã÷&·´„ÁS}AWyˆ}$7ô‚¤Í`‚—®•.~ÄçÝU=äd\•ŠÓ—d±ãyÀ­zÕv|àV;dBG^bÚIoýÑœÝuûµ‹6*X©÷å(Ò†yÝ0 ìóÀ_’ä '.¢7žµ_ÁÞ2;Ç„g5ÈÆ‰€â;à䬮cÔ2Ô ÇûB3jÕ¡Nkabu—Š%Èi’vDL=¾mMf æHÌ{)̯Ÿm_Cõo´ýÖû/¨v|áó`ÆÜ¾’aùÒsª¢2MÍ3._Öõ¹E8¥dÛE,Lg\iÐËG™WUGã"µ= l ïQ+:EÔÑ­y©¹A Þþ¨IZûEÁtÜþ»f:DkkQp ` žBº äF*¥1Â7ñÎC¤MÒîHøÒå£zs²”SN KJÞ,h§/>5kÎKŸ@–'‰$&®yaÔîkÕqA›×UïíÕàžÌq÷y¡ŽsêÙVPö`jñméÂòYD¢jº‚öæ‹R˜Ü”¾;ÿ&ЬrBNvðº:7ßz<˜Ñ zXÅE¬.´|$ýk…§U½‹ÑyâpG® Uåê™Ë'…+]iä¸= Ƭµ?iüQò‚$óWÅÂï0v$ǸþȆn8×Ð ’Cær>¨Ú¥‡ŒêA|¾%ÕÄ>I–ŨÄc ûS¤â ÎExå|ÈߘåF£9ÕüÚ·°{^&ÐôE‡Ýê¢L0!Ýãˆzªµ»ªû!2ä9hZmæüýGtäºäõ[:;s~nZÑ ‡ô‰À ^TAM'ú.,rd;<­Gqœ_M¤{n?!ø$úÖZŠ3ÔŠø¦î1·X±ÿVÙd¶êÚÀœy´Fè†!óF›D1¤´z¼‘z‚bC,MÊ©™¡í{xnN~ëC©Ü\s8Pqn·!ïà¦C. ¡ZÓw'¹þ‹ ‚Èøñù sGýòóàuxéî•¶ANÊ®câdû¡t””¾cs’m`õ¼ˆèYðµ²ãj÷Õ0´¯ø…Ëô}yñqV%Nú…^Ç„ꉧÙMÌü§Õ2:MF«¢›œÝ;îâW¾ì¸ÈÎÔ”Y švw2Z1¯f›Br5G -ì“DßùÔz0„åß»¸˜K¢XWvt3—/ˆ倶Åã ,©tæ!bl°9‘js[ªFQÌç#³©øW05pç€çºšihM®¬ÌÏ PPïãzÁ©Ö ÅDZÚ¡¹Â©r€ø0f¿j2¿C;LuÍÑé U¨°¿jš;KÉb¯[ØZWNLÿKµ>œ–âðó]²Na˜ÃÁ®m†¾™$ÈþÂÏ—|EÜOf4†Ã•ôõ×âHud#CÐsEA{Ä6dÌc–ðÉ*‚Ã/3”aùHöq)œWtÅûì$Sàýª5>š¬¾t­RLk›D`ïÁœ›a¿-ÄG{:•¶úÄ(%Ç» Õ~µÖÜjÑ îýýÄѸðwäDõ€AÉO…*hôw 4 t•¡]‰¸5°yS>R×Ü0ÖIæÇ] Ķ–èÌxO;8ûz-Ê“jg¥‡.‹¢³éyz0Íñɰ¶ ¹ýv•K5ë‘@tR¸cz“•¶Êp+.T®Sl,’‰#Œ‚V‰ÀÖFÅüŽ˜dû"ú=§§/A X,+UINöM3’94£ÛEb`ú­ ñÎèdld³úNˆ™.=¯ø¿9Ópf9ÁHƒÕu¡”‹‚}2ÄæÔ<©‡5r6«3bfDå¾ X_Y/\w#õÏëý`„Í"ºVo ¸X|‚Á —Sô )‹ò–XØ"¥IŠüì®´|†E+$ `)Pá4ë6™àžyªzIƒßÃ'8>´PcÐÆ–÷QøÚWóZùd;ë½yööhÙ€n OP‚.‹Ÿ]šŒN%?ôˆƒ~¢´9Pß)™Ôìê©¡¥}†#_^Œ¡OlblqQ̲.’i ˆÍ%¦ŠŸš!œÍÈE"wû¡}¸L ñEÌB2mƒ[Cx Ü cg ¦fû' X€p¿Í=æb¥—„üeÉN‰U ËAS Y<ê´€hÕ;©wÔWöôzô±¢Ï5!ô£ªñðƒŽÓ|r8'ÄÌðbÀÖàñoîß눚Ã:Ý üù˜zìX61‘IB.«®o! Y: ‹7+ajOnkl„ËP"zŽÃoŸÚ$É,¦@ËL^°êìê ¯ƒ¨fik üUaUAêþó³†a攪°²sê΀FžG3=·æ…0Ìe¦C!qˆƒD`'ðóo°Ø§ÆsNjujA¯¦iï$(’æUülxâ Þ—{—ÔŒÛó¯…þ‹juÆæ‰ŠØn¹Ô?Ëç«Ä‘7‚ûã÷®s›ÈAÁFi3ß(2‰H¹1ey¿ rÎE  óêüÖm‰›ÔÆgu·ÍSGØaôDä˜ì‡ÿÌ9”­Xd·CÒ$̧xüS¬½b³x}%¹õÃ]^ôÞ3å9@d˜Ê¿^<úO@¦F)L©Ë=Ö©Ô]—ªrXSªëœ) 9·f@ú¹Få’Ùl}Ñ%œ‚‚¯oñ@A>¯:M0•n‡Ñ¢³ÙmÌê÷özé- NdS¼™Ç¤uLÜá 7+{˰KNeo¸&)XÅû˜+P˜µHB Ö¾ æ×q½Éâ±çøÇÝež5ä;1T‘¯q,ÖÆ¾1´Ÿ²q¹¾T 5s“äIóT0ë˜qáÀÑ¥¶ðE®—ÎÿgR¥Z® ø•Ę:­u|¿È÷ÏÃ5òÓ2å. 265­Q SóT÷ù4“Ì»Òr;QN1¢fëëVc¥¼ì¥Â9Õ’•§„‰j*¼ë±g-C¸<¹cëcÿCQöCª€R€¤ÄÌêàoú_™=µÓÐÞW¨RLžìÍsãAZÜÞ€FxbHc‚ÛrØÕTjxVAÎ5`|gaâß5å³%qñüp™ Ó@[Íý·Ó·©Ô'ÿó…2ÑÃù?%c(OÜô!Ú9@ÞW96ߊåtîûÚ 3A™4 lS*'ñÍsýq€}3øÌ.ÌÉúP^T|,Re.hêÀiñíÐhÓÂÆxö/4K Ó ïÊtË`¯¦”µ¯–†Ëœº@äWl¬—÷ˆ³j&ÿ‰%ÝЕ¦P>µ¡wÈi(Í÷ §£º Ø8xú7D·ò}(l¿ÓÀ0šêIL.­?Ÿç³ÁF|¨\üOnluÑO£XŽZ²7MX×2ªD•Êí¿é1 ¬Ö¥ ¯ðŸ¬_;½Î6¯›šÂ t±pqÆÀÓO¾Ó†‘ˆÑŒc˜#ŸØßù‚wx9è¾C&W²¼üܽñ-x<ѵRôÌÉÜSºÑëÍ…ÇÚ¿ûT3˜G;¬Ë5†JBIe FWøË §®›Š?j°SÛC¯t”‰¦G²¥X26'¦ªKæ`w\÷êÅ_Š©/—| }¸šÞ—~“î`’{ƒ< ýOêö~)™a•…â«+ >87då˜:wÚÖ•©œÄ¿õObîªtiÞ¬Œ¡[ˆaÌ4ÙÖn*ÓÞé…³“JZ)OŽT]ýJ5SÞs”•˜KÿëVž<ЮZ ã¾”9þ W2»þPØölOD§Æã’‹†à8”–Û|£Ÿ4÷ïÛ!Ð&¿6XüÒËÏÉ&ºX¡˜ëŸp¸¸õ½|hHBÔƒ|¨Q‡f8bØŒå"n††“fTÌ%›­l1†qçÙóeIeº$Ò]¹ä¥†±)væÉ;­‚­’ßô‰0¾Âë0qàáð¶' ÷.qúZ¥Wk²|Ìžn!˜Çž|Èå¢ë(\ŸÌKàUõݨü‡js-·ú{º”¯´i<¨X Q”ªÁ‹¤H¿»&‚Á‰Ê®ý”JB¥M}$1|äUïßY"3Nƒqþ*q`";£U÷·÷eñ¦N㈖T®U+±ÿ„Ø• ËÂVÔAuÇùéñ&cn¯>få-&)Ài´ÔQí§¼`žÛ”Ø'ŠâCÈ8MŸlH›Ò#ù‡7¨nZV{1ê|&I“UàÙü;Ýûd5~:•ç1|{“©˜Û¨6Î)šjíõudÆ S¡?['—<¬‡†Æó CiâÞÌ…fÊ_öv6¶¸ä„ßûý‹m«@cÛb’íÛ¦Í3,=§HÌõùjYyæ˜Yãr’7îšÚ´éƒ»Õ&©{ø¹¦t×ÓÒœŸ•dÝœ`YG%%{‚dfBœÛÅ÷—]kêàÊu÷¾WÍ逇xÜÀ‰ßÝŽ›ŸÃíNAûã¤óÁ—ª¨Ê&;p•øGô°lÒÚ±ˆ6[”JÙÖóéŠë«´û1{U1ël}ýüxmf-¸T]ÊѦÆëòEÏGèg>ˆ~ àh2Ö‘ÎÃB$NÆHIRÊÏͼŸ–°šýþú­°Rçø«ÐŠ[îQRŸ£õ§Ú÷”7<ùñsc`Ój‚Ek^<+@ÿ+BO«låžÆQ—ÀNÖ„‰JŽF¹Ð¨sè$¬2›Ñ&êõ«t(TNбè@)±ÆþTZu‚æÙPÕQ*Jäà°wß ã›I;7…ŠŠÌ‡Bì\mÈð`Óãë¾èUཋw꺉ÓÇ£º‡:¤ÛÍqwÄ㸄"&БF%¢š«ºˆ‰Œæ}aaÖTï›ÂX‰L\›Ê¨ pàX¡ŸÇþ›f›\Üâöû›ª·,¨k·»à™Ë>Ssc'7Zo»›ÌÌP_¯[÷ÓÊ dƒœE˜ú”a³MyL]!W;âŽ4#y¹lÃxy—ì0fM~Ç.;¹²F Ì|„vÑ¡C¯ãŒ(2B|†G/Ç눵qTüøVÿq=Év÷\™°]¡‚-ùý <"¨ûírs&R'YHüK«*²µÓŸÆm2ÑOä©×‚©`ÕÑ!ÚbcåR—¶,Ü2cÕplDén­„.ÓÊ VÀŵF÷%ˆ¶´G*7Q!Ž•­ž^ö‘˶ÙüÉ^±åÊS/†à£?}æ“T­Rí›í©þÞaÀŠX–gܺ37xçÌŒ J·bÏq€¿¼aã×Fùû^e‚­öÌÍÒͰè4:5X f¼á ’“ÑØaEuÜòÚAªðáa±mÆ8ƒ ‰B«^BËÜã•ÀÜç»Å ì<*û ý™ü°¯(öùàAØÉ/šVÁ6ßÅÚŒ.D åýEh8¤½Ÿoíå‡o~ù4 ¥4ÅJ~9Êõ:$‘§²¦; ŠàíRÈÿC14ñò¤Ãñ&…žèQBÛT³ªyì¤P,ë!´¬ïM-½ÆD !>ª !»f(·»Þ£þGÕ_ÿvñ4ZòDí·Ëõ¨{he^¾ÞÃ+ÓGŸ7‚ ‰êE#;Ä4'E™`_T3ë;8gèƒ=~sëi;ÔáPýœez ‘';ÕÁn‚üìOŸˆ—À 0ñxtó;5x÷µèÝz8Ù§²»vö+„GI|³v*¥ôlDÔW×Ú™^,ë~&ÏëaC“ íúif–FBú©Å”üùi%7½ûÝ›#y—Ïm“i†”†ð‘P\^_ѼN7—Œšã0 lg–ÞëÙ€½Üü4Rðë0xÀJò%6úGh"JbèNX-=¤Õ8 C™‚gdÙó–ˆ*Ó˜ákP³Ð sÓžJû5¬*øæÂ5ǵ4ŸjÙ\âÕK¸&ÇDGçÔûÂ[3©Y„±\Zzœ†¢,Àwðõ:ñùJIƉ#ìm¢l§‹)1OíªŸ¡kÊÙ8ÁZÓÕk±i°ü… PŸ†˜!KÒ—¸ …È6;:_hã^PŸ/Õn‚EÚgfF$\>hÍ98ëÍÞ Kòa”1!]Yꦙ­òzFZ’hì4SÐPý1…OÚž'ŒÇrh3­Ðµ_ý’IsºÔ¢1áE›-(ç—{O…ñA6¬=d?7‚º·ú>£¬;5 OqÂ9Ò=ç5᎓}ݤٽU!duØsaTa)œ< s«Pš“ÙGŹۺ>l Íʯæ0ÉʆceéhçKóÂî1jrV®S©ÒjcΩ.¬_£x~qÐ#\aÑvm±vÓAØÂÕž§a3Õm’MÈzôJíD¬è]E ájh1“É#^ÃÓ̾cÀbb:zòõï×*îˆ-X)¾¸UÊnÚa'ÝXô¬æó TGðÔJnwŽä3ˆ±ì×zéXµ+ž:H‰þ¬l˜Ýú¸šOöPºŒ;î[.¾m¾ƒv¢%êÂÀp^ƋҥqPØN߆8Þó%;̬3ÄÖ d†_ÕLC€ÎÈCóM切Ým`رúAÞµ¥­f×k`Ï|Á ¦uwb°ü¤ÕO}Çe*­ÚúãdÛý0D‹øÎ3“Ö;ßuÌÞgF7µo'fϬœÀCÃŽŒþÎ/xjɆÛvú-¦ èŒ;mÞæº ö ¨Í»¸±)Í€6eâ“°%+Öãòí×Ö¥MâP{rh=™9Êãÿ7K€'x‚Dî¢-g˜Vá+1’Z†Cž°PdÍvÍ<+d0£­ŒJU¥óÐWM*Û§T¶è{To!9iÑøš$£Gì;%·2Iº2¸¡6BV, UÓµÉËfïõÖ‹ê­®øªÃGòÓ«mwKŒ% êvo[‘n‰9{Q†{"¨å»Þ¦É®›–ãBÞ:A]šÙÙt‚—ûóè·$9$tYÝE©|MHty4†1\°óZŸõf0œ2»gÐÉ .ÇŠ#Å@¤…¦÷Cè…z±vD1`¸¶ÎutfÞ!–e –ý™˜ýëÙ²cð'×+ò¤‡($¢mTÂcwP¼2]Ê+°Ô*¯_Õst9ãXAöÊ÷êô‡¢véXl¬Òwù.PïùÓÕN‹ì|_b)—{?–HîDœG¹Ú%Ì᱿i¦Òèï*+qœÓHQ$nÅG4Ã0NÍÙÉ<³ë=gã©æ¿û>ÔðM„ݧñV‹¥º¬§Ö𡊙¯Aœà5¤  iDÛE”W¾¾ Mä/ºéogÅ€SÕŒ”É%;ֺߵz¾È–£5yû~èJù(GnÓOÑÑú‘c@ýè΋ÍTê¶h™”m?rˆ ðn ü‰@t_¤¯tð®6Õ$Xw~ Z»ML]”5âµ?Y”1¾êý×=»MA%xØ޶K¾¸Ã„A°ÉÑßY–Z×'½ƒ>'°ÚÔÌò*¡å|R³Gò×ZÌou+IjOO/ZV°m¥•® ¶Ç‹î¸¤î šÌè Ç¢sáÛbÝË„ŒDËw"#Ôê!KIzˈ±í!§ƒef;z©ž£³¡ßÅ>‘AHx„!Ò××̘Þ1«1þë²[‡×÷o›Í¸nU¬á6Y‚dh™üÕäN!¶R¾éi5Ћ^ä¢VoÁz€¬ÈÉCSâåJžÙë7Nmcå›D¨_¥ Z‚ Ð2‰+Ê_\°¢ì±``kj27õa³Ø¬ fð!ƒ55"/OÇ>FfÉÏÁ¯hë%VÁFh¤ùs M{0yàúÓM_À‘‰„-g_ÁkqùX­®;õÓÜý(í£wœ™[ÄÕŒX–#;)©à·ƒ€[ ¶¨·¤g„ß"ùl¨gLÀœèÊ WQfÑÿ$‹gˆkÉ%SãÚßÿùr“@e¡¢È.¢’F+§§3-Æój¾£]ÈþAŒ±"ÈblÐp»ö¥¹$Û­ài[ê]°÷}• ±é6þ6E΄ù}Œš ¸Îð”Ÿ'Š ßMß„}Ÿzb*r–ç'q, ¸ûC$°uËBéR18„ϯ˜×È}_ À|ñFò~–¯ÕT*¡÷Øû"uó’KŸoD5¥À—˜)ú­ÍP• T,ž $¤Âï”óœv)qÂ|Ϥ%Õþ¡‹ö.º…õ9RáÙ¬`·º"{æÜâ$LX&³ìs~ý ýÂ]RALÖ«1ƒq{Z53_@QÇ—ñ5ÄPþÈêØ”?~…ùÆE!=ùfñG`¶EPô:S¼˜—nAEmçXÒ]¶ÝÉŠl—Û@v·öð‰âÝOÿmHPÙT8&q—†Ò"¢ ˜6O¾D¶±£(ŸxÁ¶ M„RåTå0—nÇV¦ž¨TÆø§:A[N¼ã\Æ)[ ,(ÊyÊžŒ6×ßÜzÁ0Wê¸ÌZÁº{[„‡Òˆšªx„É€ñŒOHõÚí^Èçª3Êx^>›Úno‡zúq!»ÇqÁm"þ“ç§í¡ÛÁ@›9q“áßûu€_·Z¥Ì»œ'À>ÙT5Íïë%»R¹yR`±ò¼ì0E]À,€0§}µê@ÉÄn¡ˆlõ RÝÓìdŠ €Î8a³=Dþmié~]X·i·O‡áîé€?’ð{~ùñ…¾£Ñ°âÃâ°C~Žóê]¨fÅ52^|[®ˆêM8¬¦e õ3Èû¦‘ºm|ÎS¦áÓq ¿žTÅiÅ\&üÖîTÍÍÚÊÒN—±CîÃí$eçGvÐÿHW’£~.Øû‘úv݃C̆gèzÔyTÀ<#ÆçM¬¦~:cLš÷§ ÿW_ ÓÀÊ=,áŽ-¥£æ¢ Ú“3 Q[X„aKÿK»ðüiõKá}2‚~vg)©`G k'Vû"²~fâVç!ZD¯Bså€àûiõzûð8cÕ¯F’¾UTZ¬~H¨W{Æs®¯‰y…ÕåcH§c%U÷®¯ÜH‰ ,^3xD…å…£ÏË$5"‡­J¶]gøfè0·' weW²î Á´tù25O5ŒþÅ”¡‡^¥3_é¦>ÿH¦ÇJÅHªèÁJ° Ðð衇áKˆ-P/@LGŠ ©|UvêZ²¨åª!îó|›Þõ1õÜÆ.²êUàü­}š¶I„mëš¶,ùv#FÛnCj]%÷®x´‹—‰ÇcßHûÔÃ÷´–~3zŸ¿±ø¿*³ôGVQ¶u®ú„4[ï†,×*²¥EõË{Ó=ÔnM×ÜìªIX^Ù Æñçîäúg–¨±]Kt°ˆ_*æNd)zÈ^3ô¨~+¸-Û84y±òõ]ŸƒMÚ›qL8ÕýY“óçóeLx/¡­‰Ô“x²A5Ž]íGþ@þ ®Jžø  ôï7‘.·GÚð59#¥Œ#|ý­÷ÿk 0Ȳ˜T–“f÷,Lǯ;ùÖßë?hš xÊ ×‚xÖ ù5W+éä9JñlÉÒöªîT5uﻘÂ8Ê œúž°åÅÞâQ‡?ó8˜RiÊ‹ƒ¢!TvZÀÏuŠ…óWçzFYrrºV ë›ß'픑f³Õlƒðaj6¯=²ZÀÏtç”èÙå:ü°íe¢)Q¼0Ÿ ~ŒÉ¬Ê\…GHJ.£m­ù÷Z9ÖQR'j<Ëüð"c3×h¦ Ù~¦' «µÏµp~¦ ,‹‘ýlÖϪU½-ÍÐïÊœêGuö^µ!2jõPÓåqç ¢Dùp‹°ŒŠ=9ˆU»{XªÝIbˆÄ|;Ú,dªûÙÙïMШÄ=ÌòUY.? çg›‰L$]Ê«ùP.ƒŸó²3d•e3ƒYJ¨<•¹bµˆÇßGd‹ûF}€Åöµ+î ÅcVòG•[çlŵ ‰ÑwŒy‘QØ,WÊtGeï ڤز-‘ü ËóY€RWAØ^¥œb·…$ì¾l#\]m7è¥d>|Ýt EÓç»ø,ŸfàˆK¶ˆàoâY žÄ]dg °@ûŠá©¿vÛ8‘…þÊ£’ï?¼B;“/N ¹fî&u6HÛÆåå.’TÓäþFq†Î_‹§Ô«D;Tw[÷Ï#ÏÝÏ’ò°Ù€‚g9`³³:ܹ.Øm¹Z2 ¦+|íCÔ"5Q‰¢6UʾNt-ЛV·Úrh¥h¼¤(®qS9TÇÌ>à ð[Kè©9õt¿åp‘ÑÆÉpªü„¨âÞ:Ñ–’U®/…Ïr»ú]I(pÆðóöÿGzñ¤4y§E0í¬\ÄÜ,d~\§Ò_vi„wû–›¬›rMë!ê-³’¿ö%¿çÃO4ì81Ô®Œóxü‰˜:gÔç²söð¶ö$Õ·„}”€º[á&nœ'G=AiRÑ£\7áyqì:#ÔKßê2õË÷ÀìßÞ÷1¿=…¾9ü«ëÞᡨÐeâˆÄŸ¯|`ƒü‚ê°Gý¿Q…@±Â>Ï ½ˆp‰ ØT7eÄL[ È–$8$ý‰}3&#€’´­¸?R˜àÇJ÷{û©L­ä²çA}ƾï>hoM})Œs…¡‘וì^hpˆtå¶¢»3€åw:èáµo¾ *»uP>´ÃÍ%éæœ!C9ÃÚ·;?Í‹Êd2}, ß•±XzÒíòL{4èv™W[+AmÀzž™mUlRÓ¨~fÝ£o™ ÂÚT;âg{Z¾qÑ"î¤s—ô˜yœF‘“ÞúÚ¤ö­BYð:ß“&“¾ó#5ƒÎ䯻á3SBT¿!ð›y † [q 5Í,á9pɽ(2¿®#!`˜ÆÅú˜ÛBÌSǤ;0`€ô&Çb9zÞ~WûW½·sï+Ž`^úÝ|e͉ӻšH}Ù1~¸Í7ß3µ£í÷Ê! þŸ`U|…«\ÿ"ÎË/ œÙ\˸9ôç¾—1^Íd­ö¼€qéªD’œ,약h^7‡^ÑÉé'tìÚúÛ?:k®-¬£¤t-ök*ï‹"§¡Îc%‹!þ&ë¨w¶ ú®ôžb(öf((„HÁG­3äx-ê€k|Zfç‡ ÅQQ5²,°Á,åú€/‰+¥ãÇèÕ—¹ÔЛH6ìÓöI îdŠx6£ð êÔÂÀŒÅ=‹/—JÅ#ãaÄs§îB6®à å~rÆ9ëCA“Ê,ÝÏ?–²By^:¿ÊDÏWOõ±ó@\ MsOzIÚI½fsðzpã)’Ð! £&’ÕÉ\KŸ**ðæB0Ë+ïº7ðùßD.rº– Œˆh$ÙÌÚPjˈ÷¿ Ö_ûlR¹_ThRñøý꪿ÙMíý³»GäÄ‹WQJ× ïY@_2£™Í0A²ý&z»'ñCöF;HRo,sgã5ÿ,Ÿbwv“ YZ^½/¢£LüÓ>IDPîÿ9Ö½¬—}„¼¬ßXøùOÒvÐ%Qr…Qò«)ì’wç‡dÇXêÌW\ÚP̪ñÛI©²ž$qÚ¾ÞÛ‡j韕wÓlWư$rW_Wy&¤ãZóvôÍ諎\0ÜŸREÿ^²ß¯úF+Â^Å“8—0Õ Ïµ4°[Y |o Wݖɉô²|㳟g—{Ü7•k˾ö¦¬K)m{Cgí8q”Ä`¹l›Ç—:G¥‰VªËVšÐNηO ”Â1ñMîâ x—Dk­ÒªâY˜aêI9RÕ ýtˆö¢¼<4cb}F æ×°@F™Dö£‰-ŠÙTýÉmÞµî Ü tIiªÙ#;Rå[ÊïëĶëã7‘–*=Ê qqm\ô¸ v,†?`R$AÌ:sÇÊ“*{'©ìɹ‚(0‰ûR Ö^”£Ê£°Åzpʶ]¾^éâp:ýpÉV , vràP¯ÓxÑ„!_ ñpÁvH9 A—½ÇJÛd1ñ.ÿx± —!ú]Z\Ü©X¤èâ½}¬Ùð©)g& Ü=»°5`.ð³Ìïˆúq[5wkêz üí @N]Î Ä´H©n–¥í ÛQ¨ •½gIÝ¢nP'Äì7Q$ ÷æÐ˜|ôõ=؇_øØ´Ž¡õS’ "èóÙ ­IÂ-¢B“SvÓ[\Êš[!Sˆ¦Ë,Z/§÷ÍŸ7Z~©{4êßTH/mÞÔOšÎÕ‘#Ñu?š­ÃF†G: P4LöߘücV–ê¸ßû€¾WsŽíA=K¯((¡ ¹±Ã}z¸¿ž¥y¤ À³ìC»Z3=4}‹f9y•"Š+Á2ìŸåIbFdïLðäÔBeŠ:*z¶êÞ¢wÅnæ€Å£÷2•Ü“n5¥ðü°P¢´y/£¹>Äžk\~ýÖ%Kò¯0àX8HAª¬ ê::›Uá¥8ð®"ݬ<‚T g¼ 2y¤êŸA›AAi¢g“ŠŠú;˜nÇþ[z¿Íéø¥<R|•úÛ“‘±ÆÂâÒE UÁ ‹U~˜ÎÈ1¾IÐAÎðe­!vÉCŠÙ|ÅÄ ž™EŠõ(â5çz]»±ªÎz,ÿCÇg‰Ö’(€VLýŸÊÒuóVÂ'Æ>j=a…†‘ýByiãºüœCâOïv­½¿j²/¤n°SÚç-® ¨õl}’áëP ?¡€J^ˆ7S K½ è!´&`a«+¿‚S\û_cB’­˜†[ÀŽQÎÄ_,Éä¿à,ö3ÉgW£•Í–…®ˆWVšD)r·t¸›¥˜RʱBç—[MŠÑá*¹ sõô#•â´‘.v”¬N+u¿E.Ùçh')á®°ÝAppF7­\h& j9ñÛ˜g–zæ!y81çq[" õ<ï!ذZ¼Åö6wãxï:œdúÖ4[°þš §›0”Gæß0ö™êäȯ'Tûçò¶é¸ƒö Ö[ü çEË ޳¥ÚÉò–Ž"+.ê>puy·í0eËT/¯†ncJ8ñJ2‹øÔ™?+sBØå;‹¦=éÄ|õïÁš†³EÛ¹_ê Æëí¤9'‰¡ óaæd)›OÐpŠ rãK‚‘v˯'A$ºn Ö*L'~q(ጛïÈå/G=^Òš{ÆŸú+o:ÏO¹å]{rvd”‡è艉Ͱæ-èZ¥LÓÂ2þka H9böÓ°Ñ#›Oü>‡òò„@Ÿ1¸½%[ê:)žþªÅ("]ÄÕ{Ÿ·‰m»â9NS+Ëæ®„,ÁÆmíÂ7@ÐWç—sß#øôU`¼žÉ‘i |Ý0¬JÃd¼j°ôµ7Q?©p—ÖŠ.PUTž"©LŸ’ÿhLÞa[üfibñ˜]ã,:~x¢·¯øúÇ“û,m\ 0Ü †Å‚:~ã9f€¾~ÀhÔoWØÏ.YǤ8F?탖à“ûwõ‚ÁM} ѧUˤ.4Ëúæõ~±GáEn ‚r!ÿ®ÁVòÆi XÜV£4\8åËóK/÷k "=tQnñÅ’myE¥L{pÜ;rnŸ[4Ù!°¥+âêj .TŸ?}ˆ(FÞŽ-’ïi7IÈB‘gÏ•¬ˆðÂ9_ómúªü<àµö—™d ; B#¦CxêãñBêyÁ{µ¢ºðM¨´yižôùGWœõóv. „̪i7ãK~IœiØ’ ÌY¥ ¡ø¶ògðCmfÁ0kiý#Èë'ÃáÆñ†ŠÀZÈ­jE?Y¥ßèLRk[Þ|®RÈM¿ËLÇÑÙC=±hë +(c$t±¡¹•> WÁèîlŒô)NHèá¨; {ó¶@¤…~ÿÓ‡OÒ™ÖÕ :ù†bb¹>¼ß$¼z4ÔäfHÈÝ»ëþX½»Ý¿íÁ (µÇª&^Ãr7Ëàiú«Uj0X‡ëFé™2†/ãw[£«Wœ#áVØ»J˜v¢ì&á]û¼í„†Ä/ÆNÝR• GFâlF°Cü¹J¦º€jŽoøX«ršGŽkH1OÙ/s}ÂÌ­áKü%æñ—ÖÏšÁVÓðyÀóŸªÜÎlœÅpGGîXUŽ‚(YOV3F¿”ìÓ­éßH‹‚È Q| <Ú?5qï°ç•B¿Í^â¢ßÕPXâ à¦p:¢–m€‹1Ç‘ÝÓ ,á( ¡: á¬áéÁ«Î&äf™rƒ\fÔyÚh•4gŠ3°hʧŠÿGàÈ _cÉæ` ·Fö‚ KÈîŸLBɇwrYâÙ♿“fjýñùP=!QëHÐ<•êï:£³•ç.«õË ,pϪ¢Ëí?ÈŠ{îŒúYÌf)Ûh‚XqBŽW?“º€—Ýð@þuY(’¼۲щsBãOéþ&×*œ˜o=ñ@M}Ž ¸Œ¬5æ|<ÌKxνó¦[2Ë B™éCðBIQùÿo4èήwÙß04dO@ó£0ÞÅB#mò÷ÙÎ6~,«]ûÈ"šˆ²¿É˜Ç¿}Cü/uG¡»H7xP1F¯4zAzÑjÖÍÄ1ü¹ÝqSä‹Õ;+ú®Ì¹å¸w<@÷µ$³¡wnvonUÊÀÅis ëðÝé“û¼VÂøF­sGÙh’›¤#<åÛRGúýç¼×™šTühMS7 ³¿^Yvˆ wk9MøáÑMÓ»u(ó=ˆF„òÍy&`1pšÉÓòø¡ßoµLm­JtÌ´èö K}"õ)i æ™]¶^bàb+U FÛP´SÔSŠÞvˆincL+E²;cxö^î“âXW‰»tá”.ÄÙ!"!nÜBºCÀ5Á¡C$XÄš.©aS³‚;ÈcÓOÕÚášGƒðœÑ@œ¦3÷lìQ¡¥ÁnC@W™±ý Öòãïf¥Ì¶b¸1uNe;ÆÌšãQî#|НrÌÆa®VexR7ÞS.›0‚p ¬|XˆÅ[Üoß7‰A ­â[ûËí Æj /©¥w!pu®‡èµË2•\ð·Q ð©Mç—%òÐ^'+ÓâDEž>ÄKI[7Hî“/Ö%&+RÒˆÍÀ¬ÚÕˆ¦'fõK7N´#s¥.f½lÕ)¸Y^Y )@Й«#3ÝTb†ú¦B ©\©‰Õ<©p¸Fs Ñ†[ …!€èV×ýâ¸wŒy\¦˜ ª>G/a7ðVä¿z¾'s‰ú®“Aó¡(bn9âß ÌÒš¨©–0utÔ·hß9ßTlÔ†¹åÑîrè›_ð‚¸‹¬ —Ǫ橠r¤Çxá’†8Xͽ#´]1‚¡¸çŸœÝE5A'ÞMØL@Ê4ÄÀi—`RYÀkTõ6Ð&äõðOµ)ü©àù’5W$šŠ8f® lÐ+Ž;ìÛ owy/¢‚jcg+Gåw6Yðƒ»ÿ#]ÞÄF8»/œ£Ïû q¸7¦R¬¥ /U—¥ÌÔäÙ¦²!ã» (ü"cóî;v'{†nÑÈ.®x»Þ‰!Ì^úTnöÚ~¼$;^…tººƒ £Ce‰¨¯©g¶AÒÌo1g%(”šj‹ìÐz'AS$M±ªEÓ´Hô>(Hˆxb¦ßju{µ IU[SÒHàóÑýo¤Ts´Õ¶½†Í„ ÝrÖ(z¨*˜2Ûdt×} àÏÇÆ¼$I)~ôrGûLq 2Ö±'™I{‹J{¡BÉ}|ì—d÷í¬Ì´)ÑU²»w=çXy[õœ¯s-’}o7SÄs|ä¶šÖØÙZ1*²ßc†tįÕÈšKâºêXµ›ÆTõVÜ‚æIØË³›«íŠÇî…®ì#Ñ/Ú·ACYÁnìæÓÍšÁy[©Æ{ô`Ì‘ w…²°ê]]"àI &Œž¡ÿü"+H¢q)Ì;>…8¢m}‡Ð,M4¾¡"¾éžP±øC=m„†‚Á:ít‚Ø“ÙÖEž-ö.2Þ³ëìç&¤I0xìVOZè\(ìø£ Ôñ+ÓÙÏ^ÏËð£»)\±­EÂ1€³¢eR+‡W«õç…‘ï8*ÌzŇgHóC};FÖüŒàÜÿ|xšÃÕäOô?ŽŠ)­°ËF$`y¼žS[¥ÈåÀ8‰º²Ô7| <[ªUOF&¡*¶ +÷°n‚Ò}I.\¹¼R‹AV’êÔ’Ü%b?vØå¾»µñ «‡Ôëʤ~ÐŒìðÈÐ,ŒadÀ*%í“îÓNoîYˆpÛ6;}nOÙ›š—õoClùÎY &öÊ¿ zKmè5ËyÛ*²>h’L=8%¸@ ‚ßšõêåsö:VNöâ£ý›/áqõí¸qÈéäz‡1GÊ0{‹9_'l…4(c…7tüYÙÚr•çÝnG†Ø¢åÁºîìê™ €Í=š×¸ŽoeÜF•ò “? Òçº ÷Sô”›Q Õ3A•“Afé Åg‚>F% £Û¨*ÓÚyvŽ6¤9z\ ìœsÒÒ¦‡â“®*ÔO­KHÄ”4¿È}([+ùöbÚ…Ê{–˜÷Ô^ÀKñÜ{µÜf¬2ñÅ•ŽPìÀ' Í&ƒIá¾LpUt}]_éà¥T™X äá‘¢7 SôZxÔ0f¥Ø`DBÚYX‡G,4K½_wÒÂæš•ù¤”ÿ «î² ãÂøç{åX:£"H5ž®\'~?—M Mk¡RÓ5Ó]Ó …:Ø}ñÊ,fÀÞR ùxJ·Œ‡f/õnaÞùÄ* î`N×&^¨‹|ð¾síüñ¶¯ŠÓ¦Ú+®ø—3[gœý‹c©¿bio` 9FŒv|›òÛdiNYJHÛ… ¯8mÖ‚ S‚»ÈŠp1ÎEl¢šú8÷mâ¼oj]#÷Z(&ƒêù¨bOï:[…Pò3B?}€b4Ðmx4’¨þoézŸÈˆ ³´=Š9P“N ˜Ù [*ZÐNÞJj–>’ûü;гÿç¨ô©jªÑ·+ïbìB‹Œ®‡¤ ät×Û÷L΢4˜ÿZ'jÕU1òªHÍ¡=«CH1È? ì8AJ àþ?`ó^ôþ(šm?©mž…ŸÅ­¯Œ,ЙòÉUiü ‘R.,&©¤w]-:wùÇ¿û$+~ë<°Ù*5°¾eÆ ?õ³;WB•HQ{c ò²Ì|†C\ŒÞ¯Ã¢F¸ÁMTxlÙ©Ûh[„ýá¥õ¦ ±ªFÁ-Ф§‹õãÛÜuñ:'Y¯Q=ú¼Mœß/»1R[Zùnô Ë9úHDzŗ̊Áð]´/~j¹8xݱ¥PÙ!ù†§GÌr[‹´¶áÄ¤Ž˜VűbfAÖÛ£dà½.æQ65˜dé¿®-¨ q:…(Ðg/÷¶ &?­½ž31¤ã[7CC$ð(à•à?hñwc…ÓGë³çM¾ººÝ"ƒ&Úmçú±—ÂxBÉÜs‚ºÖ‹ËÙ.FXâÝ·-¥E•q0Ѝa`8Q+<(Ì2‘¹>veVÔXˆ+ÊC#ö¤E{ñ¼Då;U“ëSrŠS3aE$û’¯"·íj½¢ŠýÍÅ,Júõ‘nW ˆcS/GdÑ~”Ö®“0" Qó xqp‰²y!’k²K/øv®Ïª,¾—L¥~6ï?å1M ð² ±€³|õ<Ñ0‘ø×Ý{^"ç{S‹i‘d“5kˆ”ôÜ|¢lvŒñBøÊÿsïp˜¢™Bº£½ÞT%ìÙ!W™¬À„¥ïÅ劣¶µ¡#Þ6ÛMÀ¡ð*HP>Ð$?’ÌIîŒUÙ~·@Z† Ò4€Çãì-F¼«Âûõ@@8#7Þú¡ƒoð‹Û$Á Û1w€ÓÊ‚s >A}½ðÊÌtg:~j,ͽš×<ΈlÊsöÆÀ>œ;¥ø(žÇ 'D>⊽›”³õÞÃÙÃL‚ãœåæ´ú–ŒæCw o “Ð2¨¿„/`t¹žZýÅ‘¢Ü'-LƒÇÖåFg,²½Ã÷ëâí7$qCrÙMÍœ>ÐÑ­ó4¾ªÇÙ´Ä;úÎt/¶Éd Ô”¡Ú<ƒýîìÞ%èýd °8§ §Ä¥7´K\H ó[î(1ÊâìxvF^Ûók£Âû…~NI%M+¦€ò‘L+×f¨] ˆc õ(&æÅö3ÑÔ†öYÉ~è}oèéñç&Û%í¶W÷VÅó°ÌlV>Þjß“þÖœç:ð³àúé±Ì¨(bý\lÐ],Ðò«:úå}!wdåŸý ÐÔÜš:¼kË;UFô|=*°íáIÏܰ„"-ÏÒÃL#A®€±ía7ÄAÚ™÷+¢‰M©gº¶sN>KP©0øß0·³~ç*NFÓ²R&  ßN4í„Â-ÕqÇ%€üËXby'áã=ïÜM¢(&r´*§Cíʧ´XÉûhƒ¹ŒÁ­yUÐZÖ="—làñ 3Ô"+v”S£dÖ¶º‡ µ%ocwä ó 4–+_M7òƒrþw®ë\üŒtèúœ 6òúyIE¼°HåÅ -²Òj =ÇÜeð§ É;ßfNÂä‹.¥Jýúuþ³Y^Áwë!e³ìý;@l;/ðL=¬âºo=_éAìg¢;@Ô§­aö"Ç‹ô?‘,)óÕI¹O:ypQþM[J‹™FEªŠÎÅ9¤!&ìod·<ö·È*Yáï™ÏÃBûQ~Y0;}¯9?4Ñb8™ô÷ÕŒºù[îïUíÒ.M&&H޾½IxXò.ò ‡'¾ÖÜŸ¸¸„§ù(…½p§éåÿ'<ôf@xüªG5³u2ä¶p AULª÷‹ʺ ¢§Êp^`»Š°¸n‹÷úyªúÁchùŠß½ù¨T!HËGÂÝ÷jÜjÈ}ÓÁüïÓI\ ½v¡’-¦îˆ%hm8çÓ‹=بaK•¦nx¨ƒ•ěØAþ÷š¾Àü÷Üö,Xÿ‚”2^Â×M†©äa…*$…ßâg}Ï^jÊ“¼5ÐBz¥ü¿Þ%ÐOUȉ¹fdPƒ uÿ?ÂÒÇô¡JM50âøâq&¡PCS¨Æßü鉢lI.|(éó¦ÑUô?g¦·Žqþ`0ÈêÈ…Kåëg£ÌÃÒ(ü:Ð`ÿ0 ÑÌÏìU^&È=Ô9Âhî&’p4,Ìs¤ÑaSpD9»®]­²¡õü±ïo„Ìãð|f„¨@¨>=ŽõÔ&¡lž—²aÌPØN^n¢5ýf±& øÜj n!½Ù³Š ’øñ™ØÖçò*ðž¼ê‘ZxϺn¤fˆ>rHI{îe¹V‚ç^g˜E\ y¾k'%Òr,þe+"üýŒ¡ÌÏ(cÍ ŸÑ3Nzm!_½ñ~eT)R"ïpêÌwE%˜¢Å•οñ[ÜBJßë%]-h%ÒEXL~+¿Éjyìïçä;f'æ`âùÁeÄé²¨Ø Ñµ2¢“œPäºáƒ(­<Òe°€ÓÝÿÙR®}éóRæË3dÂ9h7ãBlüêЧ›!¨(ýµËޏy‡NwƒE‡¨þ’ãþ‚*ˆâžÂ]ž+ʬ¿Ð^˜K!ÌÓ ÿ=0³F8£A®Šè¸é‹!y ÒoU¹J`t‹u£ºimŽCJ”„‹$aâ½zG½v‚9‘ ¸ç|ëÏs]Jø<µ¶ç@vÚ¬ÝØgà_%—}#ů·éÙ¹ªÚÌÔT?[|%±“ûÞLèiýbÅÖP1TÏ×E2}pG€à!Mø€§=…6T¨›ÑæÍÑÉbÿQ« ]ΚiZ|µJù ®Îýkw>Û²4÷ëå´¢{bÃ$ÑP{Ô»÷01ÒBS÷@$ªA8¥¥é„i¡c:âƒR¹^šL O1Cˆo×ðuJ%ü‡ ø:NØTׯ“±þV@þ!+@;qæð6ËIr¾yCß Zìf å ’à»%Û„QÃkç4ÇŸ\ŽBL^óqÅØD, `ß²ÖÉ"9! p’ß,áó%ÂÁ¶†pV0ü¯`Û\÷{+«+½ù3Iá%?JÔ“dA /s=H‹µJõ•¹Å]¡AuUT™q žt«G¨^E¿08ÕW,¼½c¼C a¶ƒ¸i½Ë=u·GŽÅ—˜È7!ZŽmÃl¹¥ï;ÙÑRa”9(ÖU¿T¼øa ætš©úÑçôð$¾ç²9gÍY$-”n/|Ðÿ?tt[E\}[¿®)?àÄ&÷`îÆÝFŒ JYRPZ ü£§Õ‡þµ A…ÎüÐåÈRáªZ†~Ã@¬ ÊXO’ãx=ÇÝ5Ô¬†8e"äêG¼=M…+è­€õ-`°_òï©;DæiïÝÆ.1ƒ{e‰öá^>bdˆ`QdÅìVd[óÍb@Œ4zwäº ›;Fs•B&Þ~pêÛô‚1–—™TÓ¸®ó^QK÷7àŽæÃcûÙmZõǹئk™ˆîÙٚΠ’Xh3½7ä‚B2@’ùˆ¸^ãPJL1ŒN¹BÏ­U–Âðù<\}ª8ó\Xoîg®Ð2žïÁdFR¿¼+ød2†’Ó*xùÅï×ߊŸ 'SÓÆJ=ÌàÖà[ŠH»kk¼V?{`<Ègnòž“†QgÙ…™:Ÿ:‚ó*]Y%%Ä 5#r¬d–4ƒ”'Ó!+u$˜­©\h×Û_³¶>îFÝ7•XÜ3m÷ö¦\º}Ô¢”1S8/ ûa"ïÝZ¸WÓa¥'¶öC!œ4åh~‰O÷ÚFP?Ú)ˆ%P9ç¯Ù-0)« ´•jø”€:âïæ‡òðÑ4wÿH½5Ý1à¾Þ‹ Èóî\7qûbj¨Ÿ2 Wã‡Ó-[1éÀ7½ÆŽ=O›¥0þÑySOÝíöìÐE$¥Æ6-ˆb—$,FÓÒi£Ö HÓ àϲè¦çnäŒ6õØ«Ôÿì˜ùl¤ÉÖ=DQ×±FúóÔ!j4èvè—¨Œ>ßlº]…qÒ9ŠZê“d?83¨3 •0q&S<§¢’èbMædcØd ªA•Nòm’45íÚÕå|åîåÙaCQ¾è9µC§¥@­éÙ˜¯^ (J»Uð vŸ×Ô²!€@¦§ £~h¤½Ÿ*ê4… Òûß™#*YðÜ“cáîXíK¿EúÙËl.pOÁþ&ßKÝ’Šìôš;Œ"¾R/Là’1ªKéOy%m³¸ó4rðÙxUÿG¿²Ø&âÙ¿¡ ™™Í =½»^UTŒ ¯–ùš¯J,™ %ÒÓ€k'p¦®Bax9UGº"þ:œu6xÙ~U"©fÚU³#ÉŠ#Ÿ|¡¸±³¢.ࢭùxîÒŠ(ÒÍEÉ~2cš7—éf–‘å¡kéìbå÷¡FÆÑËÔž<ÖKC-ß ÿAø /DÝGÈÒ“ÿó|þVâ.ÿk‚¨zK$Ä]Vu×0oo W`º8L°+\žûR7\R¼Š É_òì"c³:'—Bm)ެÿo[8R4$>R£ ѦmÚê¯ÒB£BX™’;(²DA$êm”gØÎ×÷Ì×&¼m8½•ÁvÀh›ªNä¡ò¸^Áµž'V>±‘gµÆdþ‰ƒ¥Ì¬iöBð¸”ç=‡îŠÓ"­µ«ÅZ¹a©¯½9­›àNte¹íöµÇÀÁ³š€˜*ù`Þæ1‰#vÝ¥Kàê˜Ãßä1Ò™W"ؘ ³• jva¾d×MÃKç|,ÖKZhœªóíÍ=Šï*l ½ÕüOÒH¶qú¨IÀܳöÿ"˜g#Åå·L"–ìý.oK9J®•æM/J®°N(J³‚íøQ"dÌײá÷dý¼²ÀtÞBÿ Í«d;*S—³’W¨>\µ\)脸j°»R¿ßËËA(„ը̭€3µé-Ðãë ³_‘ À̇öYð‚Ô%ÄŸ@=•RîrGž4ÈÇc%Õ|$ ~g~…53j˜-,¶»_¯=ÁU41Ü7/…V(ÿžnòîÖ³j$Ø£èÙèÜAÇá¨o¯%Î*ÄËêòW?ãQ®ŒP([Z”Ò‡ë:Í~¢‡¾hµ8MRÔ”Ï[K÷œ»!b_ÚÞó­?—û=š¸HÉ´J:™žl‹*S*ôI´„»þ_ ‘ :s”*"vø™+‹ƒðë¡…²8cöÿKX¦0Œð‚ô8ž\ÓYñŸ±+üQpBÝo„ ~\b¶Lèfð®(Žu8þAhIpåìéç½÷Ží@r@hÈ6¨OÕ­âq§œbJ ˆFy¹–£ ‡ážñ«kž‡Y¯€áï*˜U‚.¡º‰Ýî«…Y;xD@+|TÎG7±çOàߊŒKÊP¤ÏLZ}ãç{ˆ±„~Û¼íÍDE{ã; DŸ‹GnãñÈ—G³\ZÁUMò°s Ë}70£ Ð‡"oæ¼-ØŠÍ7òö¹Ç;lð³Õ¸L¢ÔbDK¥üÓ™ëe„¢W€yñUóÒ±m.v-؂΋;Ï"ú¶ËŠœ»žÌï­î‚ ±èò'ðhŸ‰Ö!T7tÊ|ðÿ©ˆ;õn±3¼”â"Ü­xˆÅ,ä×ÌÐwP‡¾TתðÆÀrö™´ÕÝoÑ’!VðôuºœÓ;îì_¯éS:¼nJ¶³ƒÛ„}Ôgï2N{Þ—·×ndT³Ä¤™§ó}´Dú»`ï·²_áÒðèÞ‡ë¾}]éÿ›ÿa®êï7CáúæýºÕü:¬ŸVè~ÞŽ/¥73ê ï·´nû{Iþ*ÿ@p|?[wÛܧðÒïøk ú»ÀÈø~¹ÿ+P‡USêè+õtaúˆøßm }½£wÛÚgðéøt[Ãö=ü;ìþQÿ³ßo N‡Ãö9ûuGøuA>­ëý½þÂ÷؈aÁqµËí°~8Ë‘©ir<§ Zl}ÎÁH5‹5*#ÿ[PgEn$~™L–‚?{˜1úÀRü9q 1Çèæé4_;áȺêjd9z.z|Ò©ðÅnxú‹zfvr*¦Áð{Þ0³û{)sï%ÞÊKSØ›B;ôšÙ˜5ιÚäúIñMm¹oI7ª ¢¸  ¥OÍÅ#Uà1€³Ù ²6иý·„ZtXƒÂW(4l)Óu Ó¹ÓŽ‘Ü$±«4\«½úÜJrh矤¼à³wD…óÄ·¾+Þ¡x¶i n§H+’«¯Z¶€x¡´ßÊýàÑj!|…D€àø/{I>8bžûtÈýáö“Ù2Mj矈ÕoY~õ ªyWÖQ t~~Vn[Û˜ŸÅò±âyØ„i   ޏH£#Y¡*Äc^¬ËŘ40z÷ð·M±SÁcô»Îf*¨ É]yê—ŠëBt›õÖÁ(^æéÞw¥Ç…Ž/š·J÷ÔÆ€©ûgØÒáó80 S]bT>ÓïžW íj3lì“'+YSCíò†ÖpBíf±nG&íÁ)R7)±<톒ùøxÈq?-PÎ$߸–õt#’1rCÄ,É*«ºz`®ªh« ͹¢î¹±°#(+î‡eëù¶ö_Ó‡¨áB}œlr¯Îι¬"™¤ïѰjGÉü˜–DÃÀ±¤/Ø; _U’„âÂâ˃KÄÔÎä#£aϱõÓþ(ûêsy~iMð1$õ5ªÉ­¢Ãld¹Ã{ÿKǪ0péoÀõnܘ‚ë&OlÚÁ±šºŠŒûºÐ˜Ûew˜8n75pf«í,V®ÌçYðJZ!ÁöëÐŒhÄÐ~Z¥,I-C5Al‚GþÐ0ðK§- ýZ²ÿscañÒ%nÞqñ–œ)XšgWêÏbÂ;Ô$`RÄ–mGºÞlÄaÑ ‰ƒÊ‡*ÙêûÁ“Bô"$ÀÔëM¬ýÐe¦lɘT~(+ vVØ–rEr„› û»ÌL7 OüqÝ:X=Noéáê¼ÃÄä¢c¢D F·cÖý:2z` °ãÐŒùD"=ëÚÏ:M‡”XmU`Þ 5LHzÌÐI×ú`s  ð­±(}óÓAâ´pcÌœüWª|krVÔoB¸îC`,Z¨•YÒÐ^Š¿s«eqR7„#±pçúñŸ»Êˆ¸™úÿ{©¼?ïc×hämŒÃù²9´¥¬Ô§Vå¡eü“ï;7Ïšqç*en»Q—*ÿ9鈨Y["Ýá·f.È6àÓªm’ÞP åÑ6ƒ µeývn·ÒoÊ+ä?ßu¾×I ¤ ±š-©9ßžl¢¾Ä˹¯¦Öü·•  P«K…ÿu2CEÌi[{w«Üé%“jyþáW샱 À»Ž±½? ¥ "¼hP+¶T©@1ÌÄ_öbsþƒxK~UrùrF8tOÚ!kŸ¿Æ×0ØÙj²Rß’f“0h¡Ü9lÅÏS¾‹\_¯Ä÷P…A„J\±â?-»Û ¾›w“@ï£]ãoXLWö¤™›ÃD=Í¡æa•¥5rW @Í ŧ· 1BÏ|£¥ä‘Û_úÐó9ºýëã ‹£ÔŽ Bo–ø2ÐL*Ù›êÜÖB ªØÖM"öCÛA Þ“#%¸kIªíÄ¡lŒsaŠê3;9ê¼ÝLÒÙ ¼º8èÓ †,tñùúó¹“$NÔ ‡&ÃÇõ%cï C>8nx' <ÜÚ’E=µ² ÈÐòQ•í±€ª‡_È„OVõ’_:!òîÉÆžÜ¡²0ÜÀkŸb–ì—s ©-1O z;1ˆõúP›K‡“º¯Ù±sòŸ"&O[‡Óq³9“;Tº‘>JÑqeÜN”æfã¨Á [ëóW0VìÌ*–;¸3œ€ižIßí·†;GÆã u2þy³iÑûetÿaºÃW)z€ê…‘¶Zêu»•LîRÔÙ/T6†ÿoŠ’’a9C=p&¹÷« ¶L#v—éǹ82ö‡.4Iö»&E=~ ÐO;¶ŽUfqÃòö Xƒìu¾­c…Ð<û™A´ùšb)w¹ôê4¥I’÷—VÞåŽô[ÓùØ(a-o(I,Izäjù×í}Å6 g¿±nÆr[ê¦ÔT1t½ãDK5]n#õŸü´øCôG·?Ÿ|¿„¥¶’ýHÃA’_K.C>ÚDj[Aµ^W_\7F„âå +·Hï\ÜQ’ŽÊ1Ÿ¾Šî¯ë‡FÎÀˆA%›…_GÀ§ŒžªÅyKo:0l (?Q’ÅfÃa‰/£ÅRmîÝ‹&TÌr+¯›,À_ ç€žµð0Ál$VÒ,$¸¯u}Òæ¸Âfå±mä..H¶8Ьë9ýrŒÿþãÔ”½@*ÖÐ%~"§…Ø’ÜÿHÀòžYÄ6¸=¶0Ëh]vçÑp…{ÑP®|º Õ þè²4¸»³³,&õæû ͦ§­èÊ­'"§x.9>‰Á>tµ5äXqn«¦ƒ@ö”B $|N|¨Ç^™R¸K¿a<œÆJMDUjRc;‡¨iÿxóâø¼Uot-Ë×XŽÝ£,ÿU©­X¼g±PÿJm•í¢]ÃBùÑ)WØûÜ)×Pì¶f&2R/ñ|,ËE_ò–4㓺ô ¤dÑ $¬JOÕ jÙ)ÒSƒÔëÞìŸgµ1´»—f‚œ–¼Æ¾ÀI¿YÑZhý´s¼»7p5 }‚†Çƒ*Žðƒèb¦ô`~ü4& šÂ‘ØÀŸºÂdA,YóSÈw3Xv m(\jÍp•ÈMS+&V_% ÁTL\±/"=€ëd 8¨w$zàÒÆ™^ÕøÊ£Û ¢qÎ'°Fª¥Øl¯§ß™nÒ(©¦ƒc<+{Ö 7>ÉäÚ^×$b}㢕\ ÞˆrçøLÕSáÄ3Ìy‚ªåyUÚ!#îa{*ʦA^hãL¬}ƒƒÈ„Biî­­Ì¿Ðñ’rD‹9•‚ã¶§ôB*[Ÿµ;k™PÈžcÏîfx6 /ã/Í"åæP.zšÈØ•6@¨FVW­p7ÒÎ9Ö#‡Wª ñc£SsÑê^ŸeÔ9rÚ´6„‰÷Ì5ÄýŸÔ`<ôK¹ÒGéØrû^²&ô˜ðš@冼øQ(„þ®iHgŸúFWˆÍ$õÐW—FoFÚ‘°‡¥ävüË$Ú ÒŠv¾Æ¾~å&íæ¬WóqàGBe/]rÊŽh&¿­6?žÄSwŒJ­Mx±Ëd… ÷…ÈË »K8U_ê5áŞأBJ…óíM=žP¨Y|Su£ö–F’_õmU)uqwd?Qm1àmâ7õ5H_vN;‡ÓòŸ4{˜¤7c]'Ç/pêhà‡ÐTß„¬(³xDÞ' Ï<ÿ*)}¯¼9ü J\óá+y\CY&Á2“ÜF¤üì$c ƒH’ø-<'ä“ü³q1®VÙ*eX{°t8m¥µî@’qgŽèÅFƒ(ÝnZ,RlbæfkœV·¥ÑâF8˜úã—=š( 5Ž!.Ά Uö]ƒìë²¾º•‰ÊpøôÈöÂ]<¡*º)hÆŒs¾$h=˜gÁߘëïQ ¥Â%YTÁt…ˆÿ ‰ ”m.¾Ñ„ñ‹—)=Õ©Öœ7ìc†dß­5À1·5<¾ßš»f¨>ÞhQ¤…9~pû7¤ßF /ΩózôŠ>kÙ˜Aɼ ?ær<Ó” „°Ü½(RAŒ§T6®Cn$N©/àUÍ–¹–RÁPe‚¿"Ê ‡œ›ô›VÁ¾rF)ÈͨZN¤}i}Wnb7;sݺ\Z/ˆ­¤ûºÆ³é`´¡V"Ò*¶'D^1«]ëHëÛÔèY·flEýÍ|¢PX¯»p½|\—Y:ŒÑÇþÇÔÿîIÓÑšŒw®X]šrrp欳B»y+¡'SHt2Æ~Ëe¢LçxáV«U?2Ff‚>73cB"éab‚žÈ³ÎG¶èªé’ò–€²¡m#•CAØ·ÜsÙì鎿1ܰvt]c¢uh3+ñ™úÆw9"jåå‚ÓB´¢ÐÔÜ0 ä o1N%)×£ÐÊ:iž {D+¬eWO_J '¯ATÜÞÇÔÐZ¼oúÞRˆ2Ÿ¥ÓBCIáE-.‘‡]ßv¯Õ×cuWÌ9¢º½#îJj±^®‰ÚêGC™:cÒÉ9‘ŽH‡¿îã“vòM¬þ„ 6ôŠ9þu Æè,tûÑ%Gš‡ýÓËP¦•öAàÎùC·’Ë¥\Æ÷þýj©‡šùÿªËëÛÖ~AgÁtNþ”é(uBiÛ™¯«ñªŠƒOüž"Øœ’E½ˆáÔ*“0„õdÕÏ'Сî*Ím{½ ñüÄ1háîz + elUÏ36M™F’d;À=õo]Ý·„T·e,ãòµ( ’PŸr¿)«L2CQ^².©Êؼeë•ñõR À55 ÏU‹PYªÎáVÑמ6,ÿ^Y ËŒ «0m”+Ù¾¢o‰.ûëÜ÷_‹‚NÁß^õ¾å/*ï¬ÀÇMq€RÈ!Ý¡çqç–Ë£˜œ)¿Ô¿öœ…C¹.úOE+6+âwdOÉÀ§{ô1H·¸ Ɖ7! -Ù¼æIb€ ïü­Õ>$À?úá»ÍÇ€ÿ=µQõó#Œøþ‚>™«¯»8¨q³¿èÑB a@¿³zÕ2uõˆª›?o©ðJ‚Ã÷Öó5ˆÄ+1`…ÇC‰½º6Tí&†ÜMwš¯Å*Ž*R(P9(8,Òžn·‡ŽÆFî3‚—PFÙüBœÉS’IÁüp/n»u˜j‹‰b¤Yu:¥|qÔþâAÈá÷­.÷ó;æ×‰Ðù·p¼¿ðŸãO†XSmª™d' ³:G2 ¡ £øê_G Š@=š·a*YR¡1ìåÆS;Ìø¿½?Ʊl ½…»¢êƒr¶çÏÐkZ˜.ž£”'VÞ{†‰ypÏ]Ö•ËO¢¬]råÙ¬*”áËžædýUZpæs²'âw„•õo“!aéXžGÉ«¢YKt‰ñaGõãw¦ aFr×êþ¢{xðt¯äcåR¢™EÅgI7Ž?ÆÍèQÙ˜7‹œû¹~_Iš u5…—|ë2J:ôð/QŸÇä.èîl/6÷¼D‹ÄLçÝ×Ú]#w‚ì‚f·Au3%b߈âŒI`±2qcö©cqe°!î°ž Uð㦩M•½N›¿ÎBœe<ô ¨ˆo©‚Kæc3ÖjF-FtH½¶hƒñÛ£»nPÍgá0Ç‹è¸BêF÷¼"‡¨Ñµ0ÀðÖ»(±Òûn@— Â俘Õa;dq?ås{yƒÖ)<Ê.ÆYë†6X¼ˆaq‡Qûæ³:k^ Ó°àQØh§ v‹-Hñèsào©ÀÝ…I&@¤„£#ƒ··õ6ªZ‚®PÝÝ­d8‡ƒ†Ö_¼ýv5ž…'¢d½Íû’€Q »|‰Wíµos%š?±OKPjþ*õ³Gqú¼ßWKX°¸ÔÄÄt•€²VXÅ^’oñ€²ÔD6–.FºB­tü¾Îÿ,øÙÎô›,G*ÇhæùQônú€±±N…³$€xXЫ_)îô¨>×Ô’C¶ëà ø?‰1 Z¸Tz‡ ³ã°íšÁ°)MV5h¿%GAµ…$€-Oâ És5¤VÆßшN ÓöçX´‹>ðgduÀwÓÜkª¨hÈ5‘`!·‹É&5•}±I¿qûüä">]5±…¡ÊÍ Î?…?Wä\‡¼aPSB¾»YZGž›ïZÄÀÖPl«åÉOópÈRr&p‘îÓtgïðhJƇ­ïç%’õ4tâ/ÂU·ÃíÞV<-p]b£¯K@+²Ò3 ]}4£mÙºù?Ò’×~¨?oè“åjêæ¹üùuEåqï}ÂpYÛ_ðIa¸ßFÇêpíÊVm ®’æ=t.&žb¸…£ŠÈ™då{*§³Ýºá;Åzžl”sþÀxÑ?êIÞÌ Ë§|ßÈâNâäQü ã¤Tn^KUú„Û/¨‰'‘üÿQyž5ý r175*³û_–•ØTªËJù왵§B䤀8ºžMxºÎ{ ,:kÄ„ioÃ1ñbút{¿ר¢Àf7ðÊÇ­Þ›æÔ·…öëD2Ÿu'Ó0A ]묢R÷‚™Ã÷&·´„ÁS}AWyˆ}$7ô‚¤Í`‚—®•.~ÄçÝU=äd\•ŠÓ—d±ãyÀ­zÕv|àV;dBG^bÚIoýÑœÝuûµ‹6*X©÷å(Ò†yÝ0 ìóÀ_’ä '.¢7žµ_ÁÞ2;Ç„g5ÈÆ‰€â;à䬮cÔ2Ô ÇûB3jÕ¡Nkabu—Š%Èi’vDL=¾mMf æHÌ{)̯Ÿm_Cõo´ýÖû/¨v|áó`ÆÜ¾’aùÒsª¢2MÍ3._Öõ¹E8¥dÛE,Lg\Yß7.ñÇãîã,…4G!LJRãp›r—9%y9:ãÀþw϶ /§9 @G…~Eýž2Ác÷¿Lì¤o¢/Å!f©Š«Š©_ÌpÆüsét»’>'t¹lO–0.¥ ³_™>pÈɊˤD ž°¸íô’d#DGÑÖ‡ÓÅÝøÏLa]p³57¬¼šçÂ5µµÅ½u¾mî ?òYD¢jº‚öæcs§­¾@Áaô¶¼O‚¼¢þÆ/Dãå!ˆ Øâ¬y2·˜ˆ»ÛÛÞ‘–'@u9ý•7ÅOú(cúp ÝœºX7¯öÀèšwÊŽø_ª€üü½z­G†AÎ<‰÷«wŒx m˜B.uÔœ–Á°ZÄiÚ>K*ºž*0"°Ì󫽇Ø}Jùq6ô[_¤~p#TyxjqFÖ–•뮕ßwìSUšóv£‘™;À¦x:22,g€ÇíÂx EÄ÷#9ÚŸ] ¯G­"Ö&‹(„ËâÁ턤~ÈK¶´šsj[âµ/¼NÜ(Ú¨¬i>-£ôåDb"Æjí'³“Ù3Hv`‡Î3«j~©²{Ž0E/¢J… žŒËÄSEê÷ Þ”rðp€‹éeaõ;ìÀ_Ô¾,4ýÏ×D-rÈä7äõòV¹2œ ×ð¶šZðk_€¼5)!cæLÊŒ'Cg†áhDr˜¡oZÖŠ±ö·/è‚Ï«««‚'`Òîãgð*&‡ÀÂJ/{®d¶ôõTÚ-Éñfˆ;©)­2Aé•Þ§(ì;Rbj̇®VP¯2P›Þ_ÙÿYì Dæ»S{xŒbl7õ°j'2ŒB¤ù;Œ ŧěóN……>hKAüœ‚%›;‘Ò4†+HŽ/J&&ª‡Šü³{I€¡½«Û¾¥)Qv? ž§hØmp‰UÕ}–&J»ÞI¡ìžO„Ã"ȸFª™öQ’!y¾^Š^4HÝ1ƒ¼ðC‹æmþ “—Áè–šõ[“K5à팖 7Ÿ- F4jj {œ×JÏÝ ð"ßAG>.Ƙ‘Èè&0¬éžô¡!ÈË ÜÆ¦Üæ ­íbbìÿæÇH¸±ÛýÚR¥Z~ŒÄvÇøßõzG’V˜%d¡|ŒDC…5Xú‰šM fN!‘ãC”Ù:žŽMA*Òùâ+±)¼…íl6O7é{Æ8!þp‘Šxmµ<|6!ÆÖo.ÒØ[wºî*À‘5LîGÑÈ lœ!È­2‡ OŒÝeN'Lz2Ñ,ô­+!qr(öË™G"¤[øý5´'ˉʴü„F3r ÛÇ‘íÝꙵìm­øL4\þö¯»ÍMRFØ×Ñë·3Ý+³d9ˆºdCpxíc|—1ž±óg5 ®m~ÇZ)…ó™ Úsm v²„«)=ÏEôåæœFчýs”‰ 0O%ëC0³0tÌ -v­qØ(-{˜hÞãüG@}=öyíÛêUÀx]YPeú-w%—×§y¢oœå½?"ÑçL Ê ÿ$Êe\F,¡9^@QíNÐàx4àâA¹ÙƒÓ(ƒª÷á²n…„pÎ n[0ëAN¨MI6Ó…6í`Eäüe„6·fba•¤zîT×#ãCƒSždy YÌvÑÍ·&3ùámèD­4ùŸóqDˉÕǧÇJˆ ÇUö8‹²>=↼;̤fÒ$ðyr@64·&‘I · Idû"ú=§§/A X,+UINöM3’94£ÛEb`ú­ ñÎèdld³úNˆ™.=¯ø¿9Ópf9ÁHƒÕu¡”‹‚}2ÄæÔ<©‡5r6«3bfDå¾ X_Y/\w#õÏëý`„Í"ºVo ¸X|‚Á —Sô )‹ò–XØ"¥IŠüì®´|†E+$ `)Pá4ë6™àžyªzIƒßÃ'8>´PcÐÆ–÷QøÚWóZùd;ë½yööhÙ€n OP‚.‹Ÿ]šŒN%?ôˆƒ~¢´9Pß)™Ôìê©¡¥}†#_^Œ¡OlblqQ̲.’i ˆÍ%¦ŠŸš!œÍÈE"wû¡}¸L ñEÌB2mƒ[Cx Ü cg ¦fû' X€p¿Í=æb¥—„üeÉN‰U ËAS Y<ê´€hÕ;©wÔWöôzô±¢Ï5!ô£ªñðƒŽÓ|r8'ÄÌðbÀÖàñoîß눚Ã:Ý üù˜zìX61‘IB.«®o! Y: ‹7+ajOnkl„ËP"zŽÃoŸÚ$É,¦@ËL^°êìê ¯ƒ¨fik üUaUAêþó³†a攪°²sê΀FžG3=·æ…0Ìe¦C!qˆƒD`'ðóo°Ø§ÆsNjujA¯¦iï$(’æUülxâ Þ—{—ÔŒÛó¯…þ‹juÆæ‰ŠØn¹Ô?Ëç«Ä‘7‚ûã÷®s›ÈAÁFi3ß(2‰H¹1ey¿ rÎ}ÃùƒËëmž6öY§ã$lÚ'÷Ò*íë‡Ï|®£«\ù'|M,c¢Ê}Z{¿ûá[¿¨þðÜØCFñŸ(J$Æh’ñçÒ³ÈH–"†_›Ý/{`»sÔm9Þ ³¤å'µëGIc·×DÅk< iy€Ûš..ê·+m᱓–qAb™ú4š`Eß¼«¾Þ´® 2ú%PGÕ%”»YžÂÖûy¯+¦ú^gËwye B¤NZD‡!I»ä 2q× ûþ!¹à ¥º4)4ò¨+^` VQá5\.1Q|´¶.¥¾hĘ›ËõøI6•ïÀCóßä­v0»š%6>¸Ó®QÉU >AÔQ{ÞéV÷Ê1ûiì‘OËÐñ…îÌPyð¤¸Ï›}¸àÜ'¡¯'’y—zŽWj1à‰Æ'ë,8ÓÑ” òYÌâפ$ÐÀ4䊤&•¯¶çLÁq´T!ÌÕöÃ9in¾œ–côèNó‰_å’ᶇã $}ûnn‰hýLMä’)³út ¯€Â.– 1Sœ –ßë ø.îÙþgDM›RkÖAuÃÿó…2ÑÃù?%c(OÜô!Ú9@ÞW96ߊåtîûÚ 3A™4 lS*'ñÍsýµ°B•#¨»#¨¢½éƒÕ&qÞ=-&t@q£P IŽª¼62¼$ž«¶ê”è–/Eëk†˜pœ Xïoa‹˜/¿=®Å÷Á,Šœ †bqKò^g‰žèpÜ~†V9@ 2{»YKž¼Û‡[Õn¦¾g¼ÄVÄÿb¬ü÷ »$ß‚âáµÈ+ßoãئ>a<ú•€\Q‰ÿ!ÙûFylÖæÃTN,ü–£0/?5Lx['2š²;[å~kÎï”z.0ÅT„TzÐ ó4òô{fdî ©Ý hõæd¹D2J¯²æ–eÕåÙ[º§õâ8 wxf;šñœ)7£ÞÏ®X§ô²R½ê„PqR ç¾j¿×ì –Av[R4Š`š`b(¿×è¡e6‰6Ã,j¿&îð3ä@À…ƒö½6ã­Ýw>`ƒãÍAruuIÆQ›Ý.*€Ë៥flë@Çöé86ì A â„·yFdáÌxˆ†+ó*¨­”0ç'k‹F©ÎÁÂù•ƒ/ùœf VBöý/ëVž<ЮZ ã¾”9þ W2»þPØölOD§Æã’‹†à8”–Û|£Ÿ4÷ïÛ!Ð&¿6XüÒËÏÉ&ºX¡˜ëŸp¸¸õ½|hHBÔƒ|¨Q‡f8bØŒå"n††“fTÌ%›­l1†qçÙóeIeº$Ò]¹ä¥†±)væÉ;­‚­’ßô‰0¾Âë0qàáð¶' ÷.qúZ¥Wk²|Ìžn!˜Çž|Èå¢ë(\ŸÌKàUõݨü‡js-·ú{º”¯´i<¨X Q”ªÁ‹¤H¿»&‚Á‰Ê®ý”JB¥M}$1|äUïßY"3Nƒqþ*q`";£U÷·÷eñ¦N㈖T®U+±ÿ„Ø• ËÂVÔAuÇùéñ&cn¯>få-&)Ài´ÔQí§¼`žÛ”Ø'ŠâCÈ8MŸlH›Ò#ù‡7¨nZV{1ê|&I“UàÙü;Ýûd5~:•ç1|{“©˜Û¨6Î)šjíõudÆ S¡?['—<¬‡†Æó CiâÞÌ…fÊ_öv6¶¸ä„ßûý‹m«@cÛb’íÛ¦Í3,=§HÌõùjYyæ˜Yãr’7îšÚ´éƒ»Õ&©{ø¹¦t×ÓÒœŸ•dÝœ`YG%%{‚dfBœÛÅ÷—]kêàÊu÷¾WÍ逇xÜÀ‰ßÝŽ›ŸÃíNAûã¤óÁ—ª¨Ê&;p•øGô°lÒÚ±ˆ6[”JÙÖóéŠë«´û1{U1ël}ýüxmf-¸T]ÊѦÆëòEÏGèg>ˆ~ àh2Ö‘ÎÃB$NÆHIRÊÏͼŸ–°šýþú­°Rçø«ÐŠ[îQRŸ£õ§Ú÷”7<ùñsc`Ój‚Ek^<+@ÿ+BO«låžÆQ—ÀNÖ„‰JŽF¹Ð¨sè$¬2›Ñ&êõ«t(TNбè@)±ÆþTZu‚æÙPÕQ*Jäà°wß ã›I;7…ŠŠÌ‡Bì\mÈð`Óãë¾èUཋw꺉ÓÇ£º‡:¤ÛÍqwÄ㸄"&БF%¢š«ºˆ‰Œæ}aaÖTï›ÂX‰L\›Ê¨ pàX¡ŸÇþ›f›\Üâöû›ª·,¨k·»à™Ë>Ssc'7Zo»›ÌÌP_¯[÷ÓÊ dƒœE˜ú”a³MyL]!W;âŽ4#y¹lÃxy—ì0fM~Ç.;¹²F Ì|„vÑ¡C¯ãŒ(2B|†G/Ç눵qTüøVÿq=Év÷\™°]¡‚-ùý <"¨ûírs&R'YHüK«*²µÓŸÆm2ÑOä©×‚©`ÕÑ!ÚbcåR—¶,Ü2cÕplDén­„.ÓÊ VÀŵF÷%ˆ¶´G*7Q!Ž•­ž^ö‘˶ÙüÉ^±åÊS/†à£?}æ“T­Rí›í©þÞaÀŠX–gܺ37xçÌŒ J·bÏq€¿¼aã×Fùû^e‚­öÌÍÒͰè4:5X f¼á ’“ÑØaEuÜòÚAªðáa±mÆ8ƒ ‰B«^BËÜã•ÀÜç»Å ì<*û ý™ü°¯(öùàAØÉêŸãJØ&ÛàX»Q…¨”¢¿°®h\ .KÙöþÞXvÿNï¬ÜñÍ÷–ËLu)’·¸›·ÍbœMj›ÜRªËtÓÑ\’‹/VR®&èQBÛT³ªX‡=Ü&ó i„_Ëã}Ì……²‰I1z‰-¬~”Bìq0M“mø­:‚Ð$Ę"kåÞ´VM6Õlq_:ŸYqÿp}¥@‰ºÕïR~ [œ۷zñâK‘‘þìÆwKÎf‘Ú ª¡[Ƶу£WþÆX0˜gú’ªø +Õû›ƒuÕ4êþÞ,úôeièÁxoæëÅÅVIµW0,½¶-Ö~*:Ä "èùŠæ¼ÂuX$ (xˆqŠ»A)8ë0xÀJò%6úGh"JbèNX-=¤Õ8 C™‚gdÙó–ˆÆ`†7‡Á©Qc Š÷}fä=q!5éÄ;¼#§-¾^êÇÏÞ¶¡Ó€ú©$ÜŸ€ªl”‘ANKçÎop0£…=ÿ<Ñ ºlˆýV@«cÆ6C¤®Â$Ò?ÆŠA”U¿y+­¡ù%!Fw·ž"©JkÕÜþ.åánXM@#——<àHí1g–D‚å|Ýã~n. Ö3Sfb &¦Ýþð†hAæä@£ÿr£ÒÚ=+D`YxXâ+n n“Oì½™Oæ³Nï6‹SãfYC³¸¼I§2Ø|i«[‹.ÌÎü¶™‹¹2¸¾ŒM깯~ç*Æ&Õ+$c€Ö9|óÌÜ£0_<;m‹ùެ‚ &E aåçtmf0y¦'9y¾™u‰ç-|êœ}ÿ~2Î,\_éhçKóÂî1jrV®S©ÒjcΩ.¬_£x~qÐ#\aÑvm±vÓAØÂÕž§a3Õm’MÈzôJíD¬è]E ájh1“É#^ÃÓ̾cÀbb:zòõï×*îˆ-X)¾¸UÊnÚa'ÝXô¬æó SþלæÇ¯£«‹j;§·9Vßï$Dÿ+&7~®&“Õi%.b’ÜæIcT ”òô¡zYÛ‹Ø yîZ¸¨l' q5¹>Kzä—3ß û[¦†ÓÖiu†ŠiXök˜˜ø ‹þ `y Y0ÛnßA'á¥Á6Ê^¤ÚA|¬qÂhfÈ<ª/òb6x0i˜äw‘>EÒEäý©Iœ`,!Ïëç :”WX+S¸ÐÕ 7 úŸ¿*Riïž[в±Þ¼á½U7ÁðLù£c·_V‰¯£:˜*7™¶pÞIELZ”®[§ƒÆ„l%l’‡ÙÑ~Éz®ØÍÊŠºv£™9lÝþºÑ}UB‘àMŒ™Ùêó³/L  #Þ׳­3%}4õ2rÉÀw¨ìÍne×àõ™ L§pÇa%õ“fXKåt •b¿àu"מ‹„Û©†#ÌÕB_åRÃA¥8GèOBÚJ¯FJOó왽I;lq¢ê¡Æö¢ä!ô54ü£µAлK=ðWñg<ŒÅ¿™˜ýëÙ²cð'×+ò¤‡($¢mTÂcwP¼2]Ê+°Ô*¯_Õst9ãXAöÊ÷êô‡¢véXl¬Òwù.PïùÓÕN‹ì|_b)—{?–HîDœG¹Ú%Ì᱿i¦Òèï*+qœÓHQ$nÅG4Ã0NÍÙÉ<³ë=gã©æ¿û>ÔðM„ݧñV‹¥º¬§Ö𡊙¯Aœà5¤  iDÛE”W¾¾ Mä/ºéogÅ€SÕŒ”É%;ֺߵz¾È–£5yû~èJù(GnÓOÑÑú‘c@ýè΋ÍTê¶h™”m?rˆ ðn ü‰@t_¤¯tð®6Õ$Xw~ Z»ML]”5âµ?Y”1¾êý×=»MA%xØ޶K¾¸Ã„A°ÉÑßY–Z×'½ƒ>'°ÚÔÌò*¡å|R³Gò×ZÌou+IjOO/ZV°m¥•® ¶Ç‹î¸¤î šÌè Ç¢sáÛbÝË„ŒDËw"#Ôê!KIzˈ±í!§ƒef;z©ž£³¡ßÅ>‘AHx„!Ò××̘Þ1«1þë²[‡×÷o›Í¸nU¬á6Y‚dh™üÕäN!¶R¾éi5Ћ^ä¢VoÁz€¬ÈÉCSâåJžÙ‘Úþ7'–wÓFƒ[†Rµ6OoΈ\¬kz³‚ܒϸ‡CiSHª÷t‡1`^ž*æ_ç_¶1”Q8Ûu éù^4Ïg$–Š¥˜OjÀré{ýœ’ªd¡£ Ç™ñðü­ê^‡P¡ß(0µD\’ z3Ÿù•ÌBš§/“²?'ªÉ£G>V a’‹²aAdÕxht+¼\Öþ³ï üÓPÅÕõ6´ëVÿX­½€ˆKän.ݺì+Æ*Ïëýå:<=ßrˆöÈÇjD뇅=ZÝÇlN¢.‡þ6X¨ÇÙ" ØÏìY/O5ýî¶’2Øœ—$ºð—e~†Ÿ¨ëÑÚuºî¶_1 wkGâDc‹ª×#þFÞtUYµªšnÃY7¿q„Sb¨®™sî œŠYƃ…*AÛá/µ©¤¾¯_éûßm0Eó v™jKRÖØûØþ_ë¸0»Tq/X·Q"ÎõÔ/˜+¬á‡i¤¡`úÇo‹…~*VG]¨l_…¸ò¿‚®$Ÿ¢4Ôx/**sõ Û‹^ÑiFỸ fЮÁD/õôú²JLèÒP¾!8¢šøµe’­äJ†Y‚´&ÿA¿—±ðäv×^uçËm³Í.âô¯Ë.­õƒª?œ/ˆ)I­àª¤’øá 1™oþżˆ’{ÿAz–Õić—•Þ*Å. _Q]ü-TàéÕ`»®"…éÆh$<3¬T¶sÄBBJÓñ0ÄQçWùqŒ¸ÖÔ€+´HHqZ4c*±³»1a'eŽ&ø¢nR³w5¼‹Òã×’Jÿ)ðסÐRÈX[[}ô­þª`çtåMá ÷§_]þÂÚ&Óà>þ$w@ÊŠS*øxŠtdËUlj¥F­”‹U¤I7X,›wÊÝ⣀YŽ…SõÈÛ…ñ­Ü>›d½“›êÜ/QoÍŸ‚I‚¢-ß~ úJÝ€ÁááYõ8—Æ4¿j­e’}½.ú¾Êbn'‹ü›-’ª3FãP}taú ±às\ +Â+—v‡ ùÛìèzuìÔRF¡óR)sHZÝ«qÑAÅŒx\!ð­‡–#¶ðrŒœíߣ ÓχdžŠÊ=ŸD£…ŸP»ñ×jNhEÿYèÝ]HÌVTà•šº£ˆJÒ”†1AÅÄ#ü×g75´’åú¹h˜*Q&é#«óÙFî;‰“ü%…¿c+n“…ŒÚ@c\ÉçÛÔO8púü·¼OÃq¥\ú›§üQü|ü0«ÆUœœâ¡\Ù‘(Ìk%4ï¶!íb*µµìŠŠ2Äcäí:Çóܵ茻=ì5;í§­÷p>ï†,×*²¥EõË{Ó=ÔnM×ÜìªIX^Ù Æñçîäúg–¨±]Kt°ˆ_*æNd)zÈ^3ô¨~+¸-Û84y±òõ]ŸƒMÚ›qL8ÕýY“óçóeLx/¡­‰Ô“x²A5Ž]íGþ@þ ®Jžø  ôï7‘.·GÚð59#¥Œ#|ý­÷ÿk 0Ȳ˜T–“f÷,Lǯ;ùÖßë?hš xÊ ×‚xÖ ù5W+éä9JñlÉÒöªîT5uﻘÂ8Ê œúž°åÅÞâQ‡?ó8˜RiÊ‹ƒ¢!TvZÀÏuŠ…óWçzFYrrºV ë›ß'픑f³Õlƒðaj6¯=²ZÀÏtç”èÙå:ü°íe¢)Q¼0Ÿ ~ŒÉ¬Ê\…GHJ.£m­ù÷Z9ÖQR'j<Ëüð"c3×h¦ Ù~¦' «µÏµp~¦ ,‹‘ýlÖϪU½-ÍÐïÊœêGuö^µ!2jõPÓåqç ¢Dùp‹°ŒŠ=9ˆU»{XªÝIaBLT_ݾÚÎ{ãp òÁïz V“òUY.? çg˜y<õ’2Šô€ÆèñCÉU³h¶ ¬F/“œØÕ­Ï1gårû°ÀþÃ]£o•ì–åfÉùm„ ³PhH¸–»lgÂ÷m¬³àÈy• 4Eû•QĈ ×Nê+ÏßÅxM“mYù¸võTGå—âyïñÒJĸò­ô[=˜ÿ † $£øÆáS –Ó"çdù0 ÿi¤CÕ’œÍBDCIn¦1SVT.Þ© m·nãXã#ˆ;j·MB{™47Vÿ8n‘õ›.åë]ÐÓr`€ð)n \ž†‚r$¯êꨨ§¯Z` 0^Aìó¨DSÕŽ¹bŸ°IlPqcaÚEÌâPÛ¨mŽJv€) ´¹x*s*È‹ü‹Î›)|Gÿ_Æý>X¡>°Óž@7;~ŸâYÄ»AÂ:Ë·¤´VtœOdåÊÕ-ÓéP¶cÁ²oìÇ:VïY«Š‰óKxŽ”¶Ì—™¤ô7†èRq°QÍ6DÍ:nÊíŪ•E|΋íΫµš;•¹ùX3ا„ >.¢.U«‘Ú3)/7)ôÍ=~üZ°ÉáŒiÉ™Q¬€M Rõfæ†0‡„‰«XA•o³bI8uU¨²†÷·áåbëÈ%‚z›/QRŠ·ra~ÅjÖÅ ä w/ú®sÌ_PW’ý„ºh|ÐÅQíØ|º*óekMÖàÆjBŒ~|î7Ó®ÙE* ¥VÖ5¥ÐáÈqNù§Ðs>•«á`üV ­”x<0„Ûu«s¾À l¸Õä#Å›¡ÙÐ@'»Ïœ?·¨nÕcŒo‘úFgKÃ=ƒ![±u+,+)É]ˆ€¹üç‡l| _¤o¢èOÜŽ„þzp]㸂ïÆàøÅ¬ ž{(¶ÝqvT~°³>¤fvAA ƒ£[¿iÉœªU¦ª·éÏðßjη5ªm7Ù]ŒÙçÄ‘µ½ÉÌÜò´kÅS?Pô -\ù]–Ö ˜uÍdµ6ÐÎLû—ºYbzïÊÆÉ@¾¢ÍxX¥º£¼ÍxàÝøaòUÓ7âòëàDxÌê$• f v‚®G5³êRr+‚¢Kn!daj^ħôOÿAEÙa‡-™U0'¨è Öb¿'y‚„뙇½Òit=³nçç7ÜQ,¹1 I?S­“&_µô‰XÒ¼¤*Öþ]ؘÁQî4Æ{ËJÍ¥Á E:Ü”‚³íÌaic—lÜÑç‡ ÅQQ5²,°Á,åú€/‰+¥ãÇèÕ—¹ÔЛH6ìÓöI îdЬ½O¥ßd‹‡1Z‰õ›L;óú»SÒžó·´ÉÞtê>|{Ú=¤Ѻ©&¼I“úƒâ n|Âî@üXUF®Gõ´óÍÍíf£XB|ÿU'U~_Y•þ, 5U®êX®†2 ÈÙo™¶sg0€q­I·=,ÿ|È žpδںÅ?efâ~SÎL„ŸcB::¾;3ûY]ÛŠËŠ WâGN{ÇÅ4ܽÎO×2« tNOøè´F)pþþ­eXp7¥á޼Îúêµ^åS(¶ºÀêoÉ*MÜn6òþAgÜÖ0Òvû [Ö9I]èrÏ|„&TZ¿Cɪ7T¥uÚÇJ«‰f``q‡©$åN¸Ë …nã¹póÓ3•0†@â7C»#ñª€t8¿(ô®5SdSªfZöÕA¶ÑÚ¤B̃=¤Ð¬@Y ç›ÐË aé‡ìûÓˆU$œ•áÕHˆ±`ÿp¡+Èã‡Ô·IAæ¿åôõF¿8®¶Ë ö{/1V«eK¡v]Lnä´ÑñÁ%‰ê4©ÿUø;*Q— ž&¤,gÿ–ÇŒ@]eW¢ˆ Á4x“„r'²Ý 6° IZCº$8±_VÍW©É˜ÑÐK£Rs<”ˆ9+‡þÞÿZã—^³cÛd‘óÕS0xdt(øË(7"¦Q«´(“]“l'í @N]Î Ä´H©n–¥í ÛQ¨ •½gIÝ¢nP'Äì7Q$ ÷æÐ˜|ôõ=؇_øØ´Ž¡õS’ "èóÙ ­IÂ-¢B“SvÓ[\Êš[!Sˆ¦Ë,Z/§÷ÍŸ7Z~©{4êßTH/mÞÔOšÎÕ‘#Ñu?š­ÃF†G: P4LöߘücV–ê¸ßû€¾WsŽíA=K¯((¡ ¹±Ã}z¸¿ž¥y¤ À³ìC»Z3=4}‹f9y•"Š+Á2ìŸåIbFdïLðäÔBeŠ:*z¶êÞ¢wÅÜ̶ãè·F'Òù´€ãS(wKø¾šc°Ñ}7 ‘½‘„É}G15iñsOa¿5l?ÚÃu{XÄÑ0òýì¦EÍÈ[J ExdP  Í><î!DÈB“j«j&½JS·†€J‘ØU 0,2ÕÀâm-Öl¶Ò¶‰ßÑþ _×WX|K n!Âbæ¶ö·Òþ ê$üV9¬0€å=G¡O:êm8Û,Ѽ9s°'||óz2Tʆ’c4ÿ.\O1i"ßuÂSR’“ò6Ýf0 •T®ü}S [qö—÷¹bÐÇT~ñ±ÁЮ½&ŠÄ Ë Ã͹uQÜ0 ó¹‚PT¼uèöº™™l}BzFWÐäœf²ýV6³?Wƒóʇҳ#ÿa&ìJ^åßÅú|Á³û' —€[£æŽ7Šá¿óå“qú÷q33ÿó|ñì¢ÎGA}yä  J¡xÝ²àŒ¤=ñ@3† ¬GüÀIJ¿¶Ù6T_ÿjŠÂ¡ì;¾IbKç ^‘ã„øo2x}èŸâ÷£²0&ä!¾½ð¤Bj)ðS8 ¯íÏÕU†¶K”¤áÊÖÊ—áM^è‹“dÑ×`HZf‚Ðæ‘öàðf€íËÚç C+Z㯩bͲ+Q’ìêœa/Êýh¦!”¸d1Ñ0êÉÕ€yU}Úš·Ó†QMt¬ó—5ˆåV}z|]鮦pÔÑZ㢥…Æ*DÓ´a=Íû¿›æÿ ‹¾õ¬•A[„?Éyþ.•“<çú7o—âL/ä{žÒL…üÓÂXf./ùz[ª¡&Ù7ÕI=Ñý$ãÒÉËÒHôánBÊçüˆZ3ezoOúBphÞ6b¤­%jÝ4–Ç´d!“ۥlj?}Ö¯Âõ†}F>Çb›!E6 Dä™6¯%Ss`]&xŒ¹ÂÑœ[Æ$>‡Fi0¦‘ bá Iæçü;y"p(¤ºUcâ¦Ø¥‹¦ŽFU¶Ð(¿2$f¬÷7Q}EZ=O„ù™5/Oë{öè:öYxIëÅÐe6“VWz,§ÿWÏ`$‰þ>0-$ª€M‡õ«q(©' =o¬˜I!ÔŠ°âr™¨dRç …Î¤ŸÝAppF7­\h& j9ñÛ˜g–zæ!y81çq[" õ<ï!ذZ¼Åö6wãxï:œdúÖ4[°þš §›0”Gæß0ö™êäȯ'Tûçò¶é¸ƒö Ö[ü çEË ޳¥ÚÉò–Ž"+.ê>puy·í0eËT/¯†ncJ8ñJ2‹øÔ™?+sBØå;‹¦=éÄ|õïÁš†³EÛ¹_ê Æëí¤9'‰¡ óaæd)›OÐpŠ rãK‚‘v˯'A$ºn Ö*L'~q(ጛïÈå/G=^Òš{ÆŸú+o:ÏO¹å]{rvd”‡è艉Ͱæ-èZ¥LÓÂ2þka H9böÓ°Ñ#›Oü>‡òò„@Ÿ1¸½%[ê:)žþªÅ("]ÄÕ{Ÿ·‰m»â9NS+Ëæ®„,ÁÆmíÂ7@ÐWç—sß#øôU`¼žÉ‘i |Ý0¬JÃd¼j°ôµ7Q?©p—ÖŠ.PUTž"©LŸ’ÿhLÞa[üfibñ˜]ã,:~x¢·¯øúÇ“û,m\ 0Ü †Å‚:~ã9f€¾~ÀhÔoWØÏ.YǤ8F?탖à“ûwõ‚ÁM} ѧUˤ.4Ëúæõ~±GáEn ‚r!ÿ®ÁVòÆi XÜV£4\8åËóK/÷k "=tQnñÅ’myE¥L{pÜ;rnŸ[4Ù!°¥+âêj .TŸ?}ˆ(FÞŽ-’ïi7IÈB‘gÏ•¬ˆðÂ9_ómúªü<àµö—™d ; B#¦CxêãñBêyÁ{µ¢ºðM¨´yižôùGWœõóv. „̪i7ãK~IœiØ’ ÌY¥ ¡ø¶ògðCmfÁ0kiý#Èë'ÃáÆñ†ŠÀZÈ­jE?Y¥ßèLRk[Þ|®RÈM¿ËLÇÑÙC=±hë +(c$t±¡¹•> WÁèîlŒô)NHèá¨; {ó¶@¤…~ÿÓ‡OÒ™ÖÕ :ù†bb¹>¼ß$¼z4ÔäfHÈÝ»ëþX½»Ý¿íÁ (µÇª&^Ãr7Ëàiú«Uj0X‡ëFé™2†/ãw[£«Wœ#áVØ»J˜v¢ì&á]û¼í„†Ä/ÆNÝR• GFâlF°Cü¹J¦º€jŽoøX«ršGŽkH1OÙ/s}ÂÌ­áKü%æñ—ÖÏšÁVÓðyÀóŸªÜÎlœÅpGGîXUŽ‚(YOV3F¿”ìÓ­éßH‹‚È Q| <Ú?5qï°ç•B¿Í^â¢ßÕPXâ à¦p:¢–m€‹1Ç‘ÝÓ ,á( ¡: á¬áéÁ«Î&äf™rƒ\fÔyÚh•4gŠ3°hʧŠÿGàÈ _cÉæ` ·Fö‚ KÈîŸLBɇwrYâÙ♿“fjýñùP=!QëHÐ<•êï:£³•ç.«õË ,pϪ¢Ëí?ÈŠ{îŒúYÌf)Ûh‚XqBŽW?“º€—Ýð@þuY(’¼۲щsBãOéþ&×*œ˜o=ñ@M}Ž ¸Œ¬5æ|<ÌKxνó¦[2Ë B™éCðBIQùÿo4èήwÙß04dO@ó£0ÞÅB#mò÷ÙÎ6~,«]ûÈ"šˆ²¿É˜Ç¿}Cü/uG¡»H7xP1F¯4zAzÑjÖÍÄ1ü¹ÝqSä‹Õ;+ú®Ì¹å¸w<@÷µ$³¡wnvonUÊÀÅis ëðÝé“û¼VÂøF­sGÙh’›¤#<åÛRGúýç¼×™šTühMS7 ³¿^Yvˆ wk9MøáÑMÓ»u(ó=ˆF„òÍy&`1pšÉÓòø¡ßoµLm­JtÌ´èö K}"õ)i æ™]¶^bàb+U FÛP´SÔSŠÞvˆincL+E²;cxö^î“âXW‰»tá”.ÄÙ!"!nÜBºCÀ5Á¡C$XÄš.©aS³‚;ÈcÓOÕÚášGƒðœÑ@œ¦3÷lìQ¡¥ÁnC@W™±ý Öòãïf¥Ì¶b¸1uNe;ÆÌšãQî#|НrÌÆa®VexR7ÞS.›0‚p ¬|XˆÅ[Üoß7‰A ­â[ûËí Æj /©¥oÖ\]k†!ú-rÌ„¥A×¼-ÔB|*SyåÉ|´€ ÊÅ´ø‘œg}Lˆ–’¶n‘Ý&_¬J'E.A¥{Y´«LNþÍê–nh6çJ\.ÍzÙªRXÓ¼²<RŸ3VF.gº>¨Å wvôé@ÔÀ?q(ðâäWöü$°jR)Ê2|$U |ŽŠ¬*6î΋3*ãŸ!9Š5Û´C±Ø eE”~²þ¸/X›Ôh¾]ƦKM YÞï+3zÁÔ©sJßÅëÕ¸ã×G2¿U7u’3n6}¿€|HÁ¼ã>4z“š‡ûˆQ¯Y³uÖX^ (µ¤m+Mïþß×Rê…€Îmà-s¼°Ñ&^»[þeŽ¼Èš~¬ËØúYº…"žã…M,•|oI0äèÀâ6[Eà1¹JcƒY²Òîœæd£å¶»=™a¹Cæ¬kA*«R$/á1ÕhHT$“³ÇPøÍoâ 3³x_1'Ðgl•-ûøœ=+L3 p©´ðy;Ü3vŽAusÅÞôIb÷Òœ|"Õ"6ûV]ƒh¥$(U/¯xÞÄžtNRÖ$Áåð^¹¬ÓxÚÁßìc¾Ë„ܯ÷ «Pˆ‘]• ]Vk åmˆô+}½¹¿07sàþ¬ùñ°y²²È–mˆëa®Úéþcƒ=_hôRP:îVV1ki»/_‹Úñõ¿¥ÉT„b9R¬ËNLÎe2PË ”Dáþ÷èE‹ðÔæàó,1?(`!†c3´þE;uŽ.=B›M…ÌŸá®Ë¼bòs•{ÁlÖ²ýž34^+ø(Œn[¼Ë§Cqÿqn±¯½””™’‰ºÇ’[ÿÙÓ:£(ÀîG¬?Kî!½s7L#Ûnx«¸¦4ø3.Ã(%°in’üØ0@-´œ4¨ÌgR05‡ÈMóX¸g.ŠDq¤}°Nøoøœ÷¸ ŠrÃb·Ý¶¤-NªéžP±øC=m„†‚Á:ít‚Ø“ÙÖEž-ö.2Þ³ëìç&¤I0xìVOZè\(ìø£ Ôñ+ÓÙÏ^ÏËð£»)\±­EÂ1€³¢eR+‡W«õç…‘ï8*ÌzŇgHóC};FÖüŒýð–媢d¬·3ÃEXþÚfS[a’]k(¡º˜B‡KM£ÂÒ)ó¹Ó5óêÂ¥âçLìTbTÞ1ß˼ÜýèroØ`¼e2b¼“} æ`þ}Þ3Iq!4ƒ_¦âJ±ˆp®6ÙêGî{2ÖÔŒí èx9ýäï° F¥UŠBÝð#ûK³ÓôvBJh¨Ò4ÔÃûÆMÊt¶cœÈÞÈ‹… *ÅØC8‰”]¾ÎD®£Yùaz"9«¤\n½.@)|>0þ©ŠZŽü².q ئы$Àj‚>¡‹æP5mØÕøš0ÔÚg¶Kk™FT6À?Å6ã:`Â@ÿ--+¤ÜݼAcý'…÷-¨`¦¡Zùgñ?ZƒAÔ¥HòI^/œ ìÚFPBJÎW[ÆëÍR ¥‡Qç-¨`RŠ–¦Pr>J8l2˜ i–”Ð)£”SA7õBæGZš±è8ã?á:ÿ©™iµHØBÏ«·6îj…«ú•±•uYñ?c‚«¤_Z_Šdªanœ=dÈaÈ®$¾v¤/ÿz=ÿ!¸¡$]¿/Žæ$h+kZ±³Ö Þ¬GsÓ lÃÄÁ}DUÇ ã­Rܼ=-¥%za—M Mk¡RÓ5Ó]Ó …:Ø}ñÊ,fÀÞR ùxJ·Œ‡f/õÄ4³ç´*9¿úGd&zê 0H)⢥žiy½×lŽ4¹üx/± ˆaâ1?TFÅ25ðÞ€¬qÌ!Œš®F¦ÒQ!R97”+yÊðêŽ|¹lRšRÆ@(.4œ$CÛzvŒ°…õ£´°R©Û¥Ã{c›¯RðóåA!öÁ È[KÁÓè‘ ÐQI´Ã=¿PšÂ¬9'Á·tÕ›uúݶÝÓî’”©‹Ý|ЖµªF] »ªË…ž3q¿kéšv3ƒÞÄ>Ažõ’ˆ»Ž,ûX/ù©T1ìºÖ@Fà¯(aÀՔϘ% 3RäõÃû3KPd\wV¾0¤,WšSÉC$!ò /ÛÉÄLN©9i×5ŒIW>»Û_vbÔ“ã\‘^]–µeÕ„&ýKÓ3”7ßÖjú¨¥òó¢­ŒDÖÛ‚?¹z­Î5Lブ³ÎXý+ÉvÉ´9úŸ˜ÈçuùW>oZ­ºÐÂZ^J÷¨ÈOÊ)@´¹UUà²yÉQÀîÎa½é¬'!Z”Òÿ^žÝ/·¶”WòÜWÂ…ËÐŒ¥,§þ p› ¦r>ß¡¼ZÔXeUÇ @¦©¢ZÝïÇõþRSÀ/È)·ÇB@ÿ66ucÃgžxÍl`‚t»máýÛ 1³GÝáÑKV«ú{u74Åõ¨PvøåB¾¦ó¹d®Š‘!´ò£ˆJ¯"2 UJD¡B+³)l½@9ÛVV‰z (G-îQ°ÉÌ os…míW)s¥ ȵ¯Õ!½¾‰%Ïû>MJâPoVŒÜ9¯Œ`¿Çš'hûmæZ: Z0þ¯Óí'@}Œ>ÚV7.×ýÄ}ÏqLd/‘ÖNôÝíÀ0ÀÌ‹ÎÑ, öÞÈ’Kµt2[o€7éÊÄè˜zÕ”¡3Ô÷±=j}éeÆc«bç<ò ŠÛÄzŽ›”õž—í‹nýžò)vZ÷÷þ€RÈf>9æ#mÀ†ëº-°u|­=ÆÝnWÀ«Œ\Ü!ýäÓί08Õ̽ûç¨ô©jªÑ·+ïbìB‹Œ®‡¤ äI÷¤´§k»èû1TH°Ðü›JhkƒdÍýCð•fŠ?Šh\øq\ìÙÓ­ïnE¦ØÀ¬Àª :õ“¸æƒš )É›ÒTÈ“! Ÿ‘9sEwËAæ2¾£õªt<»÷@pãt¹Rm {ô„œí†á„4ý¦ü•o}€£§‡¶…¶®X¾¥Y #Bîqª=I"ô˜Fr¼Y™öñЉ§…£DHpÎgÍ{!rRéºóí"ŽñbÂû½|û“gqg{)HVbÁD[–Ý×iNü—LñS}#äÉ–*½Y¯ce 4Úû‡ˆHBEõTQ—mfzÍŽ‡ÇÆGݲ0¢¹eÒ)àî„¢2Êoa¹)$Œ8ë[YA¼ú-1F±Ç«Õv'sû©Äo{æ7C^CDÐE…àÜÔ_UøH¶Lu‰îV­¼8K6;­#Çu|Yýû+2äLl¤R$rN±ß9 +%‹-5vãpîôX¤=hkÁ QU-ÿQáÓö]*»N¸äÞPTˆ6—ÂÜþä°š.ŠX׿9X»L±Ÿ®Tƒ½sñ==YÃéÔ¼N ¢óh‰Ceé¤mÙ44rþ3ÉÝÈ.H.ŽºÀ-ÞíO}¨,š2¼äo_–­åÀìC‡DKØÓXÕÿd:ïÝ%<Êq@›):ƒ¶Ó8 äë4G3&)q”f·›ÜtMæUìó[’ʵt§Î¤f(hÐÇg&†‹Jû?V~Î,€m!M‡kÞíçô…Tr'ˆòá‘Xip?=ôº¸”÷ZËcR›‹/Û¦"úB6FL'/ÙSÉsb¸úÙÏÇ•û+ϰ6ÂwY‹Ïm¥'O/ó"!z3pOfíã–P½ŽtŸªZaVÐ9®-ç«öC£¶gKbœÊn‘Ú·¦¬N]*NSÿaÍ5š[Ÿ5›»6õùÚÎ9MHùI“…è¤ßù¶·—Ê;ù"hN ëŠ™òoz—Ä/@x U³µ-wðlP‡ô9-îˆöÍ0³FT+¥Ð¿Ñ¿,øÅmpÃIbï"4/zÖXꆠ’>ÃÐ$?’ÌIîŒUÙ~·@Z† Ò4€Çãì-F¼«Âûõ@@8#7Þú¡ƒoð‹Û$Á Û1w€ÓÊ‚s >A}½ðÊÌtg:~j,ͽš×<ΈlÊsöÆÀ>œ;¥ø(žÇ 'D>⊽›”³õÞÃÙÃL‚ãœåæ´ú–ŒæCw o “Ð2¨¿„/`t¹žZýÅ‘¢Ü'-LƒÇÖåFg,²½Ã÷ëâí7$qCrÙMÍœ>ÐÑ­ó4¾ªÇÙ´Ä;úÎt/¶Éd Ô”¡Ú<ƒýîìÞ%èýd °8§ §Ä¥7´K\H ó[î(1ÊâìxvF^Ûók£Âû…~NI%M+¦€ò‘L+×f¨] ˆc õ(&æÅö3ÑÔ†öYÉ~è}oèéñýÎL œ5[ð?†Äf‚/É#+¡Öµ7ƒÆN!©ñ9Äê«£G½Ùövé@>V¼Â-Z<šBg_·x5’‰HzCÑØ¿£1 e{¨]ÇK~{]ÝKÌieîÔ ù༩\uuòX½tôκ’0u£É@Ûø©ySt¼9Yc•¿\1;áÖE\ÎÀe'šjÐVòüÍ2¸/?‘‚ªr³dWTýåÆàáœN0ʃçŽx·R½ü$Lèc«>‰Þ =銥ŒÜtn\Œ5Àü=$Àéüó¶ñ¤c±Þ-÷«pýïæ%Ýöáz5g /+n¼6ýA`I)›Í‡íù9»K¨Dó¦ÚzËQƒŠÝIoFÍ3Œ‰(jvÆhyª‡¬R(¸"ÏΓϼ}ëÓA§ƒ^¡J‚-§¦"RH1W>·WWn£Çj¤Í½C5B(Pسð€eA±þÂ(q¢¸Ÿ@5s{våã½ÇÃvË©¾KaHÖº0 Ê׺{Bg0ý±û #«g”¯hQ€õ]Òȃôgµý%Ñ÷•èEŽÆ…¬†Gç|ýeÁÏp«gá•“q2@)*Åò;bŸœÜ ÅŒ[Br³!6i´Ò|.;Ü(‡f½¨Á_MX„ÊñH׬àê{Õ †™ˆE•Îd¬²Sïg+ÄŸ~RÒGÆî %äœV&·Üâû¿MÞ·òOúþÆÝ/:Kn©i$ìi¢Fq÷!¼ öâ¯JÔÙÝ ì~µ!º”L­!³3§#©PÅ )Ãpn ôïÈù!OÝ=QK¹Qƒ¯èd &}kÞè6Bž‘Zm Æ!Sb§¶uf…= önIÓ©+ñæ§Æç•~´Þˆl?4†L_Ã{ Šf„Vª÷’u`ÕþO zIò[4k ²(×¥…ÎëzvuÞD^çß<6¸ÐiYõ \Qä4SâK•ˆ#zbë ìö:ÆG›P*Ê({┉¦÷¯iÀ¸ÂãÙ™H˜ô¦&||ÕYÿpâs¥~p¢¨¹E”•G*Ä`r±±QNÌBÊב °ÄðKÎC0³Ktï’ 1›Ö J òåÓtŒdœ¤Ûh5ÔIœÆfc0û²Ó¼q›ÂTÀõÿ~ˆp–䊼úóúY½ º8S´j׈½eÕÀÜCBE3îjÏvä¶ÌoŒN‡¨çk|Ö$°|¸“}y0 [åÓÎ_dã.ya'Vâ²Þ³O»VÃDe¨¨õ²¯PŽ¡P\fÄð£©~uË+:Ø¡ì?‰™ì{Ø_c´Vðùä[ëì<¿Šàš¢Çw>ÎAè™Ñf–bû"à# æì E@ìúªëD<ù`6 æ%á2[Fqi÷Q¾¶ÀD>¦·ÑdÀâ²&9f¡2º¤¯M­°Ô’@ì]Z 1]G𲓾Qp¦Ñ(>s"ÍýÁGÎðaÇ“-Á ìY-rWgDùE×˰c!&ÑÜüW±Rh®Ð`þzSè\ëGûôrMjŸâ¨9ÚgOë°Ê› dò@ãŽÌّ؟£ŸuiªÛòEÞ¥Eé7´ _ŽGªkÓT%r´=¥®îP+SŒ°ÍÏÅ· IƪzÀ ™ÁOÓÚæ~ði`#dák›!ðiË ©â~Ðiê:ÓiDêjb‘ ká7 |Êøeñ x …¥Ž ~‚¤µ¢ðO‰W‘If¢8‚³°%ÏXŠAÎ<Ýö¶çK—g¹ªTD{=sEɱ¹”ÕÞÑT.Mpc@¥À¢÷EÉgŸ¨ÖŽU;åžmRv¯—!²ýÇž‰;)€sªï|F. 'Zˆ9p¢0s°Äou~fv $Sž^22•ú$JA×Ú[óÇOqƒ·FPÛä•þ ÏÇ+8Èk‚kAÏ\Àix]ä{ûG܀Ƶ"°Ò]:L#WŽ×‘ödÖû/³Ü\IмXõB‡ZQ—º7yŸv€!ܼ;y.#]×<70e˜ý¢kÜ.Ge±»ÐõÕ®€‚t‹ÁS*i!i|þ°a7ö}6£_(|å´÷”5õIö cå”k{%ï"Ö±%8zÊ™xMÿ}dµŽš¶öíTŽœãØ¢‘ŽÌ\æflJCR#°; ø¼·¼V@C<®Ø4TñÆžBöJuàJ_½ˆàZQªSEAïOþw vÇ€ødæ®0Ç{oÌl¹¼Ð˜$.ž$ʧ¤rù‡•2㜠@ܤàž/'tl‘j=‘Ÿ„]0¸åVo`nÙc.J\ýeqqصºS‡ÀvF?ãPñ€zÆÆf®áÐp•x®Ý¦HÀT!$®â!-¬Àt¹¢Ü¬ZMG (&I‚›Y1õ5e) ÄÙ^š@©4îº?3øî¶ù‰^N0Ÿ+Tâ3ÇÒ†;}PáOЦh’#¥Hæy¾<¤ùC>ô$a;ÁW*©dÎþªÎ¬!å$ KSé“1Äé'Ä ,mN´bÒöìsR½ª&üÅMcÍàÝÚã ßhnQw·kÑRa”9(ÖU¿T¼øa ætš©úÑçôð$¾ç²9gÍY$-”n/|Ðÿ?tt[E\}[¿®)?àÄ&÷`îÆÝFŒ JYRPZ ü£§Õ‡þµ A…ÎüÐåÈRáªZ†~Ã@¬ ÊXO’ãx=ÇÝ5Ô¬†8e"äêG¼=M…+è­€õ-`°_òï©;DæiïÝÆ.1ƒ{e‰öá^>bdˆ`QdÅìVd[óÍb@Œ4zwäº ›;Fs•B&Þ~pêÛô‚1–—™TÓ¸®ó^QK÷7àŽæÃcûÙmZõǹئk™ˆîÙٚΠ’Xh3½7ä‚B2@’ùˆ¸^ãPJL1ŒN¹BÏ­U–Âðù<\}ª8ó\Xoîg®Ð2žïÁdFR¿¼+ød2†’Ó*xùÅï×ߊŸ 'SÓÆJ=ÌàÖà[ŠH»kk¼V?{`<Ègnòž“†QgÙ…™:Ÿ:‚ó*]Y%%Ä 5#r¬d–4ƒ”'Ó!+u$˜­©\h×Û_³¶>îFÝ7•XÜ3m÷ö¦\º}Ô¢”1S8/ ûa"ïÝZ¸WÓa¥'¶öC!œ4åh~‰O÷ÚFP?Ú)ˆ%P9ç¯Ù-0)« ´•jø”€:âïæ‡òðÑ4wÿH½.-hyŒÌyûð>åÃw¶&ª€ðó ¥~8}12ÕÄL{ûŒ·g»"A†Ê_[2¥¸ÿv^œ|ïÿ Û]ÖåE©-‡éù1Žƒ$¹!bÿ«MKt(5hÕt¤3 óáȯª›V¶ »⚀ñÆ 0EO!S8Zv›w’þ㦋ƒD %M,';ûÆ ÛÛ‹K±!:G1;\˜ @íÆÙm}ï o§º§œg̱ëú²œ@‹”w*ƒ®Ú0ÍjÕ5Œ$¶¨ƒ ~˜b­4a]ûóÚ÷slÐmÄr% Ÿ´XԵС¿´åK!E…T@¶¨i2ÕΟ„‡qX‚CH©Àj¾š,|)•‘¦Ûƒ ìVD”¨›¬ˆbz¿,j~Ý¥^›÷˪tÃÓ‚ê›®WOùÆn‡¦µ X—W¶4¢ö·`®”X`‚Ô »Î§ÉÕѬߖú8“cµ«Ô®w¤°¹SH:W(Þ -lÿm3´²~SPמeVÊ»Ð^>(YPX¼1Šç_kÏøzgˆÐÀÓ±À#†¡Œ¨n ±®Ò$*fÂó6µ¥O˱î3Ƀã.™V šÁ·¿ …-QA»ofMotÑg¢®±—ÿó|þVâ.ÿk‚¨zK$Ä]Vu×0oo W`º8L°+\žûR7\R¼Š É_òì"c³:'—Bm)ެÿo[8R4$>R£ ѦmÚꯗÎa¾™çx„¡-k1„уhƒ2zI©í4'Ä`S‰ÛïáHÆwo¶w³rÇ"d›h,Ñ!¶\pË̦^o­ÞêÉ겵ч}ÉVé,DϬ°Â,•Þ>ù>¾™È¡Ç*åv'??Ša@0Oh(¹8©(cŽ–t•­Ó[eqF­³-¥_¥žHhy3<î ¶•œÞ‹³×’Äè(/RGă·éÛ㊎ëE¥[•x%èâòœÄQ‘ß,‚0 Loq„EÍëê¤uÂØ®ƒÚ[õP\/óV|m£ jì·™YH7[Fƒ4êI¡5°¸A¼N¿ë',ÀÀ(%µ’W¨5o×”šw`!OBÕ«é§ÍCš?úŒÓÈ¡ƒ=NŒ¯Â¸êrù:§6Èb 4Ì’˜zš ¿n»ÙéÙC(øwP‡¾TתðÆÀrö™´ÕÝoÑ’!VðôuºœÓ;îì_¯éS:¼nJ¶³ƒÛ„}Ôgï2N{Þ—·×ndT³Ä¤™§ó=Ê>pù>OjŸªóþßÃõÕ|þÞ/ŸSbù4…òzúÄwêkóô÷óêÉðú?|½¾2ÜÃä³GÏí;?WpWϾ†ØAðýQO“Ú´ù4~'øO‡ë— ߥ¯Éпϯ?L?'m¯1óÚcçö—Ïí¿õ]ŸÃo wëžùýÝ_>’ÄøzóûJÂø~¥¿'D>¾NŽ~Nçß @ô¨Í-µd³_†­²y”Œ žï7K¨ããb¹pÕxqÂmX¿63fâ¶Lý²v0SÑÚ½Cß°o˜” Ë|\@­­Hôa÷)Ħtx΃ ¤)Ïþ§t>·2¤Å©é‡Z8¢ïb\+mý UÎ[°z˜# ½:ïœõ£U¾DKømˆzD#K-ÙÒAl³DÒYqºzû"a÷ˆ§^ø[ ÍxgK„:D…òwU»ä¯]®æG(ˆýA]\}9<׃ªæ”¸ÙàkcmžŒ¶Ûé7‚,· ›x[08 ìIÚ@ó¸ûÂŒ´ù!¾÷ÿAÏ•y0²¦þ¡ •‡¸4ÞT—tßOÔž”³È›äê\x‹|_F„ š‘ƒl@é爸9ö¯Oёћü$œæ’0zŒ¢*5HXÙý­||32VÕ7yN^6§dÖšï‘¶8ðeœ<¤Ž–àîv ê ž(]¤™wlU§áZ*»dƒ$À 1¾tC˜ÄïûrAÌì š­6¯ÄÄ5c£iw_ùËhÊJœg?ì~8u§Þ8ž+½pÕÏ?NYÈ}?\h\6q\Ú™ËF,PëßbE£¶‹‹n ÿy‡ì^±Ô6^w1$W½BÁd¢‘ttšbúµ/¶¨~MQ•†V§9^oßðvbsÙmY_×ΫU°‚†ï†»êS —{›žH¸\¾É%@$²‹á¬9İ»õ—‘FEÎ! ü%Ù@rŸíˆàŒmbCÝ+c2 ÙEÍÖøÙæƒÿ#«ªûÆ!j*®rðôõy”jFú ;A=½Ý‹P$V³ 6¸ö¶_Õ]€ ¦ï²ê|ʠмN%§ñ• Ïnúé0xBYãëÁgž&”±š8’”ÇW”™*²Ï‘m$SobÓÌ®”ù4Ý Vü™}"“Êòf~Äke›ïõ³*ÒŒg·¨"ð†Xˆ~Ò´5J‚–?^:·î€L¸yp'K ²¼ 5ºÊdàû^^s$iï:ÙQ'F%(¹”¦ÀßxÉÒUÈ­aµbwê7Ññ¯”ßøy‰r\Á Dq öó.ôØ•övåß Þ2¸ì~SM$µj37%QSe$cN)7âm¤°˜sÈb¬Õ :æ#[b^5ô%ªšGõy0UŠåÀ_” ûQïFÞ Þ×JÝâ“Z†N_zÈöÉ@¡&Ä'ï3#›YTg…ô^Jk?‹Â^E&ÎFK;Qi¸è‹¿nŠy\Ù ¯uîÔt#®øðQêľSÖÔÑù0Á%CžO“Qî5_ødÏA"¥˜•Î1éè,+éb©Z+ƒ3´n¢éƒv£:s̳ì ÈÖN:ò@9S9eòƒŒ·hÜ)Û¬ëW”kÞ]¼g‹–V! Jp`C8/ÒÕ%›£ë•ŽÁs»õ±1·#tÊ#ý–Öž¨ÇçÁâ]Àå&ühuÛ´QN¨{’%ú¡Ü‘¯ö*&›]Õ×C¢ø Ú žD~}»¦˜ÄQmúl„ò90«rÔ0 ƒõ=";ùYÜÄ`e?2b†hy|Ã%žX9ò C׋=S-/<tË8hƒZ>¶¯f ìA3^w{© Þíú#À I¹Œ•|j¿](­êxöâ÷>™úDÝò­É?ŠfZÀ¸7ó¹~W§¸€`ï°ŸÉoµ” ÒÿY[錮Œý,ø•¼yÓwÁÀü´8ûËQ ÷›ÑÞ‰–Fê¼Ëk¨Ó›o ÞAF@ÔÓîq[ÅhšæYÀЖ£ÜÀXc-jo9P§í•¬c°Áñ§ÍÆ?R.ZKHN`'1Gü°[&âRûH;/E÷@?Ãbúã Ó¾ÄP—’M3­Í/ˆ‹íYXîkìT„á dèûìeím®áº(ÝÍ¿Y/€¾…cˆ"ÍòuÉñ•ušsÀ–›?Ã>|_i8ݸÞwWMÞK -átg‰5Ü…š`ºþÅ|œ=\ÕìQT%W`ºåÎj·b/îZÆÁ¤H ›´­Õ´¤¶ô߀%"[-`˜E~%Cv•Uê` üež¾þŸƶyÒI•|n pd«WtF½ù­(;,µß²¸¡¢pLÓŒGôó1„ˆ* ]«¯e)šÐè òÎß.·¥ðøöÏ\ yàŠ—±ý»ª« Ús>4¶Ü@R¹Õj}¬$î» ‘æ¾ /ä£Ã[Xf³ácöKà;)PMI¦Ê‚¹²ë…;<\„^fŸ™nÒ(©¦ƒc‘Z×:={—*/ì«—¥¢¯òMx.„Q×¼§z¹0”ÅÄ!\¿é×Ú®¾Ënx‰æk„ó¥ãDÖdy~¥o‡y|€…ŸXº Vä*áI€wãr 4 Y†n£ÐBüB†z2„ :j$¾)œÔQ^Tñ°q&ccÐI«@£Š;/!Ó0¶þ¿ólWh—HœŸ)-zvðªfŠ¢ºwÅ­ß0So”¤mWäyöi0©V«|,梘Î)L¬ÁÝÁž»Lˆ`“-€&œOÔ…RHÊóùµ D~#]FIùõàˆæÁ¯€Iè™_mГeà¬Í’4–¥a6¬Èh¿66D¬ÂÖÒ³›ý~À…@B ;î—ÞЫÀ—JŠÙ´×Æ|íÙ™sì(/¸ô°µs{S4xüJ®´Ü–¬\€z‹Âwh€–zà›ñU] Å{Š øè–øT%®–WˆJÅ鄬’:iV—  ¾ShŽÿ¬Æ òv4ÔHÀ0óy†`Ò¯‡ÂµU1 `Ä«+Œ» ­¼¿ðHè¢5UZNOd³Ã6ýN÷NÜñÆ€UÜRZÎ’Ugc"- å¨c o§™ Ò/ç{>pÿ*ƒ{/;Ó›פ×ñá WžÂuÆÍîO»&@Rb üR$3ü…SÀ¬‹ à>-,°õ®£('&ŽJ ïøÞ~‹u+  ·Ð £(¾ÌØž 0å‹Hž½Z²%=ƒÔî<@PÄï§PX´ ÉÆÙx]×CV¬f "(«#y+¿Ú¶ÖF2™¾¶ÔDß:ÌÔB~³dnßÖWj‹“âЧ²y#%ÑíªIìsÅ(Ö—÷ï®XÆëÌXnŽéwýzý%î á05 R‚ÌÏ-³ÊIë²cdDä@&žý‚^Æêð¸R‚‹pFsc¹–‘$Í{É7¸¿€®}ß¿¸¸6Û=’X9¸ òûRVÖX|·I€–MÃ6œ âÑ¸Ì’çºæSPµPñôc=G'" jéÛØ°­ØŽq†Þ€Daø‰KFÎvÆ­¢oó¥üëBï-3íZ ·ã8« Í4&õ½„ƒQW}ÖNëLVíI9÷ÇP‡“(+ÇœÇ8!$p·ÈÕ8”aŒ-sÏd’ád—äÚ?¹H/:Û,D?@ •ô¾uyž¼1óÜÂ(¢é³~ÿü-7‹úÏAO)ï›°_éµ0±ºIõ„‚6±žÑÀÞÚú¾EPÑé|0cLŽG¶p—‘½¼lÉjUÌùŒÉÚ ]Å_ÇY$¥¼ªÍUÂêÜó‚ŠcÜ“&=ÃÍic¸*ø_œ}L5î;S$Íá½>Í‹?aá:ª g6BÿX$Eylî6ÝpÜN( ßÿ"$ŽŽ{€Ò”ýÉ[èëØøÑg–ÝËbˆP§EɵZk`-b"u ™É§céÛWä¥-)?Ûñãïß&¥ xÈV$&k° š£OK­™+<’]è¼]™ëéÇSfÂÙÁÜñð(Î,þ¨Ñ—ºin}H~(ȵÅ"åÎýo˜²rQzï«5É—n û–å›?¯.¹á½CŠÒ58ÊÞ…ZÿìòšzN;¥B¡U›oæôαðöQ§HÛÝD² i7­”‚|½Ì úç…Oñاp÷/ÿVaÄNÖ¹ý‘âéæÓ–ªÚ\—b˜eû¡²h][o!ûq¼zŸ®fØŒ¯üuQÐú²Kûq¥Yd+¤*↸ €¿È†À¸FInÂfîìè"O²F^w4dâ¥Ã¼CŽi²Q¨’° ˆ89 Š$¬¼uÝD˜¢Á‰.zw˘Ën ΘÍ‘]›ð¬bˆñs‹x{ÝÓïç9+'ä/râ1²ü[09þÔ)!Š özàsI)Q®ùëØ¿‹APüRþà€,Þ½6Þí_p$/hÿ±Q¦2å<÷&ç?G`9Ýk­+QÿrÖQÛ×ú¸UX`Sæ™tJ`…Àe˜¦R=|†î&Å÷Á¥¹Bâ“] •0'Ì(wÆ¥QÝ5„Â×'®qßAÜÍÛZž<Ã%·÷°ÜþQV«f¯qIks6´^Â9–¼ÁÜ/Å-Ì;å$}Ô³¶Ìģ¯!Ág£AWÞ^ø;˜Ÿîûµ½`) /$¯*Î%1 ÉÙ8Œ»`îsÎl×$?%û‚PË2‡tƒcÑjÄŽcàFtÌ\t uBƒ4 i­±ZS¯Ìôx…g¦|ýUú¶3¤º£Æ=æøæ»~)ìï3#*8Ûaóë¸Î ž–;ý,&Xëu–®¹QF¶*-L´9ÿD%ÑgûO~°«‚±x6¥¥ÐÍpøòäâzek¼Ñ˜¡ä^16 brÑÄIo…§õ¼ü[ÔÉ_¹4zß™£Æ‘Ö._\üDè³,¡€­€·r¿œ¾bRªËMXþT>¦@ßÀ?˜}§cXñ?Ñ0æ®a×IÙž#phùESÍQ²7ÕRTçôçÑ·“ù>ù²óö”…ð~Å`ÑÞ0Gf7á©y0·ðËáô´o%»s¼¦ò*R(lx‹.`t#*\/#(¹µjÊ*g;DÖæ@\Qvyî÷ \{™ Yx¬@ÿ}= û€Ðê[ë‘<Ú1Œq“‡R;,\¡^qv”³ÝOÛ&>?e{ú49ahÂàÕZ£ê¿+ÍÖq‡BÑE¹²$^Ð?ÖÜ ‹¿«ƒ{ßÎ ù‘ºÉT¬{¬ÇÄáÀïî>ùi|l öIFœŸi‰•]x}H!/ûÕ+W&|µfn -ðöÊŒçžØéòXÑ…‡D ™Ù[•TB|=ŸK¨ˆ‘½ôœ“ÒmG,ôþ1äEf%ÂijœO(ÛF'-°jß¡L¹+ªh8inšSAut–Ùµz!0CêkáA:÷+°ð¼ƒ&fÐ1JvLø÷6`\Xäæý^¥Ê ç^)n:,p> sÆ´˜Œë?W.t³4Ò^¾s>7O/ÆÒ…§»qDg†Ù¥Š$©XŒð6ùAñrº çy,ÍOÁÙ‚ØÌÓeNË)='QJ­•ö†é è¤À±†´¼£{W‡³xoº>P5gä¶½ÃG =aÍŒ¶£²ŠÝ=NxA“ïÙNÛ/cBáz×ÍæÞÔžQ’ØT†›Ißd˜½éôñ—°_– bVW KPS¹×—ÖÍÙrûaŒÕ¸Óò±ï'/ónŠø¿¤ž±Ãb^'¼ÝPÉ7´‰HÇér¬’.Çnüì8Ç Ã­¾>Ç3¶d%7ðE1½š›ÉàWA7™WfÑ[H–R\@Ž›.G ,³‡,k­™Îåã®7ôÌkvhFÏTb¹†`>_8”Óq¢d³¦Úÿ4êò¶¢R£›6%¯‡X×,2¾Çüá<<ÑÕò˜ló%ðæÝB4n“$NÛf¿xà­ÜyŠ‚Àm«¬–€Ð2Î^ âÏ›ƒ¤F¶DÝÏ{GŽÁ 5—R·KÀ¦‘êNõ´k8Q(Å—âTÅp¾…²SÅ1D ’ˆÒÏ V¿ÐÐp EÀÖ7Ø{ÊlÞŽ›+è”\êõ#ÜÓ.‹0é{­GÓ–E )¦9gôR2P×—DŸsI8FŒÚj¦ Üñž:2_Xu“ ä¢b¼1®ƒÛsàþñS4Ÿ~2miŒ)2˜ë—!Hú»hÆ‘ÿh¼ÙÛ…Ÿ§rŸÃë )[Oÿfå·Bf ÷Ì…M »D;Q·½Ø¼Fî³&2Ÿ¥ Ù 'yçx'7} t¢ \ÙÏì š´Êoñ¹P»(ЭÃ+©\z~2»¼D¦ræ:¤H6âõ±PjؤhƒJ d·þ JN~6CqƒZY)IYôѳÃEKªj=¯.źÃûPPû˜»g^‹ý'øØwÑ5£H2«€À»fWãåà¤ìæHÒ1Nßö^èçür&ÎgM.RÓrÁ·d 0MUjÔžÊÅaß©ábZT°Ò!6S¢ñ¢º>|Gý Â!?Ájd&õÙ»ûí–¢³T¢¦kˆÅŠQ‹ÖµzI  –È÷—­}¸dÓI¦È—ÖmEìÖ°ÆäÃ÷ä={ÐN>-WCuĪ–Þºë ãÞœå“ÈÕ7¡bé%Õº»ÿODòºå8ÆÕÀ/e¶L‰Î Ç×5Õ™šŠÒ Ð[b¥ÿP5ÿf²¡P‡-QÑÿ?‘óð %™ípéô‰àD­ÅþW‚™ß¿}®\êV„‡9þ›Ÿîjá$Ÿ—ðïe|qx €Hx^ÿ¾¢Aг­k ,Ò'+àØhº’£¬ÿX•¤õ©ÞOˆ2³q6QàÕÓœçý&6Ï¡X`+î ê SìÉ™fˆ [_0Q˜ÅôO#é6ûâ#/°ECû2ÓsV²á•s#ƒe‹_}ˆõoÑÿJñ£ 2ù:™·»Þü{"ÀÝ#fL¥|ÜÒ¬A)ù®÷£î'ÑÝØ7(”0 Ò½…1Ø2/¨ˆÔƒvŒAB^Eÿkûj€Y]tÁðTÀxyðQqŠ*hýœâÞ¾ lC¤5Å…tÔ<<ñGÀbv¡_›ó ñc„P’®ü`ÝÁmQhý¹îíwëúèEÎ|…Mã‚ÖÞ•4vÜ:X8…£ëkg}†ÎtóÞé{´?8R,ÙšBü Œ¨ÈA€ßø®ÝÞžv0o°¥6±<@Nÿq½b¨íßA‘Ÿwð´;K%زZï’0B_™\É,ÅKçãÖ5ñ¹¡þáɈÇÒÙÓ÷^ ¬&£øÍ:& ùqn³6d70„,L—|àÉž65;Çu»›8¥uJ¯ã8EE5½+À’i ÄÄU><GÏû¹ý*ânádÞ‹èl¸÷{âÙÔš¤°É¶å¬ü˧_zÎ 1Œ¶”l f ó/àÆz˜´y±Í‚f7{W:{ÖA‰Ð¨fiô|qEá²jQÎtnñº2ÇKL[4í´~”P›áQ€ž(P·à†‰³Vü§ì'™ËªO\>ÕáÚ´îøôÏ1AˆÿPpƒzwäùÈ0øóüUùcZï\/ö*(¾0kÀ0ßžÌu³ \xŽ„è‹Yj9ñäæ#ÛÜÎæÈÕsáçÇH‡f75xÁ\<“r;»füHK y:W¬±K(óœ¯'‡APu°E͵}€ZùÖÏýUבËcçŒ;}åJ'µÚ¢y=JK뺵E-IÕJêÆ5B ?þš"ÿ"ÜÒàÂqlŸšß9ò¦³Oš@—@³«_«ÉÌ ·VÙ’2C<2¬Q™Z̲yT\_ ý*»Úb׉tÿžìÛ9 mӈ߫ø¤mnž½â3Š>ΖCô•šæø‰ÑbUþxȲ%´BN¢a©ø<‡ë¶ùƒ“Ë$á4½Ê,A5ìA¨Ãý>žæjTÍv¦œ¬kU35šØÒ€Âåã½èã´ÅÓä-zxì‚»(%‘fÕ„J`H°ÏÀåmMÅí+©‚ØK Œ;½×À-eÈw‹õC“Ü8þ´!Äâ\¥qMrW‘Ãåöž–Ôy€ <}£g€,BÅQü,NÁ‰©6@»‰«šol³¯bª wNK»ú­*°CÑj~”¦X£DN_aÌÌHNWA;Ñe`§8¬îd©÷d›ƒP“ÎË'( ©ýBí´ú]ü̬ž/Ÿ7Zš?ÙÁ326ñ;Ž‘ƒÜ*lÒí+87鯛L*>b]¦õ‘¼U»†z;ò{Y<+ W½ì©÷hŠ A•RS!¥2ÑÑ2­Btþy˜DÓ3öçû—™[N«:ÊeûCâŒ<%ñÁþÏžl>(SìÜ™Ð%jô<5kÜòä¡lB˜£²óèm:êðfÄó&©§ DçëÀV›“F#Á¸WìO®î7ÄtÉ~:ÿHr} É_ö%Ûstà…Æ#£';%ïn9щ]GvN‰-Ã^[¢“@ü:>ðŸÒÖõmÐ<^cbéÝÑûÍ`=eêCoGïüÍÚyÌö. àNpŠDÄÏŽ•¥UzÄ‹í›ïb€sH@8q“V-Dš9âÔçˆÉ„‹—ÕjE=sÞòúߘÇÎLìßXVá±4 ÌI-Ù¤p>êF¥Ò&~01gêÊ}t€QbmB|Ø:‹ï+|<*UŒ‡[h >Ö]àÅZ££—»/B !áÜêœÈÁa]U~/ÃÙ+ç³Îa7!ŽŸÞ’»òJ2e3È¥GÙê^×”GI¡óŸöœVQSºÈ¬,XqfE‡ôbDû„–KaÿXöT}‚Ä·6µp…Ú#æ‘Åu¯v­W›Õ_¼ë©Î¢KíÏW]-|Û¢º5V‚£9N9˜*ÍMc¹“aÕ7 ¹fg”kv ~‡FṤWàµútðð..aâýÏvý-¯Ý ‚k WL Êªû­ËàøŒƒéÞ,eÙ¯Yrä»g>ñÁ··Þ9uæ›Ñ0ÚW)­†+…|Hô“é©ôûØV @_- ·‡- 'v+>ì#Ÿœ2i7Žöy^Øþ°ó,¸ý4ÒÈp¤5];h˜²ÌÆó¨ÔM%.>+Ê;ÀOºSÙà!"ÑæpÚ°|–y"H’ù¼ÞQ=Ét'Ýgôþv­Û´Ž‘ø™jáƒùü5Ø)¶ýWÁ:Ê÷ßõåöxâ)*óOmVóY©–£‚MöäVöL²‚æzÁ„ŸA¢\ñ$ß!IXÝ¿ÐáöM[ðsŸ™ÓK‚­D/BŠ@&%\®ölu«9óU4D¶¨šãÇ÷™˜ýëÙ²cð&Í·6 éÞ·$È’`«+õâòvRlö/Ù.:6Ë ²)Q49pÙ[Ì…Ó8õ¡}Åj"ÙS³×ÒŒ´ (x×Ò ×Ųéq?´€ t>øðM>'•9#‚û«iþhÓ¸ŒÎÎ ¸Sý,…æèAj蹜U¾,’‹¾Ôyu[g#ù[øv8Ï@?¶¶&ï+J6èQd»û mn(1ÊÕ6þ!Á׎åvQÙU8ë¢\ˆ:¥2?@þ})¬šXæ»iXUÿ6¼3?CƒµK-®‘ì|„ÝnMÁ´| ¾æ(Ö=}è¡ ë~ôÔNR±7Œ_ƒ·¹•b×\ÂʸÔt`Òëᄞrµ™§Ax? ï^ú÷vJ ÑÖFðÙ îè‡WR¸ì¾’pI‘äþ‘Ä9-'ƒ“´¹"qvp’ïL{4ù(:76ê÷5 8}öÀâõ¡?˜±‰¼K^`@àªço÷Û¨Kñ\ŒŽ¤týwÏŠ²¡f¥°´þ¥H‰£Ú —GéJ–Ã|\kPŒï:ƽÖd¿¨Í?êÆ^rE“Oºµ-ÁØ2'½üMý²â)üœÁsõDÁÌ uÊëH"5ïÆ2q?ÓoÙ¹@n€¶ájë®a AÚ†ø×xJÀƒ¡Qê—M½ö÷ª³hó½*q2Éãÿ ¸4W–åÓÚšït¼Ñ7@T%Rz5hƒ¾ªÈ[7m–U>…Ëš`i¦OŸ½cGÎËÓ™†HÖšlyÔùIG/—píÅyJ0ýøÄ£2ÞáðûÃ@lR¤n#ÛÇIüÛàÙAÝœ98`(¼Ÿ$(0’Í\Kºr­Õî³ÔŒ¬ob"¹ô÷xÁÛ*%ßvÚI*©ˆÝªsÓ䵯ÍIK)G8{é7Ì6–DâC ¨Âi\U¥·¤ëcg!C-‹Ýr*° Ý0$ÜN¾s"cåB%’-²ZñÎ3â5“-r—*èñ]–Û3²x‰±c%üÙõk5fÒ@á'i‹AãÜò}wଃHÈ1…—Šcx—<“e6½c ~ß4fÐ*Îè2ûg};¸ºtoíø¶¬9׿ö6œÊz**&àŠÅ$b81Vήìœl­ö7ˆÆÍà ~—ª1úÉDi‘Qø§éì>4Ñü½±*Š‹/J].Ž7>d4¹Åä•ù€–4{QÈN㜎Ôy[jP…ÃS„?»Óš¬Ñp•ÅúÈÊ.Ò¼f1_‡è¬ˆB£KÇÍ Ì=–¹Å%O¡<[ÊŠÚ!+õ^Ïý¥™Z7hÞöoôF΄’ÐÉê.dµ l!RŸÀàjfŠÈð¸ÔÉ´Lük`#"¶~¸¼ZãÍ/ÞùÝ…Â0®6Ø—vM#ë%¿¢ôåˆ4{k , õ ­§¬–=°&â,^ÏΰrR׋ÏQ’ê|ícB.õïù‚M*Ñå®úëvו>Œ6éŒdÛ1é9¸?®r›•+<šÍpäŠË¢°íúÈÔÙ‚S±mØg#°·°ñ[%î§96ÓPkWFVÉš²HÉŸjïrÁo¡E€ ˆ+1îkPôñx"#ÉÌX+²\(ùÉòb>€}šƒ‡›¹/>h`îbàÜ÷Ð=oÑ€pîQE À\ÚI@Ýn›¬oˆÑ˜hTÔèÂ&û5™‡ƒ`%n|ÕбMGh®ã¯~É^Ú«Étò+º fjP8è¦è¸Ó?˜]J[ <ü¹©À‰ÔBÖɃé"ˆƒæ^Z¾îÖ¬s‰›S(ÖÔ#ÉDlÁ8=“ûl¸[þ®7È`œh,©ŽÿŠ»s!z-bé·”g‹üÊÖ–Ð³Š£Ï=±²Ð3<Óœ¿[U`õdi‚×Ôb²yh+Ïl’öœÜyõã¿' ð`W„t6îœ@¹r\m´€Z…{ƒÿ/³û ÿKu[¡6·;b–¬|­]‰Àp'›--W+Ž èÝ©™0³WD†'X†é~S¿Þ¨ði¦; 7¦]@Ž1ñêÊèèáð[Òðž¾w§c…Û—‰ÜUÁÐeø?jÏëéc‰,ª`š-B’Û|cÓ=Q—ë(¥z#ÚöÏÐEWìÖ»}h<¿¼åw,͇¢=v†a†>WJ'­À½Ž¦ºÖY˜bý÷Ú•â.gª_"þ2ßéD5§¾–{Ö>´ ­5ÑÀÿUZ‹³aÓ0(UÙÁ•µN…‘1+"xßx(Mc¹˜6ÚÑòJF\f›°ª[&™ƒø1E´ù3,sjkÎ ¤_@£d™‘ðÛswDŠ·œÝò‰‰ÌTŒk&i‚´êŽðnÌ‹«iLt–üÅÚjv’¾Ì£…ëJß·ÊýD ·d¯WÿQDðWô\ª8ÿuZËqhtS¥u'J عA›#Å “ L%—p…¸ñ{Öÿ€M’ŽÚiÿ ³g/f•w‚˜ºZ-“̪€ ËX ‹,[…bkAžBÕ}Z'jñ\$’8 c3õ€Ä—´VYÔµPêHl0üÝ-n9‹N…žµƒcmèø c?ð Šàbm§×È ¶&Pý±h¯qYŸþK¶ç­B‘-ü(bÎ˪hå•´[±<ŽÞAõóÆ%ë-†’(bK¸Ä§C¼ÓIû¿n$J~1elX²s‡ÔIü$×M¸ Êþû7OZ}Ì‚KŒó“|¹Ž(Úê­Ð,]Ðé^VÕϽ6x¢¿nÛ&’ž½áÓŒ¯M)Ô‚H¢Kém’Êöœ_mùѤ†¼´Ó¯ƒDÝ©dò>ZJ±Ì»©¼3ÛÓ bº\ᑯjádI³.]·³ëUJG…yú\*èôºéWôhx,¢ Ìg±aÓlµ¤ëGf…ˆ‹ÌŒö¿¡­Ý›»Á ¢ÞÉ+31ÔS€Ü‹1–ÔmkT–iÏù¯_Ná±1Ëg ®+r…#Ë #Qþ"J·œû‡®&“…Ã0¶2¡hîÀ3#Ì{ðô ¦j²:ÐUúS|Þ±Hqж3Øÿ0:ý½7‰½¾’žžŽ˜Vçåœãî´q0îá­[cäž)u=ŸO‹Üc„ä^ÚFfï—Úunå´>ÝiD‘êõšòb ·ZftZy~ATTãÓ¼Ize·Nªû#œ߱¤‰MY…0¤ù‡OÙ*9HèÄÙ>0£0<]Âé½ùä¢ï×>MUqøBºwæb3èÜLÀrÆ}&ŸÏŠxŠÉQƒ•ÞÝÜËƲŒHË`3v‚…Ç+ðû¬© „°ˆâ£P¹ë랤BÉ JF`MAKÎUË<)%D»]ÇŸ+ºT¯èŠ´ŒŒjŒ-øÂLæUϱÀžÉâB½³ÜZ½|ÅXÔèÇ;Þ.ɵÃþŽ7›ž;øjþœ=Ž`g\l;­<ÏÍ>pèúMŽz"Ò£ÙpÇ1ŠFî KÀ:Zn÷¿¢YÛn®ƒáPÌÀ˜¬íÁÛ’?ïñ$w"8…{¡—áK}t÷Ö"Ðe|u¦M’bÕò›ól7ïá®2îVÏ(Ý[n¤8ESTÿ4š$8µ' Þã¿° tÿ拓 _B瘯íx^WXè ZtêýÆæ<^Hžù ® ­G›Þb2È£/º¯½E,ZKÙEœ§×)y£¥È+¼þèX(y¡¨ÓX×ô‹Džah#ñ)'"µ¿ª`2“WaÚsRNEø5T šûK£S †ÿz{§JyÄ`Ä$“‘ÄåtóÉnðbÓöl$ÂD`ƒ¢£[?ÖîÄPJ/P°Íu.pW¢8Vö»’—q¨HxÇqE›´‚ˆCÏ$7× ÿ–Ó¤Ef ò„ºEJ¸ö™äø=å8½~އB#‡š<¥¾\h)ŒLÿŒX¹I©4÷ 6mÉI¡nþ.OJiˆJý\5zóK.Áùk=›èîëŸÙ ¶Bsñ"¹‚ôZ]JûŸ9ÔÐmÅCe‡T›JEÒà̼´›½ Xbƒh”€Xv¸Þ¸áó4ŒÊƒifÈ Î¢‹õrQ‚j3›êÐࣵF"A@ ˾ Ž÷]é‹FÔE{YÔADz¯ô½aŠ’Q®! Ù¡XÆþ,EŽœ<ÒLložªÔ£÷hõës{N¦e[S5j»Ä÷¾êsÜwµ1T»&œQµ½¾h1ëæõcµÉ¤üÌ^§ÏŒZç&RÇAΫë½'Oî8 ¡·ºVe¬h&¶:ÄÒðôoñ®;~”Ùþd%¹ÕÇž{¯tUæ=¿ðuVÖ.¿õöÌ+@SÏ\œ!È€d+€ *ýl’ÅÇíoÌÆ°«‚‹‘&¦L“ïØFíäù ãÕûºöB‹ÒÖÍŽ×4ßuÆ-¥UF"»Z(2%ÓÕžføœ!K¡nz¿öuñÔ,iž0vOði™ûý6 ,TÆ¥¢÷t¤¿¨ÁÔ>‘8æßVä† ¦ù˜¹,šBêä© 16Ùú7b‡K6±èrYñc¤îù(…–¢;-Ì ×纅[‘ÿ{TOÛ¥Bÿ2Ú(|>ªÑ±&ž$ÑCí¨Å®ŠOZc©‰ƒÀ¯ ¼î´×xe,Ô´ðïD÷QS•À3<ý/öþÂÉ…ùÇ ó¶€¡L´«1îÎn7ÞîAV†µ‹Þ;ÒõÜ-‹OétÖ>l(\F :Z‚ëA\t&âÐî˜*a4­°9à^¢†t!=‘d×_dW0ü’²`ÆbÙ¨ùÃC“8|ø[ï ÞÆ_¥³wŒ%ɦä{¯€(àGz¡öjÜÓ–Úa{oEÿóÒ[aÄvªáÒ Ö(S$W ›¼2`Ñû8€nyÒ’ã'³«Ì—cNtç5iÒH‚L™E=Y„-%qÊërÌ!Cþ‚¢ÖR›™Ûät~!?z+MK·›¥,ß®2]»ýñžA2QówMBYß¡0˜øÚ‰á߇=ï¯õä¼Fխϼþ#ZïÓÕÁ16–V:õú+ñoÄÜÓãR°oÜÏ܇]oµT`óˆ—¶ºêl`/GzmÙu#°ÎNÚŠÂ’Œe ±Jt‡®˜^i2ê¦$˜Þ»mKZe›:æë¨§·ª¶Å bÚx£ÞÊt]BÃF;š/¡’B<¥þc)‰Ö³ÌóDV_S ’‡EÂ'öT‘âôU’14ú\yÈ*°Yy½~.Eu †Eá› ìx„]Ïd´O'æùÊ Šì¸Û‡Ÿ¸‹@ÈK]ÀÓ ÄPXL©_îTOÅv¹~_3–Œï£ô¶ù)5V÷Á—K¹Æ÷ò«™GÄçp{za7²Ì›cH²g„³‰·L¬ÏcÛ«,Ë‚@ò°YQóÑ?&Áà“ðÄ <4o´“ð«ôóxpè”Õ¨iÄJg-ýФþY;­‰h³ä‘B51WŠºgS¢/~,êÝã>†Ÿ8f­Q å„1!~3v¿a×M¸ñø¤¹ `fwd M˳>!Юo-Û;´jÀQD=àµgúÉÉ53.6Þ ¾–qCž>êc²y¼ó_Ü7ÃRÀæd8 ÿPTpµâåsý&ÀW)w©ÌB½º™^Ž%-À.Æê WJƒ­WoAZÛI×pë1kÃ!ä$Á:â[¥¤ w›§}rbJÛ¿¿>†PëYøHWYH˜]ˆ’“qp¨‡1yÛ¸ØyÛ‘eèÑÇW, .¥0Ç#ÝL¡\O.Bõ³ $úßmß•{Lâr¾Âq ,HÅkåèzvñQ- —öÚ÷{TjÊÁ¾LÊ’ãGàûlõÅ_%€*“úg¦ÃùsÄgzÒ}.)Ë&Ì %-w?þD®goŒ+FNÈóЉԤïÒ`ÿu jSÆèA„Æ’Ó¡~G‰Í8[ÌçE³ QOy£UZ¾åòÕ sèø–Âê-È“E7 _xȱê4ÌûœMfßPñéTMˆ“„®·eEBÓï·çßa/&ôàDfîþÞªÿUOó“n%R[Aò)c-ÉLLk™øƒ Ô\ɳØ6 3qK3:iqÀªþu<‰“#ó6Ô@w¬xªºCß´¨¼ðEÐC ~ÅÙàßb±=… `Â×xÎBŠ–Tê­¼_º³§›“T~ìÌíqâÈc¥ ÝÆGjµ+91W×.9ú3Å8›î“NçB·°p –Û¥Ž®ÖóGï©%g©…w.õö£Š:,{f~ý9b49S«^c®*jzšñ9qHÀ9BaôVÝÑöCãÖß0Ý{©gäc´‘Åú¤Ô×)ˆŸ.ƒ_ŠÎÆBi OåöNµ@(XÊúˆ;êeìuÕeÂÕÂ9;kJ.‚§²¥ébæ ây­¾ÜN–1 ‡b‘©±VFO}´ è6œ>Tò–øK­ÊœnÁg] ì$`67ÿŠÞ³7ô˜/æöbûô~ÐØ$Ÿ¯ª¦¡8u®ò½NK¡ÎB·ow|hЦƒr ˆÐUšÃü _‰Ññ†9´÷üt½{Vþ;ºø æ¤Í<Êæø’pXëöyYÿˆÖõZA6r©Ùegï_JWUSi ±yÓ$¼Ÿº“•H —uœ±ã±‹^¯Ï3eCÛL%ñ€®.¹¡âåY54Ìý€cÈô¸Uÿ¶Ða£=gí'Ù6ÉW‚žã‡Ðc kŽ}„“!‚Në{Í0?¾U_á„^ Ä”êOÿN…¢Ðié+]l¿öÀÕÁÂ3‹ríŠÑ*ð Õ°B*"ªø²M¶‹nË¿á:WÆœåÙ$~œ­'S>¿Û:{,×ê§Irœ2Û7ÊC5¨±e ˆmm7iÈʰëÆ5ßiB¬ñÌJBœ›€Âobu"¬7®K¢å­;×%pž\ûÌtU‡žÍ ‹‰÷kºÝç¸Ü’v7… £rÜö5ÁÒ k{Ùgøša? ]}B€¨…rޤPÎQ/hýÎwêÁfa±©Û™Ub£ÈgHXŸÚ$ŽÝNÖ1Ñ‚ æöSo› Y§<ù–Ó„€¤{ô›³4?jú}úVþÑÑnòM*2EW€g¡µ±¡yk–AÙQc[Ö¢C nÂpBã"&‘´õž»_1ó$~êí×ÒâœÏ  ]|E ƒ¥º q59 h3ñIÏÃn;“†‰ÆùQîÖ"Í> uOó,)@Œó´Mƒ¹ùA͈ôЭªõRÂ_<"’0UËxM¡ ëB¤ær‘þæâ'ƒ–H—,fª¼_”5óÍmx;ߣܗ–v éžè†ø@nªU.تS“5É#˜Ç¿)(kÈJ|¹ý³føö3tyäs’ïȬhd@t.ݶš£}×âÔWÑšÜÉ‹XœS¢GuW¥L¿ó0ÚN"¦‘‘LË¿™ë}¨¸žÛè¸=èÂSÙ™n„FGRÌ—P@¦…°Nïÿûs•]à—+âó|gÎü[zŸçââÂö”* ˜†ôd"º¢ŒÌÞ0A9"ýœl¡äÖ<P¶BB1èŒÅTùÎq¼YsÍšc {›D¡á–t\Vxò®<ݰØbìeùâŒWWtâKÿML2UPþ`Ŧ÷ž•-9.Î%HÁCiÂÉÃâÌID-÷óý÷[Þ†ê0ð[òíTôô|ºê6iG°ÝAI37Àu•žá§Z·õªÒMÃ4†ÃöZ¾¤¿R1Y4˜â}þ-º*Þ`ÞB}FhÅ`ö«òoç.¹ÝÛxmQH4æýÀ©¤K«ÞÎÕ&utíÚÙĪ Rd÷)7ˆ_Sá° N})KÎ]üçcÑ:°‰I µöÖ¨òìV (×ð¶ÑiõOWoj{–¶îšh¦´NžÞDSàÄ ó* —U-5`îyí/®±ÙZw UÒ†A`d+Å0ÿZL•º÷m·£½:±#4Céog-0&(|¾ž‘”×H²aðkÛÑÓ\rmYOÿ ë. L#VÏOD´+ÚùRV¬1³óœ’@eK€ßÂjVàÛƒgCë®a;¯Xöï°&Jq¨zÓ¦$ûsµbóh*ó±Ô$ˆï„eql¸ù6@º æ/õX·ó%ÁïxèC ‚3m[î»èéY4tÓ®mQV™IòVÓÝW‡)Ì*21¥žëeãšßtð,ãý4ú3ybÜVñnIœRðQUÞ ©Þ(mHp3zS6º3å%F™ÿ6,àßMzYkÔAcÛKÑCZdÑ–g &î÷À¨Pr÷Êõ¿eŒžop jiÃ0âQ°ŸãK(#Q[éâ š©â÷4ÎT¤È¤ª¤fu¹­•йQåà…ÎP&ðÁbUÖ.f¶Ú”ˆý:Ê÷`M$L¶Îkã°8LH.ö ÈÜóè°Â‚[¸_µ»WÒ }«ôD\Wu+(øqå…­àò2µxÿ~{&ˆè³¨Yc:"jÏPƪÍð"‰ù¦äWý¸ÓQüÚ€3bdfvþv–ì”h‹Çìª.~twÑu}˜<ƒ¾ìÛÌ9s‘Ez¾‡<á9ÃPΗĉ±¥8‡w†…Êj¥¥Ôt„G‚u¬é{õé>Ã>§ÒÍ t¼ X p·IL ÀJb¢ Ç(û×)é„\‹¡næhö!¨6Üà^;Âí‰_ZNoN_(K_Ê”¦Wo^u¨,@¼ 4¢år ¶÷ÔÒ÷)¸.b{„f1ùä FI…IÀ¼ˆË‡j0ø_œAye¶¡}èEÁ2¥Þ5†ì5¤HÀÁÌrÿ#2µÝ?mÐÃiñkõy]µ_9^df+ý€tûyoÕ\Ÿ&ƒÓí;mñ¦jx:A¸Fñ¨°Ødå]ý|1´}WŸnß)9ÙćBÄ»ê;¸³þ™Çaæ^Én÷ÑH.A¼š·a˜šÂƶ™ÞfY7°ËóEoË\~±´q eïEfëJ2iÒ©ŸØÏK]^Kme‰*É”5Wâè%ä*Ýf×SãߘØZV 2Ù„À- ÌÍw²³œSw¼S÷š-jG$-ùO‰SB$Få`•|¤¢]bÜ›¼ 7A s,â1»°6 ŒŠÛ]iÏm³Ù¸«Ë³}&ÓÚøC¦dòÿ-®­Ô_"–è|TgÑÀêi í°ÒEó‘Kø°yŽÞ‡r€S-a„²³;ýC¡|žæQ/IsØ6™ ™Âê uzO¸1!z¦NÌDÈ~ÅöDv.`IL&º(Æ~þV A:$W/Œlxg™û c‘47Ò€I3v˜xÀDŸÚûzÜÅ=ßð(a›¦Sù—EÀÙ´ƒ{>À‹ƒEæn†°$WŠg½òØ$¶éh· XNãþ’å˜õMŸ—áùæz œ6Šº<øF‘Mœx™Ér¨—]ïuw@Ö–r‚xŸ†`n ÿ3Ú/\̳úÔ3ó#ÃNiü<2Šö’®ÿHž½†nkÜù<–÷ùO•n@…²Ð5_žÂI7ŸåX6^ÜHq¹ã"—[é ël¯1 O С˜¹=¸F‰][[›ÎÁ7³ØýúhZà ÀÆÐW-vöã÷‡×Z|¬Gmµ^+ÞW[f«ûÙœYð“ù6(3anÀŠÒ°ïŠG6SHeîËì2)«‚=)œðËaˆ»²ƒŒj…¶•,åøUaûŸÆ)ìVs!Šš(9îlhËœQRPå¶@úËËhû³ TéáNÿ>¡ézWà÷è¿«N³ið+1°PuP6,èTñDÖ}3ÁüS‘xà»Âi1œFñÏf‡f¶"®Òaù»Ñ9ìnøÛìô»ªÊ¾ª6wœ(Š…«É»­õPð‘ã/EVü€ÃÓ¶%"º…ºý—Áa/4Ž¡µ˜¢ üæˆóÌ)Õµuv>œÈ¹И|8‹dqKj!¸¡J÷D>"o׌Sd4%݈m%¦6N0¶F8Úm;ÇåÉ2É?‰Û²w›+y¦§BK>›~/< „œwÈ. ®"#I¯XÊóD±`ˆ¾Ÿâùå¸îgçž$â$«ñD×r; ·É?Có¥ ™¼ù¼¤§¶r­êç§àgVp1ó÷FT™Æ…B)€öuÄ`Ö&Eì^¦Â ¹õºÊy6«‰ÂпªXS€Möm‰PÔùG•½¤™ÿfãò¡´<[¦e2Qˆg”¾Üò0¡?Ô°Ë-T  ï“~„0š­lýYLÏ(ôÒö£žËWÈÁÃXâî˜.uï·}ix°4çU/:­(‘Møêm±’¡ e 5Ã땤 ”Iì½Ç7 éǦ 'X±\ê®ßoßjÿNWÖBY‹N×|™³çÈ""g49ø™iÖ„¿è€ˆbkÆÔ—0í<Îð÷`¤âàxzTâ ZK>½-AD݃ m 0]ñ”lê`Ü9#ùóB|H{é!sµWéöê1æÇÍ T»nš‹*Æ„4ÌnM«µV#¤uùÄ›˜,ø£÷íGáçî/û´úpëôn·ì¹^å­ U® dà=V¤+ Ã¥m9§“µÐ¤ž‚ ×YëæJŸ`½F˜êZfÝ’ƒ³4ýôˆ½øËÁG.JXÞH,%"@È–ÝøeEìÓOF@/+Óø ˆÃ†È»\ÇówuÍ4`þWF¿ô1ƒž ®¯ÙFªÃ´ÕÂ^iw*¡¤õ²œbÛ Þ’5Óxµ—‚áZþùroG¹’X"˜›·\ZSïfØÀ kT§«·µùãÒ#¤J¨8Þ2)§CVÚ„i-ÞÝñW–XÃk´ܰv{¨Õ Ù½ò.,/¤ßjÝGý”˜â—„EàÇ»àwʤ±·¥(Ÿ_Û}¹KB0¢à ù¼u!},IN$ð¢~ª·S!ÅØb®˯/"tqåÍiÞẫzav'i±¾ý¹L²¼w„Cs甆—­Æ‰hòe]‡Ri<=l2g\ÛÞöeO…ï´b€ž¡d$ÿvà ]v?³›iØ(Z¾ =z´´’Wjv*Kå¼mî&äÂbQ€ø³\³Ð“›JǦ!ãŠn’ê3–CÄ’FZò ÖníäŽc—Â-Ö˜{ì’hrÑd9ª ὬªäBùL¢@}3Y3R²Û"r®šoœ¨V»HùendƒÎìu‰©Ö剆|O’½[تYL㥻6±s0ó.²4´þ6&I] éÑ"çïmÛ@P…ïûC‹¤B~[ßð9ùÄXÐi¡V€ÈB5ö>‚Ãå(—¿§™J]ꟃíwáèÆÕ²ñ%g©M“qêúúQÁ8ºuÃêÖ'Š`Ø_Må_>ái~ñbFÀ‚M9aߤ:Ä6MÙßSð5¾Ó T:jäû…¬Y…=ó¦MLë`ÆR •GÙ©X፠¢aŲܽ{àR$ xEëÅÆPn¹îñn]T_ÓŵÕ(Kð³:±Wük¬øD’ ¸´ÌDÜ» Usð% ‡t*fcž,N¶ˆ¥ŸY ÎQÜ× Ptl=ÞêþnÙvt+ød‘2’+Ez?íÿLE©v‹ˆ•ÝVc÷HôºDÕK$í‹ñr¼Á¨³ŽxˆÂõJI÷žÅÙáøõ«…>8 C˜‹ YŽàD4î{MˆóÙ[ºÊ ¡†è@½¿2±s¦PÐZÃ+‘raMÛ¶§\½×MÁ:ñgÊd¯°­ƒµÙ­ŒÕˆVʪ²H6· ä¾í›ë“‰m ðI£" Ó‰x*÷ ©Ñy¾¿°TžŠvB„0RJU z£¸RKÌê¡Ø´û[‡R ªéYZѦÙZîx´×Ì«®ÁYew˜ªüXlr> [Ð_ìYyKâ×üƆLa&G•DÍ.Ë·0Ø3ín{¢i,·†ÜƒnÜ£ýhÂìĶUSŽñA…<ŠÜ?¿ßLp4´VEÙˆÔ2µ½7­+JÏu×»Ñ!~Æãu.÷áVÝz^Wy³·9œ0žÁ·s.ƒŠ»úw×j2<ãÀÂ~š57N`@13l­.Ï´aŽú›ÁmCùs ÷v…|¬¾M/´ H%0_Åvw"‘Ø%¬¾(½ê#ÍÞ^¼«‡ÖKSÛ9!½©5´ñTÞ-k;DÔ öü½*¸û©Ââ‚K÷¢‚Š_ÖÃä°²ít\6:/W°œ?.ÉlÁª@‘+¡ÒC.FC‡sПý©Œ `íϱ4j°R^Z ‘ͤþÞ9nÕr=‡ðhîÝÈ&¥)]m…¦&pÉÆÒø=’/é^3-îúj›TÁ&œÖfHQS,ó,ÂüÓ™–Ø/]å&ÿogƾ£±À~‹bê˜)ˆãv½;ÆGã0Èð}¢ÿ]Fåò—¶Ϲ×Á¸¿V„ŠDÉ®‚8"ÏP곃ÊwÌò,ùšŸNU ¼ôÖí›4=+¿›}xÇP¹)éãÅÖ5Ø-B< /!¾-XµÚ½b¹]4ÂÖ)“»ûÞ?@t+cLb¡…£0ÅŒAvÐÞ"pÀí1"jÎOþ XÌŠÙhæ5gãµò]¢©~†‚P!tWΚÿfü>[Á‡Ì}KÊÔ.¼=£QdªÉÕE0 +¤­6`¹Ô“+lkîKoÞR:´hOP£)^ÔSZ/ÂÁäeላ–czr'ðyŒR6å†U¸ä·.ùºjÕ‚¢VæMGþÆÞ`mgo ?ÃYLŒ‚¾wÙd¸`JkìçöǺlÃ.Lñ¥¢g£à€žûQ¦Kÿi‡:)uõ¡Är¤“œêÚv…:Þ‰Ù¼ÌßoWö¬­Â0¥Â~“õ|æ9øçàN¹=î¦@Ð%q@ë\§0!èÔ¢MÞt ­¡ÛÆ4KY óF¶8y-¤ ÿƒO´‘M¾ÿtÊ`˜l`’ìOÆ(™µT@g­×À¬uò¹‰:1ëû¿Mìâý%þ4_·\ï~ë¢øTÕ”îÏ%{}[ÊšÞ@4ô‚ t¨9¸3P’Á­è/¿•†`T:»°ËŽÉñçsÛ±ÜѦ§ ÑåÂsœÖ¸áÍÔËÌ .jìtÃD²v Ýü‘±2›a¯oÜœ²ÍÐØß%XÕÀfÂöxÆ&'Z5·?zÿ.,: v¢RÓçaO<øÞˆ3Ô•_U¥£„U£ëv£n!þ<8 ňn6¬·ðß5nùµ÷úu×êÙÕdÏŒ×êÚoÕWÿUqþ­¡ãàôéïÕЭóï?Õ«wÏqá¾j‡éÙßéÚŸ«i§Ç¥cáRùûA>Mß>ƾ~Ìqð >;¿êè›õi§êÚò\ø+ã¤þm[¿6¨üý‘üú^|+þ~¾þ}#ß>ÌýZß§À-ò\ÿ+¡ïÕxþ~°ß%¸óxp½}²~ck·˜áº 1H¸¸;F}(¼Oñÿç>ul,‹ÝwÈ<‰ã—·¿’Q°¦U–ä²Ygf‚ÂzšÑQ-ú9s¢ß…¥Î¬ÇÚâ8 oÛ®bñlŽË0O«pߦ/‰2p!4©y_—á„Xù;)„¯$zø, °x³öþ°BŽÛó¶Â”á“ä‡XîáÔªõ9A*éâ¸:MÈœc½2„žã²‚“އœÎ(®¢îAÎ×OøL¾r††{¹ÁñR«W¦B àm×jóëqÿ` ¦â+âºvØ]ƒi7_~Í< Ë#ÙnK‡@Dj>Ã^VÍ-D–Ÿ­x„fÀÙn3'£—ñKþȃZÖ˜Ê4¬ÇIœ4¯9"œ‡®QysLh£0aqîCù¬µ‡ÊƒYU?’—e¡&@N)eó\ý|dŠŽÓym;™™âýxSQÕ*]Ðmû–5rG§D AŽö1¯''Kö©sڈʕ^ë[ñ+…®Ô9lÀÞé^m[$æð¿ùÙsz"éXÔ ÔV‹Î.¶ßî{Þuh‘ —˜Â˜ŽæŠ¤·è25´Õ6…ÇúÑËqÖò=P¸úpº@DéZDš„ªñS$X>V5=Z¸Í;^#ØR õFëD`o/&Nl\#ÝmHLìJÂ(üÈ WïÚo™à’† MæÌ0î(í·Zbn¾LÜå&nC'(N›“`/„ƒŸIÃ\ˆl™ÓCøüªúõÙl7À´X.Ô›®+N–}m·=›U³˜:{ç8•oGOâ9±„z”ç˜áÕ 7#²@ˆZ»ôhØÇ2nEEÜ›·ÊFÓÆûEjJ=Š|-  þøXG<ëÇ;Åÿp?9rBâü«êËöÅÔpkW7mZ;$º"¦:°Y¿ž–_ќã2¼ŽËjX'ÿéÍN“îmPÒ«™g:õoBzš#äh&è*ãý‡Ì°F6Ù@ÃÌÌ‘¦™4Üú¢ÓÓ‹¸þ¸âEê\½þFš­„…Ÿ"Ê”äÎ3 &Ò@ˆ:à¿¿Í„7jÃÁ:úX®.W3Ož~ߪÐ"=¿Sޤ£•cƒ{“ +bº‘…¯KÖÎåØ  Ì«ØÚÊÎ'yZ þĤ¨w=g¹ã,ç“Äè,€sø¢*@Uˆb($ôÔ TšøL³žZ¯áNnúªÌò5C`øžÁÄÁå7âè%,~S+ç·TO§æÙè½Â+Ž ôi>¬øÊèoàÓÂzCí‰ÝçX/³ ?Ëòá_¥_’ò¾·’¹p‡Û(](Üu¢­* 3`HwPFÛñØÃ›ÇÜE ÐØûß^QÞаš5ì^#˰´ÄJYoêÙOÀ_ÜS ˜× Æ$b}ßÜ?ò$lè@n •¤¨Ó”'¢¤Òi‘w‡óqzaY|ôB[ —ùj?ï¸Äê½W/jïëð#*T†&Û]kJS]Vg—b”Pùí‰æ4£¼·½ÊòÞ´{p»zVcòU˜¾Nº¼3–Œýý] Ehò®³ãUÑ×xO’jóôŒe@ö¬uçN@Wná­ôhY•°Æ0Û ç£ " XM‡fúîA#²¶–ƒ‰¬ßóñ­óËo]ÉŽÀy%ZèEöùJÛ‰Ÿb;œñ‚«ÀÜ2¿#M£öé'¹uûP» Z]ÜóQ¬¢<-,Fî55‡“+¿(àÿpÒfÄ6)·r”a‰çÀëÈwÝï~0‚×3kg¡†$¯TY@`]‡PG¦<šV˜àìoC0¿û¾¡»PÛäæulìÊwÆtV€N鬵þ¬ÑuøŒ~ÙTÖÁî%É?yM U !u¾/ÿoÚ¾îÒ0+üÆ„ìmºrów®¼&³[çË[ñ ô¾Ý +HØRo3MmýŽÿ{ûW†ðp2~ö¶·i²°!'BáÜÉa“ÈéR\Jt¼>ëœüEñ;oKMýååÿ¹â•ÍVD‹œ@#Ïø1Ìe! »áº?:ëxbÜL Aa’-àgçä!˜4*l߯Wò“dZE(RPHœ©F¸Ô-x3éÿ0ÆŒ÷>¡ÕûÓy^-ƒZ ]›O„J\Õº–6nuÜb6â^\°Ì ù­í×ßìÿr¾tçæbú”»dÓ$則‹™Õ”mé¿5m œ‚ï%¶%SZ¯a”KÞTËËÓ,þbÆìCä÷:Z¥™ æª|šK.ß {ä˜`–¦Çî…½À&5ïŸ(Yo>- €¤ð+eét†[ü40>#þ»Ý6…(tŒÿÀñtðs}HÐÛ¾,€[Óëuˆ©Oøµ-±zä~¥|ËqJîØ½rñùnô-`F|¢2Ãlà1–\³OLÌ“»X´ÇFJÙÅ{¢!{žÕ"U¥¢½/4 &3Ä–™¹è*L#¹òËÄÔr3¶’5Œjƒ (ˆ×…?ïéJø…Üi™û¨¸Å“p{ Ï"¦¤Ùû{Ãu¯n+½ºôîKFe–r\–y6yÙž{Ö@Qa’O˜Ž ªuz[s#nnåûýß 0§Ò³äÄH"|Pƒ-Ë_ýÙFl÷ß ®J¢a̺Ÿ K|_»_àaXíÕr z(Ã3YªG5!Ør‘NÆvV~/(Q$[åûM8ÃoÉ0”GŠ¢YÞS;ÓØpƒ£ž­AzKÕ¤BHÞ2nPÙ9[œ$ O†íÓ&K´s‚õ³,¯Y0!¿[­ãƸÖë^LŠÍÓ)õ­-‰»…ØÇ=ç`ð•ãм:xmS$ÏŠ¢¥ÒçÑuZ/fè_Ç‹v=GV²}+a>cÄ¢õwçPÑÏ…VñTÊtØwP}GÕá0M™œI ÈEn»æšaiiðð´ÙPÖDAŠƒ(„Å<ý™ÿ*lÖÉûÒ”wj 3ïJ æ°È7j!5”ïdåO»æ~ÏpðJccNíY(ˆ­ßWé¼Ëöñý–† ©ß5W£ØÔ=ŒÑ¶™gƒ +‘*=vø¤ k üÓ¿œÈ ËÊõr„V®Ø¼Ú2tžé·Å·û.¦üRÓm·Ýg™Ài[æ;Võ>Dd:éø$»5R™ENm?ÝV…Þ–Ž{7nŸ;iØ}a2"‘´+Š­fÀ©]ïÍ_Vu ªŽ'Ïej"ÊÉÜ çŠ3Ýæk 7ØÄit7ŵyÉ2ʘ»·>Žâ>f?ùÁé0±”_&ßãTÚª¸ïï­»gŒ–K¹­ ×€’÷9qôhñN èÖ×·|!ÿ:¸ÂV}Øëvhž[êì>ÜÀºòÏØåÉ}®è À7säNÈ<9ùAް­ÅùŽ6’gS/ÖH¡u¡_ß;$:àIVAoHú|â”m•µ •èhnߤá×G~ðZëüN.s&óÅúü·±'žóúÅP6Ûâ‡ê÷ˆ¹Š ¡eQ~R™㼎ˆóÍÛ‡ŸäÁÔÂݶ?«M“4pñ°jYÔÆ‚â,ÚÓ’1z+F%ª‡@>ÅŠyÓPÀ„ynvvökÚ©g¨ëúgŒø ò‚wfÊLc`ÙàÉ*kÍÕZsÍ$”3jæ‚[C¦V(.¿õÄ™OA=•òs’^MÙûlsŒ}ç¾$7o~â´Ž _÷ðÜéÇUIRFvÀuëÆzº´‹~ey®æg«¯g_V\î°Ã² ²™“]¤lâ¨)?­¢Àú^èî'ÔI v_¯À¬² ¡ÃP<«r¤þ¥h@Ÿé éî*³’Íúïk¢ÎUeì’l£×`…›å¸"æ} ”ò–>ø¦­¤ê„ü]ˆÿõôšÃ¯É6ê*õGVòt/—ÒŽ[oáêÜç áÏD3h”8aÊ~)®Û?14³bNÇuÀë󆨳ä¨L²%FˆéRoÜ62V(ä;ç~{x«‡üL³Iiõò5r½aÿzѺÏvù‘.†ßÙ2$ë½CéÐÀÛZå?UZs% ÂÂv›NÇhÁý:Ôñçp”hIHÿ}t¨4êaóôðï®y7ð fú€¹üK° öQV¡"ž!.l™©ƒšÌñ{Êr¾>ÊØY<·\ñ;jh%\ø#øaz‚¹„‚ïÏAð@ }››¿•ûýœÛŽ› —F2¼ò“ MÛ¢ "’£Í¾ÞmO¤kž…X™ä>ØñZ§/${‡k~÷eçj¨«/h.˜ì%}æŵr~5s‘új¸>ÏZå_seÃ<"¹­úFÆ”<Žÿ(mñËqôpìzó€é ä¹t˜8æÒ/¢‡»D—¿†ìqjC·R‹ÚÃcVBG“hxÔ¶û¦ÿGФDŽ9ôÉ͈M!~ÔãÏèþtÃú1™¦Z¼F÷¢Yx¥;q`WëºÕì¾VØ u˜UäÿI@Ü6úu þ8Þ¨Šá 9Æ’>¹âÐåk%êü®çαT»èE×¶,]dikæy·ñÍÃÖq”Îã˜kñ“€×Mú(˜‚@VÆ™Fçëƒ@¼BºaAçK‹âê¿Þ¡eœTß«^»éÜ5 ë‘;‡¨ çìuøl«áMòއ¡vÚš/±̕*d,æ@¿‰ã˜¥ce0óÐùaV˪³QCŒæ¼KÚ9s4uì‚kk*εÅ=iÛô§ôm+)ÇyÍA +®s ÞßmÑXlj(œ©¸µÝʲåpÄ\ÞRxáäc„z¸Kç#ê>^ V¡¾ßÖ5(­ùñB'Î¢Š›W„D÷wR-ÈX«ç[αQ‰ ¦Þ.”#—MDfw`D©e‚‰¤YIÒø˜—LÛìà ·-ÈLôqÉC½‘K›Ü4´‡K‹e¹}CWèÜÑÔpjçƒÐ£¶,äÛ¦,‹3Ûž¥þ·F=-!­¿ÞÎúõï­*Ý­°Æ\$جA¯Ë »]¶ŽñÂnܨëÌé<Æj<éèzZ;F´µÔçãmcªÄõjõS¶íyÿjƒ{nCIåâDb&&W%0–Ù’Å5ô¾‡3gý†ŒA ˜i¬æõ÷;xE už?*属¥~ÅoðO0±„͆ò:( ÿ^d¯õ§ähÈlt «ƒÙ 㨄ƢÞKs&ØO2&+À–Ù ûׄܯL,Tˆ1|{ˆ®Ûq(…—ºY¡«JÍ×z,þú‡K^ÛlÚ5jPV©¾ºHA_^¬FV_¦røÏ÷ZpžiYm6¯m#!Ä–çƒ÷‚p5{iõVr Í4¯ÙéË uêD+:Â)÷wí‚íí+›@`õ#mkÿ&<Ì"]!"û7V‡@­ÉÊ¡*ÊŒe_º%%õLíì”N…ÖYÚ¢¶ÂÞWaà/àJÇTbØ/ÿ×ÖjD´‹ ¾S¬6Æ´> u/D+ozò…$èæK<„¹â4ê‘…¹b‰5ÂTç Ï-óMTŒÒElSÖסSÕ!@X«ÓC GºñêÌ®&[<»uÑIE˜{±b3ßwö£b …[ :ÒM%¶T*õœèê$¤ÌàIì²4–¸ Ðú[•ƒ‘_çL°Øú ÍÏ¥Çq±#¶BÞ® B“‚æ!ÓÚ•o<å½6Cëì ¹­hU£âój¹ÖbQôÃ9¤«£PøzyxË*>Ówx‘øÏÞ™ygäOâ^9‡hrO¢˜_a2mËÜ30ïy¾1Ϲ ²Ôò“¬ŒÐë¯mF¸cî®VùvYƱ8Aî0@k›åÅ(9Œ_6ý¼Ž ·ð«zôl8²ˆ¬!,n6ßÒ€ÆùÍÌ=+—õ%ŒÙwÙº½^ž¦!„Njoï2ÌÙjè/Ä™+ÌP8³® "ŠõËó/7nË”ÂA‡ÿ `kL'g-oU«‡.¨¡ˆ×¯ jëÌ0~¹¶ø@8†…Ù/xvÍwYnÍ•è¦%ÊXŸˆVüⲑâ *ÉÙ×½NVð_f[;%¯u$0Ô³ó~$ä-3=ë°` F¬p“yNB!v[}Åü5™•H§ÊÄN…Ò(aÊUŸUÖMN/üýÚÀÐ)dU©æ"6±Š¦ñ«oMêòVºH²Â#¯¤ñuþ8ê,áçÌ$Kü:lfLjÙí–|¸2aÙÀj{=Š Pê"¢-޾0ñ•–‘Å2~¯{H-~ìê/ÛþŒZ6-Ößȼ€ÙíÕµX:€òIÂÞ€çÉŒ:ÍX…$¯†2I–ñ·(\çöüKkÞ•_Ãd*vÌ—öÖ”J^2¢Ìa IAa½ï°{á¨ج’^5[£Øò¿Ì r'oÿ}Öeœ$°0 CÞX–#W¸f“·òW ©µ)qçØàk™Ø¹”0é+Ý}$‚‚à—#ë¿–GM—¹ o˜ M0ËGœôQŸÑª+%rÆØ¤w¹þÂ^¤#f • ql§agËî°²Â}ÓÕºêï-:·?_1ÿIÉÝy¸Œú®Osª.+É&·BæóC¼P kéÊÈÆÙA°/â¸l°rDCSâßnƒ¬1¨p‘GaÍ!ãzéV2 2<¯òC;å,å×C4jâª6Ε¦ÉZ9 3«¼e6¹¡­8×cŸ-ê¢V›ÊS= ÚæB³6è   (¿Ãb^c¡4ÜQ©Ö˜K‡QÇàY÷iV5Í]á}oœŽO¶‰h«\p0÷ÌÚ6çZÌŠ2c»L¬˜Áˆ­ ‰zT»º"ÅN\W*q?ë$™œU²vÒnéj™y©ôu Ãñ\ŒCP%¿#Åêë‡üX!¥iDW…—{çªég&¡Œ·Sm ö¬Rê/H•[ëw"åU ›2—h° øPU’ඪÃ{¿hQj‘uíœÔy[Љ‡Õ0ús…hÿxú#š°ƒTcCŒ7 ‡<Ô R “íªØHâÉô\ùïFAÓ‰´_JGÚ8~EÐkå‹D]ÆJJ Äð”³u[%·bй¤+$\¹­ºƒšlÖ¿S`X­)jlF’èå‡ÌË.Ù`™$ ^©…Uêôñ&Š€„ˆCkøú,†ÛtƒfúßósØÌZçÄX_è;²j9Ò¼uþ°{P4Šz¹Ýö½òˆCò«^ ¡ë1]Û¶‚ÒLŠI]H¦Úi_›t`¹Ö:M;è™s£dèÂÞÿUH&‡Â½QIÕçr_æULøÊ' Ìl< G6ÞPä5â’ñtÇ%J+okêHc#ÏÀzŒ:iR ^ i×Kuc¼˜{a¡±Eó5B8Ei×oÙ¹5‚Ñ$GœÀÿ: uÃÚ dÅFbBNÎãèQÇÕÕàÍÇgõþtX¤ ûÞí%0ßqF ?‡\;¶½ô[L1üj…“N«˜yiÔF!®_øLÕž¦ œ¡´s“²Œ°á " Â%ágç _Õzf¡+¨Ô7ÄÇ5|€|iF ‹ºdpý£è¢¦šNÅr3§fr–æMi<ë8ÿ?¢£pÌÙu £i}¼#ÿÃýe±ìô\´¸ÆG÷þ@üzòõ°¦,Bšø½P lx@±úþß¡ ¿ßé]]J€Ü JâcУÅIæÚ•úÙA½S]ÌÀ¨ÇF°f î>ùÅònXAσý*ç˜cA”>àí¾ûˆ;|«]¯H€‰%žhXÛBë›ÈAï3p‡TùdAedå- à6´\Éái\¼d­±£[ýqïYLÈï©5^‡”š>»ÝcúúdÂT?;µ“0ß\7”ZfèRªe†¹1è$K(¿Ą̊g-góà¹)@ÁÕ“ÏAi¦ÂÔ8¸¶2¹™à#Š|®À§Q1—òÏnYV ¢wK³X—¾¾·™yÚòÇBÏI‰ÄyP7q[Ï,º¡Åwû+´‡ž¶–CRÎïÿ?¹µñb3gA²%Iʬ4^«ls^Ý€$|Ek–‡®•œŸœÊ;3”¢;b«Œƒ¦ùmÛ³›¼9øK¡"Æ· ÚÝå3›q-)†8ªt?¤>¥ôH¨Ä’—‡»â“ûËÉt)næ’Ìúû„ÕX9°¯ ƒKOâc5¿-Œ«‡ÆÅëÝÂcá|ã çž&µÏêýDÉgÌšÁ“wÕ#[ËÌžÞÃ-®H‹i€Ôhô{”¢•5„6ÝiÄhŽlœeÇÓÓÎDKÛ× ´dM£~øØæä4ä®<¨ YÙòZw¤Þgaÿ®“ÈŒJËø‹4/Û=¼¾¤0Ô6M‹#±†…ñYµ ˆG:ôÌ:BÿUûÍÒSÒh_J– /­Ã¦!ðˆx6¾O:r½¡«@îª15!›!»nû£¼ïÿ:Î@ƒ-¹Cœf¢ªHS÷cõÞ !À¬(žpš`´X1œ*š]žaÂÚzî–°*öaÞiä·‡µí&rƒåÄF„ãUÌæÄ4oÀ+ÌÀê3«¶õ¨Y¸Îv&,«íwºÊåPãkéÆà4[´HŠCå"HÞ¯ Å@î"¢nW‰|¡î_ëíHS+@ƽ?M ü­¼}/wyÓobe‰™£<)k AA±Qû-[‹ “fNZ,´§ ¡çxéão{²\\èQ±ÀBšYV~¿tˆj†xUuU*옪)ÃåÄÍtÅ˃Ô[‘ñà¯\´¶êîz Ùs‚£Úèb‘üNx…¨rBé¿Ûª«u“Òþ~ï3€?ÇêT‘Ô鼆 ~Ї“a•ÿ…~‚ÜÈ#6Ž ôN?ŽEû7dQüÚ}[›³1ßëo9Ìï¤ÃÎiÄØ|; N©¨qÞë…tƒ‰sÐøíçÎÑ+fóz=¿`›ë°0lG‡ ç7¡Üøæ2cÜû™EstøÀ°Êm…›xOÈœ ˆ+6& ÅõH9Q¥ÙÕ€5OäXCá'˜ Št{õ$éDŒÈ¡å[CPÀhu×è4°*ËPÏAÍqnBcsqM[HÀb§çÇ—ÎøQt:v—Æ&õúx Àí*ÃÚÒ–Wµj€XX4‡C†¤á¶e]ngV Y:%‡4× öì'M¾ÔÐÙ¡GìJ,ä½^åK«‚ÐQ…" ˜[7k¨EwjTÎ#&$¾QM_ÿ?+ÔÝûgÇ· r·IfýØ^àÑÊÐ?ŠëZ´Á ­Ü'6Jä#:¡u1‘;™îù®ò‰ Xl7z]•'÷S¶üAí«RN2·•Ó¨t|›ŽGmþâ§:œTÃ[pþ›^°ì€ù/Õï. —ôÕ>ðÒñ¯n:ã,fÎQ.þ­ç•‰µ  *V™%zïZG·M]Þ*‚Í]Åiïzg~C>õ)0.ߪ؟`|AyæÀ‚³}Å…×ZÓÚ𸧜Q†Fm¯k7¬gb]2‡Ñ¹í³íîKëg«û ©ƒårlßmVÛ½B‰€Ë ”1µG&êa«šâ÷¬a¤‚%$v_ýœãó°Æ óÀdÈ]Ö?%àÛÝA’)à €Â~êõmÛÈw5|%J>øÝ8o[áÈ«èóÊšI/míh³³ÝΨÆÓ>š.tÝ…²1”•¯Žâc ]%ºlNçDÂaû*-Ìf…ì9 '¡Ò¸Š­0HåÃx­+aØ”ì+uÙj¶åŸEl¯onáÏàà‡Q`>í dÄ®VV‹i„þ7ç༷ժ¼íÉhm2Ùþ®#&Má¯rzc¦DÆ ºcÕ'ûÐ× r!4…xÏŒ3Sï–)Bļ1ã&¯/1‡P¸«!…} Q¹Mÿ·4óu®nOU_ûà˜«f‘-ÂßÌþgø„_…t?áþ%ƒÎø ²Ë8Í#Vˆÿ`s Øí"V‚ü–ÜVû§p–6^=¨¶ µ'é64ÒStIèè/¬¼÷òw‘U³ÔQ +Þ5Vç pˆ›ü[.]¡´9…Àü\4^ç)RÑû”DK5©ÿ†”tûf‰U)ƒÆÊ‚ŠšÌô1/™™â‹ÿká|2¡Ëð­ø\NÄ’"ùŒ¶æK†~Ÿ&TZ,Sð›€Î¸c\ ]=‰å÷\¡ón5G0¶ü<ÿ#¢njÝßôèQ-,6 ýႃÊ)Ì;†ÉÊcú8Õ?Cÿh§z$§KäêÕ<ÌgLÕÅù©äÕ/+ŒÖ{k@š(T×QT))¬°K”wCà¡+6¸Q ϲµ3­¤.\ÞË^ꌇ9oÒ›ö’žŠå†ˆUùÈ?ûg¥Ù:6·‹{pT«§‹É±Qäù;”½­Ô™ƒŸýf£-e#ùÍL¤zJ»Z°Ü9IZâ¶°u‰aß$¼nÉ]qvÕfÖu•»–`à`«}š"ßÍ…,g^øÇ9¨°=µ:ªØ¥q‡a{ìI œËO-KD['ý¨Œpã- ˆ÷<âz+€m5¿ýšYl€.ÕjM¤ñ‚PcgJ–ÜGÑØVÉèM8á03;=ѸPË’ ]Ú?»IÚ²îvšŸ Ü’H®<ý.…ÜÙ°n5 U¡9AI)>gAuÈSð®¡õQŒª69ÍžüÚ˜Í, TŒ3ò˜øÞ#+b“ŠƒÔ¥r:8ø Ƚí.³3ÇÁ=ìE„ðBB*øõ5¾ÎˆâÓNØ¢4–oùø‡üvtpâ‡.>Ĩ Î±ÞÆ$²ŠLê¹mÀÎß•9ÅT/°`åJ\ßæ+ßÉ@˜•]5Òz](CF½Î={ò«ÏÉ"@•Œêvö64烜D§/­É<’@ÿ8 AË@šøgíb« ó`üÀ^áå¾Ý©\Ñ[øqŒ[£ä÷/ϼö}çÑ÷¡3ß9U‹Ü»VáßDuÉ?úæ¤[GG¨F1æ’ !h¾gˆÝ´g/™ït›Üx¨Ä;¥ðžbzórõ€ì”ÝÆ6ûôÊÓ©Þ[`õÜF‚dÂ*ùJƒbi&Ù¬…RÍ©ýÌ<e© Lúªê0ÿRM7q1ZQ×Y‰?ŒÐøþ{ LCËÍÞIÝ*ŠpiÿZZ À.‘Ç4(]ç|À0â¬Î㞨h›Â,ÕOò~´ñ[úœËr¬9«,Ðrϼò}çÑ÷¡€3ß9U‹Ü»VáßDuÉ?úæ¤[GG¨F1æ’ !h¾gˆÝ´g/™ït›Û /©“ˆžéùì…ƒÖ‰Ó™U36ûôÊÓ©Þ[`õÜF‚dÂ*ùJƒbi&Ù¬…RÍ©ýÌ<e© LúªêJ Úñº7q1Z3RÛÁÿgŽ„Äè8„‡bwòîâ“è©Øú4Ü2³8ô4X©j'rz.¿År&Ôê1=X".¯´ê¹yEAŽÊ¹¿ðüÏiÃé_at>ÃPÌ#!ßñŒùov…Ñœ"ßá1nØbîîÏ«)ƒÿvléò$œ† ëŸÅhk E“'y . ¤W`0?lsÊBš‚’ðÐgaÏè€Þ:GÌ$5PÎèèlRaĆ'"7.œž5{ÔM!¯šdqü¯-Ü1§ÒüìPœásèÿÚú$Ùˆ/A¡ _DΈ›wŽ¢-¦çϼòü§ä„Ì$>e·ášÜÛ¸ü‹8** jqŠó˜QW…² à•å”§žQ èb7ô—ç8ÌY/ò?ŸÕOHÊňžÁÎ2|ÿ½Ø“ ÌÕ™›nÎö1@ÑOõÉÖõJ¿¨¹=q¢P П¸>ËÈ‹ÌÂÍ!Ð<¤5fVät·ø/xæÂàt{Jâ⦡ÅlªÉ1KïÙ¥Ñã@Íûå(z­-í¾øo€×Ì>_ÿc>r^ŒWf_Ͼ¯Ÿ}e?*Ü`¨_1…Ô‡4V©ÁhŸÚ3T©àæˆã‡jØ…³Þ£-§¡ž~Îâséºîd¬t‹öiOìN‰§—-V éc &x…=½§ 8ޏ9Y‹ éá¦Ñ[õ=Fé„Pß–þ©L~ n1”×hÅßî`9j›ê‡ñ¼… Ãý.ÊÃáÔB‡îó?ìÄè2Ö¢?tÜTÒ£ h=6û2xÁd³ˆ„PÔi–¶u«%Äýûs‚ww‰h ?rŸuƒrâhDæ0q³Ï¾°Ÿ}g?*à`¨_1…Ô‡4V©ÁhŸÚ3T©àæˆã‡jØ…³Þ£-§¡k…Lâà› ‚Ú*QòîwÙ¥?Q÷–s⮽~»°u°ßç÷@s»Tðhã°miĶLö …¾ŽQ¿WÝM)u×gãÄLÉO?Çv/=ÚpmÀ$™X#šN&sþ r¢ž€ÚY‡gƒ–Y^}~u"Ÿ$5A•µ¾ ×ùûŠŠCçÎYeE§¦®WY\ÝØ—Á"‘²&ÃZfiQ3IOÖ3»±¸|ygâ›Úßþ¼ ÃÇCk=C#'‹*Ø%3þÙÿU™µ³g WÚ.h¥êè™72º&޵¾ŸÙí[@ÆV™’.vÒl…iS9áF/\iUØ ÐÇnH&é+€ItæøÅÕJR´a§ú‚ÒŠ•æ-ˆÓ-Éö Ê~«_2 òdÄ Q@£™Ççù§Ú„%ÛÓçŒýµªrÙÎÛëWuË(é—{Š•gŸAQµTcê(t͉ §3a|š}h0&îþŠÿ€¼ÊR‰›÷¡$MGLÀä8™Óºäfˆ„²|U¾½Ä(©ÔwÓ)R/Žrëp{!ܨCO[Áb&šT<~é%¤ukÁë_Ç…3ߨÀÖ$'¹]i‹çT=ôÛ'fPè‚Uåno†îÕ†Ö%h€wi¡O޶¥ÄæK¹ùÚûTøÙÈf楮P ,ø¨“ÿPò£äìèù{ –×0­ßSó–ó£ñl5ðÕ5t&I·ErO1 æRßúb½Ä|’mfk¨ÆŠ¼“ÀSeIFåÅÓHD>ÁzœöB¢öýèá¿ö@ ãè™Å…»´HY²Í~…0ñ¿\8'Ìék†‘3“#!I—&v·:Ä|Â@µC‡DþÎ9mÖ‚°Pï©÷}Á§ão¶>ÓªcåˆýpÉ94™Jê= 䔨§iØöÔ­ðwžÁâAbˆÁqça¨¯gEVdÛ“Fm'©“*ì_¶Ä31%g¢¶ó;3;#C$êë£l…fE5oª¦ä¡TçËQ<'lFóg3qD…é“e'=„¬4Ÿ„ÚÎ&„”ýCפúvO¿p=PÊŒ¡JNtyK3§®±¥ÄRvêæ²ìsf½B3œaeØD*+í¤ŒX/oßoèå½UjU`Ëž}p£§†Qtdäù‡š.=wÈ •ªÔ̳"–ÃpàQýÌXY¡*@ä.Qÿß ’…<›ù¯çÍm:]Ñé¯:~¼8/ÛïÉ“`—(©àk¡Áˆg/#ÜÚß9ÿuIG—­h…”ã?F²® ù½‚‰/[=Ò¦šÒ–L–ù¹3ê'"?9ì®å´þÙ4¦€³9Ïwl{BpȹSJæ}·¿ÃDIà8XQçPt&AÿÏÃr/Ïwgç¹ÀÝÒ”|Èc=[žKoîàÍtp XI@uapõnŒVvûHQeJW1øÂŠ ¯©h“¿"°p`'‘õ~„á™+óöé *0ÌŽÊéŒòz0 7§ØÔU±% ô¶à³J;â¿èü×®hn÷ŒÊXÚ[´¡ý1_ª¦ ÞRxß×ä“»Q+q?ru¾ï™uw?¹šú° Z';P¼Œ²ä 1Ѓ=§ht"Y+¼púè/Õ"tÀ€Ã–A°"Ó ]ËqŒ mFä(-Xæë½4>œšæÏõç#yž²"|1e!Ï>J_©BO=5ÄÂHƒÝðêœ Þì…ŸxX,è-H}ð~ÝCóÉŸ²1¯–¯èyãÄÊüð‚¦M_ìEð½×–û4”:gДY¬”E<íÎ’'YÛ’.ëñgõýœV¿£p]9B/¼Y}~üdÙÓÌßH1Ø+\âÀñÓKá¯Ì¥g¶ÒŠò*8Ò‹~~Ç9pw¤eCË…—°¸.á¿YµãÞÝÉ(Ë7Ã}ÿd¾–®y°wÏŽÈꤳ*WÄ:ž†ócÑcåÛoùÑ÷«dœpoôö‹L[ù¼A²ù0¥Þ°™G"  £>|…àh,¸Ñ±›Ñðˆ¤Y?|Þp³ûþJÐQÇö…ª4(BÝw)‚u» '.ŒyrIΠ˞ü1¬¶ÈGRìéÈ<Ý!iQINˆ|2ˆHzE0džÏB±ÔJSW[:¬“˜fÆh´)S5@Â~¸C†ãþžØíõ-ZÓ©â!n†ì‹çöÿf§oŠõ]ï‹°éø(ÒDɱ¢ÀƒœÚ+† -˜0WÌSè9ÂRgÛ@Õæ×ÓãRÙ÷Cß¹¶ 7_üún’D®«ׄ¸ü0ƒJfG¶¯ äóyÈÇ;ç—zYšF–dÖ¤yó‘é罤% IDÞ³°gºrEñ(¾½êðúÎÒQ’,äÙCb1¯ÒP$¯b°œº/yd!ËU¼7çLÉDÂ^NuâI'<óÿÿ-AE7 M!„¶y‹ß`U§‘j tSÝã‚^ˆøZ™‹:Ÿš¦i£cl!N¨…êùz÷Ôo|Õ”8Ñø¾6+@B½ÖÀèÝ;Øt'õó©Q~ÉKáŸþ¨´¥ïGŽÑ*¿-­|søƒ®;ãÃÁè¯vý‹TyxgÖàVlýÕ È$Š¿Þ‹Î“Ú%¿Ièxx`(úò‘G[? ‘‹_[Jb.½ä²†TÁÄ“-šŽ+_KüúfWTÍZàÞ«¢`piF ‚ñžÐ˜œ ФÎÀµ^_}¼5ôô¼M“; ^ßG\ÿHK¦=i°‹¯WŠûÉ(¸)0£2*6†,¹>¯HÓsöñL“×€ßÉB ^ÊCL¾€Š+oi?;éÿ,ŸÜØtuÕvÔŒ”4Òäs#¬#éÚp‰Zˆ «Aá JÀø.)D‚ïR‚pxµÚůµ=È(ÑŽú‚0Ê“¯œÒ¸2…_ç”Æ+pqxJ›w¯¢YþN0³¬Qº^ÚV! †¿€&”Ÿxƒ¼!±ƒÉýÄ„øµòBeâVY.É릩 §áÉf~×>p¢‰±xŠœø»õáñòQm½H‚œ EÀl|›ýD©ßȧqÅ ¨gòáÜã")Í4¸·y_vsD^ó€ö™­O*clî2¡<‹uÙÌRµ\ Éè GÒûÁ¢­±ojà·…:?|A­å ÏÃr¯Ïwgçº@ÝÒ”|Èc=[žKoîàÍtp XI@vþW™•̶Q¬§ ¬ïÒ¾×§B¾–“GåÑ3Ám®hpßÉ cx¹·ôÆþ9ZN Ùó»_8µ@T1Œ·Åz.æ™Q6Ã%Rß—Evå1Õ$8/æzùèVÃÑS/1-¼îeŒ…G]6VŸ.»æEoOxœ¦ÇŸ"•Ìd°b{“$ ¶\éT·Ðe6zL!?¨9ʰÞöÈR¨p*VÑâdÜbD#ÿî[rÆS8=M«‰Ž€é<ˆÉŽ!o7FÎðK!šŠæ•\T^ôv%3gt¾¹ÎŠÐVïÓ·ÌH±úBg(1~ûk4µ¹Ë{µ\ÜÞ9É˧’_Øá÷tªÑ)l{ýº¤Y¡þú îµÊ˜‚-u¼«'pÕüf»«·[ Þ¾”åßüþ”TÌ?A€©×lsüÅ@6;tž¯—ïí jÚ.è!œÑ"È9FOLÏŽ ëÏ0ds†¸"ó¥­±öÌp§Œ \|yŽ£ÐùÒ2¡&­™Ü}D™-Ñ_H1V·iGšòðÁá&þ Y|4“%¿õÈÇMÈß6Aßl¢éf _a¡ìr¬àá¿YµãÞÝÉ(Ë7Ã}ÿd¾–®y°wÏŽÈꤳ*WÄ:ž†ócÑcåÛoùÑ÷«dœpoôö‹L[ù¼A²ù0¥Þ°™G"  £>|…àh,¸Ñ±›Ñðˆ¤Y?|Þp³ûþJÐQÇö…ª4(BÝw)‚u» '.ŒyrIΣ?Êà0ƒ±œ§ñãzò®>E¬®[&rÀ¤Ê)Ù³ÅòMc½…d:? &íýkê™""&¢#‚ œàCÁ `(€eñ²7ŒŽ &`Ï ªñ£ú‹äPKD«ƒÁé?~çP&MˆðõæÑ\0IlÁ‡>¯Ä%.:[ì(L­ûÉ=Ýt)´±E GÓ½H̾˕ŽÈU†­ |£…t-;9r‘kSpˆøNÕÂó°– =ÉÆÎùåÝå¿Æëùß‹õÀÖÕº‰¶¨ŸVêaÈg¢8*sù;„ ë2üSU2n$ì}âsd÷íÚ« X+ôÝî¯åáÛÃðÕzÃñ¢¶"ìµüX®VE½Cƒ½Š'Ýù¢ „úB“U¸žLžÌ–ÛÂþÿL§ÖÅýQàU:âÄêº5tÊÙï×Áwîè$ÆöôšC{Ué¶ÏLc³ÚîQ¥k†•:#Pëf_Á–(©Q~ÉKáŸþ¨´¥ïGŽÑ*¿-­|søƒ®;ãÃÁè¯vý‹TyxgÖàVlýÕ È$Š¿Þ‹Î“Ú%¿Ièxx`(úò‘G[? ‘‹_[IˬõØsù²æ|8;¤Ï`0t­}/óé˜=Wz37içôy>Lä´iöR/ì+K‚óðÊ>vO¹xjB5ˆ.Aò̧앺.ÏŸTo ÙE׫Å}ä”/˜cL:î¶ÐiAíõ¦£S:Žêo2ÍÿO¤¾Zß}¾ç >™ï,0oJg€Å:›ªí©¥õvMš´ˆ(q¹AÞÝÌ~¿áTÒ#) çH æ®kbðýÓåÚ?ê'õ-®¿SV8”svjˆšŸ_aËFl¶FN)ŒÙ”®X+–ÍYW…ŸäâûPŽC2ú ½¾?æÁEEó܉\O$áOQã<‹kønSi<5zp…4l; ý&óEVü© •Ê 1‰±ùyuÅö Z|›0¯>íÅ€M¢Š Píð]~­àö¶çål ÒÏ"D¨¸K^äÍÆ¤VišÔò¦5é ¼M#ßçtW6N%'€×4 ÒòÄe©ZÌØâ¹Ù<#v4¼ÇÛ©1öê\üõ³_G}>…Õþó¨ÂwÔuœxU¡…z“K´ùwLÞØÌnb)­~ƳEÀ«ê•×O#­sJ²ÜÞ_%‰Òf±®Q¥…ŠãùæsIóM‚ÛHuÜ1oAø Î¾ÈæfÊ{Mó»’ɵöñî:´’PÌe®E̘‘ΊÂ×UE¿?Óë ¤nÒ0ÅÈI`'¡ .;5ÎÌ‘^bI”½íÚ åøïOGŰqŠò;ãttä4šBYÝŒµôj‚ÒÛ©ns|`ác ú€ºHuG)ìwEiÙÆÃ,;P ¥Ž ~­ Ûñ €úØïö’sršVBÀH#6ÚÔŠ,Œ¹Þ¡ÉJ½BumžÔ_Fì²X¦Qå?ò³ùq{ºßA¶lÓùû„1 âMÜ8 ’-=Šdz÷›2´ìQÆýj[ÀrO#²pÎbFw®c_'ŸòІ ƒE,ç¨û›½û§ ‡´L½·Å°lÈ5:}EšþS‹-øèÎ+I²².4ÞþÞL dlÈKk{ø!:"‘:¼>h‚#ª;R #_÷wpÊ«@N¸~†<²sÇn+ˆ¢‹T¡›­Wºo_Ù+ÜEBymøK…tòN‡’ãý‹qÝЯrÒƒdøc­Îâ|ƒÜ7îðƒÿ€³i7j‰¨ †œXt¿W°»ŽÍ×ð~:íóƒ¯w4±¿T¶"å+8”½©xI^×xìÝIó+%†bVˆ ÷‚ô½$ ®K‰ÔDƒD¨72Û‡—e†?Ì’:ÇõÒÜ$HFš% œ¹*öÐlÄ=ŸÛ*‘ÛùV7Žˆ¥/¯ýþ룲ƒDÇÚø±ºƒXÒƒ–Ê™Ðï+¥#{`#Ø_ŒJ·V‡G$²sÀôs˜ùdw8NPhÏßÔä2J*|8?ðÉþË·\ÓÒ$@A@*¶Éw¼pgÿ%ó6|ÿ#ôDuA¼÷_s<=·3ºÃ3Rü£ð.¸Ù”<×Q].i¶&êúycV›.'PŽ-Õºº€S¸àhš6Le%ÝçÌÖÆ‘gë1F =·¹OæÉ«…ƒMGóFÿ„QÐ팣iÆ’[x´©›Ò¥7PëËÅÜ…|ù™’ïÀó ;î|ìä¤NwÛêÇ:…bÜÚK“êI=Ô¸[J_t£øw–Î/ ¹ÞÇÁü؇˜ãeíg„©×ØO\Þ-,2nþ”}ŠíÖ(äDAÑ"•n¼SKкÅïµ,],åaæurBˆ3Ýœþµ“k¥3JÎâÅžé=¯rø ¯ ³ËI7ÓãO"›&ݵ¬RZmÉͳ$Ë Fî*u8Y7]™¶=B †-á4ýxt¿¯²u*{ÄO~Ä·Cã«òÅР´ÇøšÆõM«A#>³+pøª‚A¯E©“Í8Ž¢n@F Ò_T„L2xåôõ8[vý— £ „å3))燥BdŠöfC8•CÅåV¿Ä‘ §5.±Ì@+Ge?)œVÙäI·07)Ct„”LSÑe5æœÔR™ƒÖÁ–p±Y)©M¹TN>Q«‹‘‚ã­ĪK&rn¯,É~£‹ Rå߇Z#fEë[.Ü“aü éË!úÌG˜¹g,9/$|6<+>ý+vui6ÙAý?BÇ®>%@˜í¨r. J›>™‡èH8GÜã;q\Ð[ù=ÎØænœ¶³yþ¶~rwï©ò§Š ª«F·G¦ˆÎí$Fâ*ó l+‹U"÷S¶§ˆGëhæÏÃÒN~–±øzs˨t²çñI2À¸X–ò«·IôÍN¯v]ZRÖ ‹Iò3-GÖE­!¶©Í|æ¶·ú¶v»h_N,ºˆXh õÔ¿ &5:f¬»í-¿á¡(®‰bÐ4›`9“NOÚÑym‰ üùõB{ÜðóÐLA'òÒ(kôÍÒµ¨´5Œ%Þf(~×oôß ,5Xk$qrFF"—«/¤ã¤FÂÔ,éÖù*J‘Ó4s,+Q£Ü–lìò7¬Þ{7‡+~¼ÎF\È‹¨âÖ¯ô‡XÒ{\ñG ±«S•õg`êRä¿h>s»¿O(–?uwka÷I‡ç£oö—ëï±a7UMµ†þ¿¶³VOVȘöôw&£È[&/äcÍ•4n#§¿PP ¹ J0µºŸ#Jr•Òrâ"¿t!xkT zG:n#c(îcÈÑ0–Î'ŒüM[OÛÞ‰)•%-…t• ÙóÉuœ¹8[´2ͯ}7Ç Twhb÷ô¡PL*VúagH(pV¢Œ\ŒO¿üv.«hrß|ãÖQ‚™xvž†,ºPâÀÁ¾ Eáûlî]€20H`¥h‰Hò,9˜w3æITÑö®xj¹M%çÓlÛ $o’-*@Ðçº5ÊÛŽÀµg+¦¹Hc&¨ðÉ÷9eþÔ¢¦<%®ºG¤¾/‘‡å†Ë‡ÚW°‘³ì÷óS‘]‘úž$?žÑ’$‘c›iÄ"ßa½-ÛÆ> ™Ã-€%"5ïIÍ’wý…;Óº¬â„ Uy„H@u¡lÃÑfäùóc :&ë'Mg\CRè¾-ÆÄƹ ëã³<•.…1Qdˆ¢Ã«ŸØÌâÿ*b[0±÷…ð¼ÇUŸ/?+îw,Q‡~É÷}™ÚðþYÌ  4!Ñk0j¶/Ùm}yªdÛR°&Ç-‘Ì“_?Ð0ØòŽºê΢ZTº×͏ÀÓ{’›@sΘê·OVÎvÒÖÄ®åÒ÷;ØÕÖN€ß È{š‚Ûå) sú ºÄç_Ǧ6'Z'Æ…»hðA‹?ÿôª¸½JÉ*;/s7¥bušû¤½K‹Éã…6ƒÕbÏ&vI´Ñâ.à!$&é(#T–Ý ›pBXÿwëR54ž@ùúIÈx«|¸^nݶ“X’;ÿyž³>a3¤f(µ(| rݾá È¨uíÙ8,“}t6ÕŠêÎý·f÷ºªÇä7œ'™é‹5xÅ 7Hð©Ã+Ý×™Áo¨ždƒgæ]ŠmY2ƒ8žh`&d¬RÅNPî‘êˆ?ÿxVƒºù¡düô1&û°xs»ðªf ¢Aª×(1=‡Œó-VªKa‰r*˜L|øïÕ£Ít¨¿áOc6ì§2£o¼¯Kûy¡5U1{Èôàß´mçY@)öQ'¬’  ™ž%½½±—Upf;r( Õ‘ÅÊÉšXä½EWåÕùÍÀ×ÐV¼ˆ‘}çÒ{­ëPó¥k©;ÛÿR±¯´Í>èí5ÁQxC„Ä1«‘OÝìIÅŽ?ÉÜâL“yÐAUE‚ä®~_ Í'¹¾¶kÎfÄö°K:He–­Ì ëžyFd1X]¢bj¥¥w¦ÂØÈKèüƒØ6Æ$µ&HÒ§íÁ Ü6…Fro$·76txˆf¾ð`8—~ƒÌ¢RµíIù¦ZÒþáÛ4Òô®”ª+ò ˆ"ºSGö³©LÃzY8âªeßcÙßëNw íp„îjQÄ=êãk5¢76ïy?\:„…¿ ’ûøªqkŒ(tÇÆ:i¿óMO‡KæZÐTvÙFªPJiba1x|ìS–;˜Ee‡kî·ë €«ê0 N#u°¢(ûZ .^;GyW¸ÒÇkã¤%—FÈôÓ„ÃþŠT‹¼ "ñ3îCûݤ¬òè\æYá|‘ôŒµ‹g6×ka³§†%µøÐR·LÉS\¢÷|Ë 2ÛŽ !îõ.žÀDY¾>@-ö85î/^?(g¶‘#GÅ T6É ÛëºïX&ã’Œö Ö]Œ` äWtt^ïM¨ÆÛüN±8+2Û5¬T¤Ê”•no¶šÚЀѻ$Ús‰ÒŽzÇõR2RV¦)µ=ód'1ŸçÅšÃ=å‚J óݬcìË`_õÍ;þÃáp¾C Ç8z3 zœt(¶Ì¨O•_ó$v<‹íúª¦C>%ñ“ûIGãW½ œ µ3¬ŸÿDŒÞܘ¯ªa˜ ƒ‚À'²OR±ç• })Ô‹»j¶bÁ~*c'¨o‡÷Qt™Â튊õŠYö ÀhéCóéûÇÄA­R9‘Aí¨èóïà]²‹‚›±îP¥ùhÜ6‘Î`–ö†Í¥® FË=eÒbz4bEõ›™Þ´Á —Ø NMÜ_iÇ,= A©ñÃ,8ªˆSÍDüt=»ý×OmÆ|•×H"òh—Ë$̯JŸLO)n·ÞŽÒpx{ZÏËoΓK˜§œV(EdÕíE·pÚ…!d ¯äÁ¤ztÑ:Xʾ۷c+ºð‹ÃÑ¥¨ðÐl®‰ÅC A —¦-¦-é ¹ É盳f¼\}ÝI{t‹ýœ7 ·ÍVgYÉ “ˆû¹.u¦´Êzuª;—˜‹¼jº!‘Ä3ÌäïßXì~Ao=:úéü6ÖŒá_§àV|øþQý†©kVžÊ²(eýÀºNÞq0¸ÝTú/íT`GƹAB¹ ŒøÅwþeþ$Ý|õÉóv#9Ëö»Bhåõºu>”ÈY²ž†+IÆ ’ôÛ»•þ‚ÂÔ~ìà-Úa㻲F¹"„éÀgõɺ&cÄY[eÊQîɬæ–ùÄ=í†n­?–…ÚàÞX…Jœ.ê`52[¤ßf…f+Öª<3’Ü-(Ù{çm Ð@àé»y’ÉX@>¦;üI¸|êÀmöÆ«³\âÆò&' ¨vcÛ6âÚ¯ûØzŒO f>X7Údó~ ´²å\Û÷XF¢@Ï›nÞYâ/ô£¦Ž…n^‹®é²¿d€GWË÷ÀüÔÀ=ÁQ9‰˜zo:”*8ÍõÓ=›¼_uë,ŸbQ'~ZIv 7ò…Ë>fÃ.\ï…’¦lß…ª|&uìwlú´¼ñ𒞆c.5¿:'&°¨  äÑü`”jãü‚ÔñÀ©ì®Š¦ ÷T£)RŽ>ö*øáL%E3.'IiyÁ¸IÞÉv¶Ïfž(^.~÷.è-ý CBªFt#é°×2¹Zҿ֢˽ˆÑ³«±RWéG)4*«gÎdíÛðÎç=âÑžtoE+‘û,ϱ¦Ø1C7!ïp¿¡j$»‡–=¶AÉÝ®C·Ëc)ÑþPWêD& Jë.7|mK¹Â¥=²ôÀ:SF°XùÏ@aÅkÕ ô[Ò-ÞVfăã~²ÈÜ]¼=ø‰REÙ¿*OEêôPdÛv.]¢0Ç46â„V¢ïØ‘ÿ]m‰"ËŒ-œè±c·ú4|Ë1ƒQY¥ôªH´ÕÊÀß>Cû#‘é"šÈ/’p@@6Q¹ß™±ŽµÑ¡×=ÃòSè¥Ëk𻾆ZcÐáHÞmfØßÝ 5™µÿcùéÞeßÖ0–„¸«9Ʋ`”ad&<îÆKSKT”dÞ‡EèsÁ&|¶ÎRÇDþÂ=?{6EG’Ú]“;QêòœûŸYÜt€ÒŸQû}ç®3=îç;µÅš|sf·»¨øWä„xñF$g¸£*ÉÏÆˆØù¢Ø•‡iW6bnŸŒ¬Mģ߰W§gœA:–ΉTýÄr,²ªì{T;Ð @uœ­PdZõ%|\¶2.ßFûióª1¦…í¾†Ž¦5‹Òc^Ce—Eþ¯qì´??AåÛÙjGh¬NÌÌ*z2€¹kìy³8  ªðN pæl@qVSkBâC'ŠÀ!sÛ¦.óA³•hDñ>Õ¿à®K¹:Œ‘—ï)A\ ,Ô³ôP=ËXÎVXEïÝ ð DOQŒbëéÞËi}€q7ìQƽ÷‚â‘-5íá†à Aj^RIšÑÑhÔÀj÷¡ß…×?E¬úíÔ,°6ø€L/²q”Éo‰ÿ(§,´r¿“WÎf¿eü•ÉIÚ ¾Ä£Õ¼aŠŽçC“®?1ƒè“¹3ÅV¹|—ª| ŸŠ{v"F4Ô‘ˆƒk&Èù£Äy{RpSÀÓHW¦Ö¸FäA©ÊÌXWÕ·¼‹m|z0\£N¿Õa2ˆÇÌe¦Ât¤´í X0rçér'óœúØe¸Ç@§ŒÜäŒ1(ào~µÍ‘åm'IÒžÿ"»Ë¡ÇªeD)‚8¥C˜ˆé¯Ÿµ‘E*95÷†¹*r]õñP„r)Þƒ¡Ý[6ñÅÅÙÌ0sY§b1ÌñrôƒBFãÒªúÍÚz'»­‡™36§U«(pHÛE>q:dW(û|_ÂuL.2b¢G]áÅ…ëŠH@bô’<÷•>ùTŒ0®8ÞãìÎÁã‡tL3€v'}…`‘Çfß zà^[VäÀ$Ï~DÒ_x][&¬æ8DbWXIdw'Á<ÓÊÐ ‹¡8OHb)ú´hãhd7¤ƒ°‹ïRµÇ$7Àã:&;TþK€¬8ò¤„ë¹,J–t“µî§ûLv#ÚPÔ{»þq‰÷“”2ØÁ.üdÍ??Lg€Ã”«>[öŒƒ±»»„î‰Êþ04œøà8–¾Ë‡Þþܱ9c™ÞœT™þÕñ]×¢?/¶òÝž(Âè­Cò1æÊš7¯Îé‡2¾SÖQ0!*ͼL]B)°v’£2ìåÈ£ô‰¿¬Kv ­}*Á}ükÌiÓû«ƒVJ™ÌÛ1ÃÚ™×QÞwÈU»ÎÿRD^¯ÁÜ âèõ®˜=ô^ܾÙ•}‹Œ·@¢Ï{úßÒëDÛÚÜuƒ«‡Õ_T.õ^yöbq»(¾>é¾øLû~&¡“8d½¡”ŠÂ ënÜ{l"\DîÍDÑ7fš®©{ÛŽñôAÞl ý7èí#rE.p㲨W/lâ"6@mÞbCëÿXj1Q'pòµv! †“¯àÔËÖ›¤#ŸßëböwlöÈ ·g‚Ðû\ÆY¨AfK›o˼‡Ha¡«Œê¸r¢#„í'ê*bOèŠvÌÚE\§ƒûÁ=²ÄFô‹7'Ï›QÐA7Wm»(¼ýÙ8]Ù_ùkâÃLð@Þµ'Õeį— F+{1ÊN 1‰Ó’ ŽWŒ[ê6>æÌLãW¬“ŽS]¸Z Tg!Ù^¡Úz\Õ¾<3¦6ë^ fY‰aKú^÷ñˆÒÛT@étí(òûiIdꈚsT*Ùîûé>è˺¯M©\‘ùÕh‚ãÑ&Ì1Ñö²ÌÃKàU\T›–)šÚ¨*–0ÓKAçˆó¨.öRt WøxL»ÎJ€ Ôm±H¼€Û<YBÔaþh¹æ¶¢ýüî+zùЇÏݨÇÎóeÉ8©©¡6Ê˦í7ê®¶ñq8¥x4Ű4ìP +ÇL‘6²Xï- qÕOô{súpÚß$¢ŸRäŽiþg©/‰(¡œä¯vÜhùÑÃjJ‘A¦šA8¦×››ì³ÌýZ„¤Ämþ\uš¿¸)-t&ujYÛ±}l?€]°ü1»ñJ‡~>eˆGTAÿ´×Í 'ç; $Ëõ앵ÒÞ(JýI`~4fìî+›ù;~Ý¢©[*¬!™gì{¶cÔú¡g¬r!W,b0šØd×Wƒ4¥\Çöý£o8ŠÊO²‰;ÍcgÍ}Ø wà«[ŠëhtlɤÍZ›ÅBa‘C Üz‡½”S^M $ÙÓ¨‚°Ÿ kïúSg›×‡e™ìá¬ágb ¾çŽ«º ;é«5B ô²ÉÜâL“yÐAUE‚ä®~_ Í'¹¾¶kÎfÄö°K:He–­Ì ëžyFd1X]¢bj¥¥w¦ÂØÈKèüƒØ6Æ$µ&HÒ§íÁ Ü6…Fro$·76txˆf¾ð`8—~ƒÌ¢RµíIù¦ZÒþáÛ4Òô®”ª+ò ˆ"ºSGö³©LÃzY8âªeßcÙßëNw íp„îjQÄ=êãk5¢76ïy?\:„…¿ ’ûøªqkŒ(tÇÆ:i¿òè…}Rñá×½ç(u5ð7Â2ÎK–ýé~[)µJs°¤Ã©Ö×{rª¶QåaóâÂI 1ŸÝS„Vƒx,¬ÓióÃZôhíšâÄ^!û»gÆ4ÁðAünñÈôëÏò`MàWÉHËX¶smqÖ¶:xaÑ¢[_E+tÌ•5Ê/wÌ#+ܲÇËT¢—ƒ±#qÈuCòw›ëÉG¶êŬ Ä´! î§ÙàEGž/ž? Ô†ýÔj¤½*6EçY5-Y+tÄÜîÐm@5¿MPÕŽB!²N³[ˆIIqãà^â&!›iîF·ÓF̘ÏóâÍažÿYتîs¾-‰³]xt£Y ÆÃyèã8p Lº%ÒKÔàÓ¡E¶`¸0é~ÍF ŽÕ Ú#jlʶ"<`ê]žÃ»‡›.ÝÆý¶‘ªh_QàÖ5|áψ*aߺӜB\§9dCt„…¼®ŸQ'ìí¦åˆ7x<ø¹2"1;¢5Mï~ÏúdK‹ÓBÙ‰0.­ªÎž‹yÙÓtïºï1™´H­iÚ‰.ôñxR‚|Ž´nHç0K{C¤\œ¤üùÏG‚žˆÉRjª ªoRîʦÝòk£¾ñÖĘ1a²”a3´ø§0,Sþ‹·1Ɉ ï•MaeŒSM@ât˜Dm±àGÔŒŠÈp _ŠÊ/æ°n:rÓ;®=óv"Ry¨£PäÒ²¸T¯›sD_Uê2êO¥£ºVÞý¦˜ûü®&2ÊŒ¶ûíu¨÷ò}LKàÂ-H±j§Œˆ$îÆ{‡% ‰þag^%Ùz ¬Ñá'’?µ5ß¿d TO„¸PèI»¸Úpr.‡uç·¿[Ð%ÅQOY)“æm_fÃ.\ï…’¦lß…ª|&uìwlú´¼ñ𒞆c.5¿:'&°¨  äÑü`”jãü‚ÔñÀ©ì®Š¦ ÷T£)RŽ>ö*øáL%E3.'IiyÁ¸IÞÉv¶Ïfž(^.~÷.è-ý CBªFt#é°×2¹Zҿ֢˽ˆÑ³«±RWéG)4*«gÎdíÛðÎç=âÑžtoE+‘û,ϱ¦Ø1C7!íÿl+œÇ–´{ ¸(¨Š!™g™(Ü?Ê ýH„Á)]eÃFï©w!àÏláöÂQÞ¹„<×í¥Ã†Ü%$.}L\û9Ò5gÓïíDfÌEg)½Ñá*êr;xÈ"#†[ßÑ}M ¯­âë/4Œ@(¦å<ç3#§Ë2Æ/ ¦ËT?ÓIfãOÕQz_F$¨Œ0±ï>ÄÊ„â°@åKç&“øÞzpp†+­¾!´/Ç>–eº›&ÍYîP’+딬Eƒüx÷å#‡$ÿcOZÊÑÏßÍÑ å:Ø¢Q¬Uù]î5´XˆV'‚º­ Ê}âYDgûí¾ßÒ#(Â8là;öêÿ(œ5ÀØ{>qÉå­¦tG-/¤`{“T0§»€í®Ý.|ô½xÛÅÖåõÀѧ¡!ÿf,¶ÇÍÁGKgvøQôÊQ<Ÿ‡-•j“}nîû§Y‰ñ³åšóœ°ÅÞˆÀ[š¹°A5’¿,ËZ“Žó.•€ …­Ò:èhù¥ne<©’ˆìÆÓdT)Û#µÜÛ3³Î0Èœ ?#r WùK'©«¿PÜüÝseÝZ®ŸùNW«•øNê«?ÏÝÕøQéÅ•’@ó!2ß#C_ £G„ĬƒÀòÄë˜ÂÔM.Ü>Š»<׿miÿ8dn‰òF­˵€èÄÖÏ´{s~ÏQiv=>”½¥©8–n@$Tx™ŸëÚ4´B»NaÍæ8ìXêT âYîö³,_^ «ïÊÉÿz³–%G í­¿Kvx±ZL“Ü5¿ HŠ ¾ã7—’|lWn ˆŽ—OÁ£ã´ÌϸÖ¶m¶>÷gæ‹–Sš[¾™ÐÉ›Øk5qÝŒ͉ï+Ö(Ø®™"HwÀ¢aá¸BÚ©1Ng ÅÛ„ÒpÆ~„¶ aS Ð=ˆ;'Y¡C5 *„½9ü®ä”óvŒ33üZæøA¦ˆSRt)‚’­{‡¬¨êSèök×øjú}òÓº„¼á:αÇÞêÀo®ý‚c2n¾³²»cÛTÛüm„LÒYjȱÒÐéh>ò’/U¨¬QŒ×V"ú÷ÞÑÀ7½,äPtäK`"?OíayD&»œz^Sõª±3õ€e°¡ÿX¶ZŠ—±“33柎ö¶½ÇT×îW ÔÀc„6(Ê3;Išáþim#ÖUìz]šÑ~ÔýóÁìç»$ƒaV"ô øöz0)VÕÅ2ò«}ÄLÍŠxœþfm#VÚ’>õâtä,³wœfÖ_o¹2/¾“õÅðWrÂx ¶¹û½ÍòQБÓQþs5?ä[Ò«¾ {j|uä&¥0[ÝAO6ðʸ¢Ð>plö!OÝ Âè@|ÿ|Î%å¦u~™*i5)vRÇ0ùŒ²2é%­}QW[µÒKÑoÝJ*©R% ÐaDï¿íïRT6à5ì2›»Ôôי諡%WˆÇÙ°<¿'›3šœ=!ŽÛÃì÷§Þ¼¼Áé+÷†Ë 7N´Ù’¯MÈ@‘{Z T%íú@‘I9£MFþºÅà P4:ð"J|þX]™ÅE=I±®µÕÕ³âqJÀÁ  xʾ·ÚyG#ïMGçdñá‘)ÞŒ"¿)í¿Òeê<‡`1Ûгñ_;vÒäõÈíéóø•©ÈàV¬§"l³¯¦Ÿ–c¥…'×ɵåeÒG)C¢ß¿RÐg0î8– ê°@ˆ+Bdt }Ò²#"€õ‹LÜ œ<¦Ñ|í¾ ÌÏÎ!¢Hxñ‹‰ä7Ö^WûîÅ~`B½bƦ”¯ O ±4 Cú·$¢¬B/†¡€€Ïr¿”‡S2®|×xµ("œTiBá}H ;Oþ7è¼x„2+Uܧ/´3²„”§Ü…^‚eÇeKPíå0§8^s¸+büŒ³I†Å»oDZkõŠkÒbBÑȼ³ê÷å‡î¹;›ç&§ÂilL^TÝ;ÿyÇ':¾ ´¼˜‡Àß—4fJŽ1£¸-él†jYýJ ‡L´XÙ aŠÁ£·¾ †m·lë•É=³¯>²¤Ø -¶iÏñeø«ÃC'’»A5ËHT»Î6ï£u÷ZrÛ"ðqÌäbÿuEºà×úS²I‡nJ4µÑe6íbÎè-ä/Zº*šÇØ•ŒBb¤ $¤˜Jø©†Á‘“Y²¾ €\úù'ÃC©€êë¶!¤²íLZò‰³ý æ³=Eš ÃÞŒ­¿ ¶~wΗ1ÜCM¤¥rÁŒ=¥YuV!IÏH"V‚NIÛÑÝ@(q|9È|õâÞÐZ8R •¢qíË×vè=/8Õ9žªØ4†›iò;1¡ð‚ÄÀk™¾ÇÃäâ.ƒÄàê,7ú,u²þÔÔs¢h‡pgh†p£wGÔÛ3 ÿeUÃŒIa0võùv—Î4Œ@9}pý ‘ˆŸúZß@ÊÔïýïyÐý1U2’JÀ FL1eF_f·´´5¢â Ê[{e®Ñ9ÎÔhuw8r¶=‰Í°†cƒÇ .éH¡O´Ã,ª9†mh´Au4$oãÆ˳±çKjÙÄ­¶lJ‰D~™Œ.Äã’¸^«íN›¹CÙöõ‡ ÿ/Gt«#ÛRs fàjt–÷t€`/TåœÓ¬^Fqí=Þˆ¡¤àÅÙ®¯ "Òb9Ý׬+Õ…mþÞ8ðvEœkÜkêG¡^µ –Ù˜÷B;†º3–àøQÄ›çmèFX-ðW³S´0e‹ûjsò%|‚xzš)ÐìJuô«j³‘Ù}6ÿpKݨm2Ì|"•*‚XrÕ…‡öÔÚ–ß©±îq+ýÌÚ…›=bm©þ$g+͇±Ð™ï¦}v¼:—ÃCé·{žÐ¶š‡¾Î‡›UdQ˺™™éHâómìQRHæÄbøC•®÷—ÓœcCŠ\J…ý1*ãäuúÏÃ­Éøv#‡Y@ñgDëeQæÑ°Å`x²­·UíôlÁÒzòA®•2oGv[ê¿àC‘VÛæ{^´q™¬üH‡ÇØQÖç|tyø¤ÂçËÊï@€Í™2CDé>(Rç9P7 Âükü©‡3UÖêEÁ:Hû¸Î½PØ_:XçÊ߈o‚õß‚N¥<­¿çMOëáDÕìå껕Â,m5ïk.uŒìÀ‡çëJª„÷‹Ž~‰zHS¹zÉÐgåTÞO<»v^ÚïVÛ rÏô<ŽŠâi&b}Öƒš[¤¦Vö Öß3*h×`LªöJÉ®n‰™ÚªâqçZF‡ÓÐÙv¯B$'³*L?ّܾeèÒórfÍ™â¿öÅH›,ÿñ«®¥2V[å’0M(¼C7D¶K-ßže…jÇšðáÆ’N0MÈlܺRºÃJ§cŠ „¯¹b_0’·Pfû"õeuì*Âs²cCRþVâ¦eŒø|ða´B®nSÖ”Ã\h"SÉ$í>©FÌZøõ—€)Ÿ­\J¿e¿Ã•R·%®Iª ÑˆëJ˜­Ñ¬0úü-ØL=Ÿú¬ŽÕ9x@ nã{½óí 6=“8 œoC}KAÚáÖq«æàÂCVHb$À_˜òÆÚ§›÷C²¾j;DDZХ“*`Ñê&ûuB³Í]Á%Û7±ŒŠ¨ú Ó1R˜¬Mµ:‡‰Ícf†ùSÞÒçâFÊTÀ–‰æ&%%bƒböcfs˜Î-¶¶†æý!mù›rWu¸C®Ø÷§Ñ·¦ÍYð]ª¡Ý-9@1ø¹¬PE‚*§ÛŒÂ`+[!`tñQ "ƒr#…è ðÇž:ªl!èÝÞÀÒô‰yYš¸£ÿnÍ9fŽ^cAIÑŸOý'/cé6@hOýì!ˆSh3iâU@p8 ô䡦‘0ø;sƒZ&>Îú­»÷âîj¦Öë1l0 Á¤º4§¿˜ØL\©2´«,÷ŽŠ>¬ÝÑhz*êtsªp"ÏÖKnººî깬•Ã@þzúò?ÎU5þ¼¢°À $(Ä‘¼Vœ…2/0äëfQûœ l æ1kNé/JƹþÿÔýÔžM”g<>„þAnšå|_s½À£àÁ8‰ÎÈ;Ìöš™‰n3 ‘H¾™níÚÙU‰gÿ_×ü\êCcC¡á·ÀêìlܼVÁé h$·€wꆱŠÚ<Šm/EÕ‹aÈ ÐLsà‚Ò`3ÞÿÃÓÜe;8ΜêøŸÃ{’*@Ó–å)šqT¶×ðr1¡XÁå¹?cDz¡1–I¶;~T1“µÿcÀ$A»ë<À€¯x&k&›XàõÓu©ÿq£šÀ’nP'ß0záËfO¨d,ß°€[§íÚ”šº:Ñv¿X/Û2EîIÇ ¼Öoqô.¡á¡n¢x€»ªÒå†étÇ# |ÄÈ5ùW‚ æv0<Ê)bük$âT ëõ³ææÑ +¬ÅÃ"ÂŽÎuyr¹¥ƒ“\ ñDë%'Íí6ŒäïKÑ †1íSîQÈA9mïп溱C¢ß©r2~¸úsÜÞùæFRë K+ûék ‚ý9acÊ_¦ü)[üHÚ©Q£ mS¢ÔX«õÇi±¤ï+Ño1•c2\³‰›¯FÕ;L¹é\«¬êy%ЮðFœ_ˆTM1 4k¯9±/J\å6Íã¨ûèÉë[‹áØ\Òkïº^Û£¾P¨àvò»9†yS]·§n0 m;8=¥ô]¼“• ôòí;÷ݪ®¨ü:KäÚŠ²óö«.¢ÙŒsÇ%Ñ@JÙ¬7g}”Ÿr¸Ë«hÑ‹²Ä ‰1¯'å-Ô ˆÃóà ؠó ól=l˜ƒý¯€}Ägüx u•Àòg%Ÿ¥‰ |'ãúµTÒ5S‘'Bú«ÊŰ«y{j ’iüëŲ‰é+  õ~c“è?û}wž,F¿•T¹3ß™IwœÆ‚(µgÙPØí'Ëz¶ôo¯4èCO–×ÏÈKê>,NÑpI§˜}ïÏ’£éÕ\Zš/LëñÉ¥‚7?fá^ùß jïîEÜ ›A˜Ì({lžª±bš(Ž ר¶¥^=¯“»Pââ`´ŽÈ|®šÌY0•kç˜,ˆåÈôpNn—FäýÞ¢;Gý*¾ñüå|5g‘x <*•[GGÖ.³‚IÉ€h”‰Ë®oýgº>M7™‚b^Zö4ã”´9CgϤ™DUqä2%PÝv͆’–`Ì'õIÕÒ«Q&(„(ìE3kúU)qëîUU…ïZ³êQg)ÝuXÜþ¼DÓ¦Æ`2FzRÒE¡õºÍꛓ»ôÙ˜s{nÚ¦É4¿·+jƒ'²†óÈ/  }è¤MXMëÂË5S}è@¤¬Тñ%®!NK$GdpU rjë¹Hm1æp¸f ‹Ù€«òÄhç 7ÕÚ M±½Qˆ™•v<2¾à»w7b@Y~ƒ Æ!:¹î T/ø¿ï"ÑžW`Aô`²†º®¯ˆt ¦X\ÕÑ=üߎŒì³þ>Ú¶¡(øF¶2–6ûÆáÇTòdz:A&6?ÜG /Óíú"ì=Sýe*ÉvÞl /QVó‰ç3])Ñ‚õRÕõ"63tà£B³ç,ztc©ÅÕ~xuy™]iÂã¬ùæ)³-ÕÔúï ì‡]ÝE_@#šÙ\QòzœOq©&(¬ G ˜V+ÈW,¿‚B§0±Wö?\Lçðê›øv›ü;¾ÝïÏáÔGðíøv©ü7¿ç¯áØßðì'øoHÈ[3Öñ¿B5‹¼Ýà.&VŸ]H)‹jh›Uæ³Ó#%·Íƒ]·ûE6cµ³ˆŸwÏæöÖ£ë–çQ+S(duÁ36ÜF_;7Ó~mÍ%ÁP0ÀyÃh#CÑÀ·'1Ój tMã‰2м™¼œRž(œÅb_ß"N ’m`ÄGo7 W‘~V-ý°Ã´$¦'IšñC¹ÔLEå™RI­qòa­¾-!u–T½ÁÒ‡³æH÷Ä,:‡Ò¥³NµsÛÍ nTàX i=º {É'd2–²Šœ6òõеoøÀ¬¥ÚØ8ÜyÉ $Ï/nH¦ôq\2KrXærqb-žËê{cÍþ>É&ˆº^º–z»q^üŽŒ”¯ÑÈ/I©|¶P<ŠÖwOCÆŸáÔŠ7ºæL@ ‘ƒÏ*B!&úÀ¼Å¤¬¼ÞCÄ^ˆ)äL²F£ÞøbÈ8Ýæ@|fÎÚ§°Ñ(ú7RT _Ÿ¥0̷˃ÈK¼å]%ƒàN‡kbÿo‘¬öùHR®H«™šóïÒ¤¡QÂ34pøÔò‡{Ó«joÆBx„zµ@²Ä 6JTÇkL˜¢Éñ•¹ÇthOìú!k{7yy‰» p±}rqO¥H‚0P'¬\ú¤Íû[ý"{É9Gš­¸7u£Th 7œ&Wþfååü‚GðèbuÓ™ ±SS Óm6©?H/Ÿ\Yÿ€nžc‹"YßlçÒ^&Q¬ž3å ÍóÛ0}Ë©4(¦]ó±V Ï>©æÜáð|‘pb3+À=‘úo?aü³°!Äï‚ú'$~ôŠû nÜ㲪µTÈ>+>.èñas‡R\A, ãž‚Á’ÐÞbKV¥2U·.W4·-ê<ØkuäÇf§~àgWAJ¡chþ+kàO†HŽ¥ "ÚÆv.­ÿzÑ 9ZíyOšþ«‚é>4ÓecíiS0|˜ÿ‘}`_¹&Ô9¯Ø» cÞ¡›ŒX²âö"àñjèwÀnþMVS-!ÎÒ«Íð,:·¥ºgQ] #¹@Ë>Ùb8>o8#‹Ö>ÌM~¦“ž’BÑÀFƒúµÍhP®¾™¥Ú³&n»]\ò «$=®u–ø‰ã-Pß‘×{¢:’Ú²µØÏ±6‘žâ5“¥ñî‹å1šWlìžá¬¦0ÊRRè àny*µõôM„5¶«64CEŒÖØ)f6þÂ^úáâô΄K·Åo˜ ™ˆ‰Maú⋞øGˆD‡SÉ¢ÛÁ)ò*‘eR‘:  W ‡ŸíbõE-“åiÍÊÂtà´4†ÓrÁCñâ¯<òœ5ÿ 1[~…’óö`‹þÛ­`>’q¼á›ö ÍäóÄÅJCbÙÚ|ú e¸j”qŠᄸkiêu Z~§\yÇâ¦ÎÃÌ\ñ¾ʘ&/ð@Ö’ 9e`U9ÝBOCPÌଥ ƒdW’näj%AÜ„Dü¥¹\R-x뜒‹Õ£Z¾|$]&ÌÞ ‡c3]èŒæ…ÚZ¹ötLûÈÔâ­õþ‰×j›¿:Þ.ÔcÑN#}„«¾„fØ“Ïh›µ·iiÔ!lµqç„<õ¤C°õdÇp²(rgàÒ…f[èquÅ@öÀH¨—»\žÅmn$D«‘tONCXqîв9U´{þÒ¡åÁ%{Õ,P›å_«W U¯ìàýÀYûºéy\o+á±nši!Jÿ:–™O)36Ù0\BµÑ]k‰‰yÃÂÙ&üóz ùÚßIiõI—ýÑÔb3ìû þHžú‚7>?wwÑ»‘sÒU“ýxÖpt%•.8¸ßÞìÒ—á¹]yB~´ž[aÛLÁLyÅîæ•S§"1ÚD÷VªƒÇIÙçë`?Ú¯4ÔZÌv?¶ÊØkjî^âa3ZE(Ì5«…ogšé’ôÈØ¤ëûþ­˜wU"}c+3Q6`úcÖ#IKíšœ×bYéÂܺí+ºb½TоŒ·°—»{i­È˜v§ìlµšÚO/ŒÐû³À-«3t0ǩͻ~L­NdWvK¯~ D(3¶·¡$_Ûc@µäÆÂêðGŽ^/Ð_!ÀIÕ$ñ1èûm„ñà ¨/ëz¢SÄæ¥žã?ñëUZCMa&Ó«Ê3¿æ] ñf÷!î¼´4x+ŠFrPœßc™T3`P{µ!úrF—^¸EAÖo:+œãg Pœ´¨q—DàITùÜ”f^ÌnÄêÎÏQÕo׿xøU’éü«›mrŸ—ˆW—yù<#†²qHƒ÷˔ퟎ†H  QHŽÖi‹™xÚ:ßñ§›mî<û ` +÷A¹rtzܰë°8Ž»ÉN Pÿ.áuÕÄŽ–»¸`ÖÀ8 bmשáî53ä7”Ô£­†Ó“q÷Äz©kߊi~ô[ž ùÚº‘Ø]´ 2õ¿ö%ÁFÝ$¶†\LMlDÎß@R/¿nÖ¹>Y2ÀC[5¤¼‹–7PeÒKˆ |xÍÕLÜï{n`yxÕÔ7u\^cÿO ÇXâ1(êq²°Í²Ó¶ÌxGqÁÀêE‚:’Æ®‰ám #RØ™dRœBòôkÿyÓ¬ZuI¯©JøtätU<ªÑÁ4kB§Èk¦ ÿI­oØÚ _˜#1ŸëMþŠä*CÓô^–ÿA­ÐQìØ›+ÿuXLÄ9b¸¬J.)Úq¿„£PQƒåm6K½J£î»‹¼ïÚ:DÐV\1ËÎY¹Å>ëÉZ¤%ø¢RÿÆJ»`”»Í\·¶0/êÆBwfL{½¢îm'Õï¢ |µLÆŸ\j/.»9Ø6¸ºp½¶Xf¢‘§IÀ6çÕ1ñ‹ÿ|ªD¥!a›¡FøçÙ ã\;; "ùø¯Æ,Ì’rèðwA‡™¯eqê¡>áÿd:ük(”®#™‘j-ÅLù(²AÖN87 9r¼®’Fèΰ[@»¾„(˲‚1t ÔÂ!Ê€tÝo¸änùß\©™OÓcÄ™ÖÁq¤t?KuÌ-æQ ÆlÎ\æÏ‰#´0ÔÔƒë³ljw_v.¬4òkU‰£ªBA½½×¸°.Â_–sn½k:âUsf—Ïœ…ì·Þ£ÎIâ°RÆ)Áo"vD”#NBºEkhg Yd»\AU™D.ôMÚ‘0Pb¦']r'²£ÄÆHþ¥õn/ˆiÆ%Þ9thײý:ÚÙXWÌŠM•Ýw2•;Ì‚bX`™ô·Ñ,S_»†øàïÉ­e¿ÆPÙå¶ý¾ÝïU"›U÷÷²·€[Í¥NCÍð¼ ây„¸âf¸ñÃ*KÍÓŒð.ëˆw׆“†î€wd*®xçÝxvn™v°Ó­HYn $ŸMD,§T¸òY6 ?¹£¥ª’ÁmpÓþÎCÇÒ{tåí›!½ƒJÖ¿ÚhÂìNC^AFF ÒÒµ8ð†$–lÎÀ̺6ï࿺mÎÓÚ{¯Ø?dª#j3Ÿª¯gu™t.]ä³¹ŽXÐòa&Â5ÊÆæó17ð~7áºéKë÷ÉÅ4¡Y»uoš,>áYbx´³´Ç‡h3q4næºdûØ„‰R”9ñT)Ë¢ ˜ÍÄtý¦+‹ºNè¼)ôä¾–`šQ€Åu!å//ļÈm@¿Á%å-è9¿“»Sþ”#s4 œÈcQ9ÓütK±KN¶‰!í§ºE6‚|¨Üam»eCŒžÛMK¹7yEo“[ èy­÷ZÄ¢Z$Û¾k”ݼ‰²îáòæÌšT‰ß¸œébþRzf$~ñažRòºŒœ5¨¿†²ÒIÞ~Ÿ¾ˆ¹\ÐüðÕóúeWV¸±'ê„Í5ú¿ 7B¦_•2YE€y*Øl‘‰GÒ˜ŽæÐµÅK°K MKƒ ¯Ø”Di' è)ÐÌ{Ÿ`!/ÿjÞýS_®Xï"vÕÙžÎO]Ôƒ+°ƒ(ËBè¹ÍäW¶{{ì;)ËÚì—d‘nÅÊÌ‘ö‰œ]‡³ÈøiY¯0Ã÷Èh,Ö«q5ew+­GùöH"ñÕy¾ÒVôÎr.Pµàë,ÅaÓíSÇ–W<¹™ÊÜ˾-§·…¥óµ¦b‡"h2˜Ë£½îÊJTÁ*Âomˆ¬MßaЉ6¾ÏžÖ3tŸ˜'á1›2¥±dÂåû_¿²¿.ñ®¸äî³mû,w”MÔiµì‚ÅÕ»Ú`b[þ¦žƒ–å6í~¨‘ý³Ø½›v8õŸKÙéàÝ×^%µLV8¢ÐEcê#"#|¸2qˆÌl)ÂËOäŠ*P-÷(#s•DÓ&ïL è)ËéÁ­XÁ ±mÐjÑ ŸeÃCIóÔõù6|ë’TùÀédãÅÈ„%ª*¿ÁšN’yžYׂ¶ë­$ã?ý‘øþƒP?”É­5&ýcèquWžÌk‚­¥ÝX4ª *y¼Ù¶k Ërø ÓRä•-^ ð£Êº¾˜ˆˆ%‰å¦ÞÄm|»^È"Z¹ÿÓg`§ã,5èÉR\¤L½0„ E£ëH©"ôóïÔå±Îâiog’]Ý‹)Ýô]Bc†ëIŒ‚äNʳ‚ßóšÉb±mÑ!?âík ÌíMÊA{‹Ë™²eu­Ú0©>˜?G{óˆÆi^ÆÕ€|u$;=Ìqd~•‹Qf) Ôeæ[ÖPCE~þrÚÁôÒè3C‡ÂVõ¨&ï2`ÎDÙ‹3Ê»‚Å–@Ð!´ÇWÜ™ß}ȇ¡Ú.žˆYˆm®>ì÷a•±‹!>U¡*ÑÍ÷è¨a_ÕKFK³Ìw¢JC8®…pT|$Žä1ȵH9Ws.Šßi4‡‹:ú=­I÷c2æ£ jêþ¦Ù¡ìɇ"8 ˆ]=‡Ý¯pvŸÃË/ÜW zñ]ûvAGå â2`¬¯B!²‚RRD/ÄÕÂX^¨“¦µžÖ ½ ~g§e¾ýç˱ðC•í¹²Yx(®ëèž#vwÿK­L%$æÉ0í*Vü{Ù¢i,àfp=ú©í%Fño‡_ü¯–&\ž¦ˆÌ׳Lçõî¾þRóõ¸:ˆ©?m¿…%ÛÛ‰\†mè g…êYEt[ƒxsgÐAnb2llPbƒ6™²S¼´±LYÕÂ-~YŒãÛ´¡¢„Q ;Û‰L‰¾å…ŸY½[4"¥²ÖI§ÃqÇL¶ßªDÖ²«‚·½5Šû÷¾_± MÇ \úNj£BC'& [h±NDÑHºªµ)¦ïÊ›kb€ûî326s×q#açå”? ï•+ô¿² ¦°Lè¼RŒcÿxáÞD`—²àf‚”Ÿ•û…“ê\nfMÃy¨¦O }ÊcßSv²†8üN„OyÃù‘¾¶D5µfä+&”±øk1~‰ŒÇåïO5oØ;±5Ô ö¿ò˜ñ¨øOŸUrqé;ŒtiÛ"Q‰±HŽ#)ÜêÜZYwÊüù|×ûëªh²û%‚}jÇ.m%6ò {,¡8Ð,_Á;³.(¤¡¨Ó¥úc‚z‹R:þ"msž·Ïšë(ȨCÍÞ-®_^‘#èOíüqQ£<‘jJ-F¨=•=ö#†4wĶþ÷þ´’αÛ€s‰ŠIÊ(“W¬v¨ £o´Ÿ+n~&j4ë´Öë±pŽQÞƒƒÊÆ•«<vó?ã2ôž?.æ ty³]4%â sQ üPnÌ«èüÒI2Øy3ã'R¸õn†C®ynku¨/&CÒÐ=§GçIWã0A™Kmõ íz,ÕâY *„8Ä`ØEhĦtqï÷ÃÉ7¢[ÕX ‚fí3y Ðó¥Ñ"WúR ©m $ÌP·lãí“B¥QÅ„Õ77Ø÷IˆjôÒ Dâð_8aé4¨³”;^P⎠ûe…E¼IáÐ>ÞYQ…ë «+eý£>Ï«¯=Z¨´µ©€³£\¤ü€ûJT‘£é&î¯èNÝ× Ý„Z±i °©¿bñœÃ]†X—Þ„Å€ Hñ›é³^—Ïú£œzüÛ;Õô`®{<;i ‰˜J»gäZͰЊîÂKk–[¢“q“ì/ëˆÇÎÎЊ¡i¸r-†Q°ÇÖ¿ŠAÞ¤Ƶô‡O;=F|hXÜðêh³ñÓÜjªCqšì L4QHTçÅ øN²ïÜØ~©ç{_°g7˜$ڛЃƒâçS:–€ìWË›R‹¦I®§7líEIWd_éøËù¶KëEì€$hùÍ~!¥l`¥E)¯2 ª’ñÑuÒTÜBu”&A%çŠSp;MuîÜɃƨt×ö~ ¸ò;½ØÒqt—ì!üGßÙýlXÏ ¦¨¬vˆ¾<¿ØøÃÀ±j2›Øe³Àâ <‚6;Žxy{´ÏJ™;‚Ê“Múd@¡%QÊ­Eg·ûŸ²ÙW}†Lh÷ÈCæñý²ƒ©ЧQ Ïuuu²q†2ÓSòëg2ïæbm¢"Ÿï1]×;`P”y!QTD]2Æ"Ey£Qëd.”JMÍ·Ø"Z×zÁdH"¾®è§·«W¾— ÞÙ©¬àŒ•·t^×{WxM;ÑoSHÜ÷\uí•Éú½|~{&Ú=T–NN&[ÇÏ v[=ºUJµšš¿q’>lØ>ì"Ñ"mh,/QCÁôZ±­K[x.§ßäLúÅÿO{½¡xcnÓ¨~ꦊñMÚóžOßñ¨É=F¯Q]JNì]‚ÞÍ &s‘iTo|¥nï_’àáœöu=ƒ× Ò­ü¤8ŸðL¨3€l(ŸÊ±Hní ¤h–•µBù°mÝbãÁÄÝÁ›ÙoJ˸‡qIè9KánÞyÄ@“l²iÂÇœÞ3ƒ=¢¯L 02Íâò2;MOU«¦áÅ3¬ÙjÖµ÷v¸î#gmû÷;¸gX{áT‘"Á‘óŒ(ÓqP€ÑdbVýðõûãBÞpdVzí0º!'…–™fúgŒ‚”űâ)¬à¢IHÈ€f×·ëÏöž›ñÙаÂ/#ygþ&'5ÑþìkÛÞÀó=lyz¥vçSl†0ü xkGßÕhæJ¹õxŒ…\PõGKó9üHXКj»pCR©pòoþ韋ÎËúsÐáäÄËZ41É^tI-ÀÚ·²è¹PŠMuÉýŠN3®cŽ(ÝÜÈ„#°ÍS¿ñz)év5÷º…Àè½:Z¸õ Ð@Ä%n÷ïbg‰2/²+ut:_ùi©R“ÑÅXÿIÎèFZ˶Õç¼#ô¤qtoÛ¯;f”_.k¡ ‹‡€[%Ò~´e|­Ø/¿ ˆ\ÛÃÖ7n#¤Çh^Ò¢°õðFÿÝ¢—6E¦9vj«È¬‘ÓŠa²ªDÄ´]XD–=Zv‡ûFu¾}°°w.­*ª¤¢!–ÂÅ•ŸÆÜùpáeɰ‰ây·fª*W'E+m:% ˜—,õ3x˜RZRxÕ¨Á`{¢ØÚÖÝL° T/éÚ&Ä€½›ÃØ“„AM£þÄa†{"·œ”A|ózµ(ÁøõΡ¶äó Î’Îëm.ßÉK›]}cýß`×Ãzy˜n x+òÓûy)YÐ}‹ÂqË:*¸c%g˜¾5¸ì¤³­+i¢ˆjjõ‚õÓvÆ£õ¡Ùø'f•ðëÿ%ª|žéÙÑ(uäQ™>ç_Ây{éKL .RØÜ=í6Ѥ.ºË¯t\)„\™Ä\Ü>S¯åc#?öSífÉ«²Òzï>±ePî½k—î¥_8G']Àj šNá*r‚Ïž{„aÊäC¨ ©^ûzVÿx¿Ð4W^ktrÔñ{®Ü/uº¸ÕØÕó±Ï§ÉÊð/‹@:æ e'׿¸i›@„ ךãƒrc‚Kp¸˜q 1ÑcÜë5Šu€m¡4/zÉŽ‘â ñ2ý8,/7EŒf”çàÿ JvÅŸ•ЯºåWyNŒ'âöÃP£×˜Ùü¦ž%†ýRuUY¬Ü§ùõ6®ÔXŒ`e$Qý£,E›Éº‰s8Þ‘ÔÄ ýéèV|®>¯u©eÙ+N7 eÔnÊ ©øQ«¼R¡è€° •eH¼jͬÒ5±5²Cp¨þy÷:OøP³Ãc•ÐbPèD+²4Š º ©(cBùëÄN¼ºã¬ïXE¥´9 &°”ÎÞ^†³ÌñU$ ®Ëî|»­ƒêH«§C8i¸ãäÙŸ´À%ŸCtî”ËŽõT¯Ÿ+©ZÊäš‹gõ>FµÙç̨ñ¤È ü„OqžZ M‰ó¾‹¹üKX’ìÇb‘ÌçÌ‘yB¶øÝiìªe/Çwìs ÕÉ3ŠyÃYðÂ˳»óc£[ömßíåƒñîÊŸkÎ¥#"ȹìÿ<È«µw ÂaBõïS×ú{äsjDñ'½'U4(“S»óê Sfùö€‰¼y½åÍ]²FW •§]ó 3ö:)÷%S€ÄÜwhXLÑ^tE¹á^c­rP÷ÜkÝ€¬?_pÅÓÇßu@Øi•¯¸þOÂôöœôXIÀ¹¿;œÜt±Òöóš„{%!ù·7T@ÙA!µhšÐl>…ž±v‡qp¹†-‚Sƒrš5ì°{€X;à×@°øðyÇ«óm¢cŒÁA0lQÇWñL'laShž<õ‘o¨¼8:”ŽTŸ#^l’ Óî Â2êiauŒ¡ÿlµ2PWaƒ1ejtŒbvF¦4vÖ<ÆõèÍ¥Žµ—A9ijËᙈ¥Óy5ǰU“uÁ]ȾìŠuñÄ=_š/M#Ũ»ÓlÎ"-}6Õ•áOôb«àz­Ý‰#¡?AH¾` &¨>ÑómA]Ǩ1ŠÜ×¶EFºÇ] ¡'Oƒ9·§#yÓmmC«e+k™´$i⣞^_HƒGÃç·ï›Ýþ¹+fíË½¤ \ì'½*ÑÕ˜bF” W§Cš¿üÌø–îA]n3 ÆÉÓÆ&±·D Ðþ0#Í­ÒŠG6'›;‚e,O€ÑëþcYDØs"_£TØ­Ì’W¯ƒ>€mÜáCëÜcˆb~¬ðg×fšìh< ‰œ]d õugj&ÕÖÚ½®¨>íÀÆ,-öÍLˆO"#oÃI(¾ûŽòô¤ˆ¾é©z”Êq—wà;œ¾­½¶ALó¨É}—ÆýRZK¾#$o¹Qò¸•}¯ çTÙ)Xì.¤g n4?°tß9\$™ÉsR ^É"¢ß! ;‰?ô<—ž6-+b½ãçôC½'¢ÛTù&—îâ´0ô¹…v')ðùpÄæ8ìváÅÕ Á%¤Ý >ç`UÝÂ|wŸÝdÑÿ–4Tx>è’ë^«hÂs†àJBQOÿöM?§äÛÚàÅ¢vès7¯shPÚ«ƒ"æo~?88¥‚˜íÓµXâ¦02ÃñÚ÷¾=XSî€Ê"o¼U¾` îS*šà)¡°¹ ]–ÁtRoÅemx)y—ˆEõéÖ¢Ùö+wÉøæ+~$‚Ô}ž[)¬6ÞN˜z8'¥n—‚xš_ŠÀƒ§Ó1îÖgWU鼦^ƒã¡{J±çj·è8z¼qµk(BÌÅ2%• ‡7삽¸‰{}ÆÏ†õ&¥¤¹×šÌÀßz·‡¸}1u±<)n·°¬¸hÈÚ73”ra¡)èÕ{ã©l10ª@iès¥ÅŠY/<³UÒzýÙÒ¾ö‹Ð`z&üjB”k ‘ë°­åµKG´Ë0ùv¼+± Ä1ʪ\tW šÖš’¡Ü.1ý–“¤D=!x˜ÈNc} ð@n«¯[cP S`-rßùàçF³…/e6~pÞÁ ×™­“}~çBÚHÉÖ×@l3xÐÓé£XóûÆ¢7ô¸4¸#d³›iyÉ"àó(ÄK±k¡ÑõÛÐpèö©ÉµŸ†&nÒªu¤™¿ú]±CÎ.+õÙ0´ŒÔ'¢Íy¨ïð(7—üÒG‰Ð·IyRg+°“Ò“" V(*FÛ¬ï¿çðê—øv•ü;¾ÝïÏáÔ'ðíoøv©ü7ÇçÏáÙðì7øo8È[3Öñ¿B5‹¼Ýà.&VŸ]H)‹jh›Uæ³Ó#%·Íƒ]·ûE6cµ³ˆŸwÏæöÖ£ë–çQ+S(duÁ36ÜF_;7Ó~mÍ%ÁP0ÀyÃh#CÑÀ·'1Ój tMã‰2м™¼œRž(œÅb_ß"N ’m`ÄGo7 W‘~V-ý°Ã´$¦'IšñC¹ÔLEå™RI­qòa­¾-!u–T½ÁÒ‡³æH÷Ä,:‡Ò¥³NµsÛÍ nTàX i=º {É'd2–²Šœ6òõеoøÀ¬¥ÚØ8ÜyÉ $Ï/nH¦ôq\2KrXærqb-žËê{cÍþ>É&ˆº^º–z»q^üŽŒ”¯ÑÈ/I©|¶P<ŠÖwM³!NEb·Ñ=¬PiT_G £Ø³Ž¬Ññ²lö·ŸztìR$W‘ßÙ®†ôrã1vU=f‰#­’  p[y )†e¾\~B]ç*é+ü—t;[þùÁ?o”…*䊹™¯>ý*J#3G€mO(w½:¶¦üd'ˆG«T (Bat¥Lv´ÉŠ,Ÿ7¬ð;4 ö}µ½‰¼¼ÄÝ‹‹¸X¾‚98‡Î§Ò¤A(Ö.}Rf‹ý­þ‘>C¡°úæRŠRPÇÂg:7Ä~dÃ䉮¾ŸNèr7¯Òôš{¼U4Å6Ô숧•ùÏr† òÅ]t§¿«ÞÜ%^*DËYBÖ)K݉?7XÅgoÂ/ä8/"x! ^@ɇ3ùúþ_£aÇ•¯’AöU¾4Фõ7p‡^îð•§?WMÍ×Êb²”æ€rãÜ7ÙD$O!Û!ÆjâÂ- EqÏRJUÆtûˆKV¥2U·.W4·-ê<ØkuäÇf§~àgWAJ¡chþ+kàO†HŽ¥ "ÚÆv.­ÿzÑ 9ZíyOšþ«‚é>4ÓecíiS0|˜ÿ‘}`_¹&Ô9¯Ø» cÞ¡›ŒX²âö"àñjèwÀnþMVS-!ÎÒ«Íð,:·¥ºgQ] #¹@Ë>Ùb8>o8#‹Ö>ÌM~¦“ž’BÑÀFƒúµÍhP®¾™¥Ú³&n»]\ò «$=®u–ø‰ã-QFµæÖ”–©çŠ#iX° ‘ðÂ72 {Œlç^p8-œ‹9ðŒýõâ1¨l!­µY±¢,f°†ÁH·§üYqyÓ‘~†`FÁkŽÿ<‰„µ<ª•sûý”@OhÀÒ³À[§Ù«f˜åj Òz%Å1Ohƒî±v¿¶i+.Ã$/ñ; õõsíiN¨-«Ï|§#MÌzþ”èw»(\k³“¤V;´‰% ñ8–XS•åíthæmƒª À'2¨e˜T9—eõA뇲œ/+ q¾‹Ü*÷gì[œú<ù0À²ªe qž[L Kb‡xŸ‹«ìÐ÷Bï87 üÌ¿ð;Fµê:ø„-Ô%ç<d4L+µâ´W™?Ÿ&Ú‘ƒœ-_««?xVk™ÒÖúÌ„VôòÆÚÍ}êª{_€l*h`7ÆÀÎ?XádáàWhðíÙ×§ÙȤe3Ð Oy|s\¿ˆ µ*A¤eªÉ1è”yº†—ÌxþŠ0÷ ƒUO¹t´5ºòiÕ^ú¹ã~å9Õ€GÎ ‡ABJ\„”o¯¼<­ì‡NàLYƒºSô*£}RV+}¦lG\UbE½ѰwØ­$î“7ã™z»^…·ëG1|-üÆ£¾Èß³Û´Âkx›æä¶eÕ9ú{¦S¾€N—µAÁÿl­°Ìß%±Bß1.>gÛØZïä&‚­9]h{:wƒ5—Áx%ÜGz Õ0v È ZŸHmeÊBZ¹¤–ö¢WŪÇb6ÔØõôJ™,7ôéŠLüKòÃ(>]ª=n¹2l«BH¬¿é’ôÈØ¤ëûþ­˜wU"}c+3Q6`úcÖ#IKíšœ×bYéÂܺí+ºb½TоŒ·°—»{i­È˜v§ìlµšÚO/ŒÐû³À-«3t0ǩͻ~L­NdWvK¯~ D(3¶·¡$_Ûc@µäÆÂêðGŽ^/Ð_!ÀIÕ$ñ1èûm:0&1Á°Û`ۼߵ ÜÔ°‰ñìyL¤–ûý M¿æ] ñf7ÆV=ì«~-ïeQ­²x2ùzú§U›³ŒêåˆïwGãÉÉÊqÙ,ÞÒÜ3°¯ÿR5§—i°ƒÂÝ•<8öIØcK:(âÑŠ!aν­á°¤S™@b »© 5µÑרÄiùNGƃ-9‡Ù:Šœ³6±*¾kesnC|ñyµ¬DÂÂ`!: ¿ó7›ÖqÐ]ô…Xã((:®‡ÕéN0“? Î —;€ŒÒñ‰†Ïž@Zx»xÜðfáüY9ÅÁ*×®Á«ì槺¬nV*l ±¦Ô9wúIÜ<Ç|]þ?Ꜭ‘ÒpRV(aZµœa7Ȥ-¯VL»lG]q÷ý4ÕH°F~#'Ž@yi#1ÎÓíâó É{kf·»ÆõºòM®P£ÈkPóYî¡à…žŒí<ÓUîÖ÷ÑUGxA 7ƒ¿º_†ÄÏ’×äGÿaRFÛ4nž¬:88®L/¸âÜæ»Íãs(`’2¯1Ìmj-$%SϬhâ"\->°ÚPecË÷þÐúѨ‰ç²`÷Øë\›ùÏáv×lœðÎÌ…Å•¹ˆÈ1ý­!Zw/EYlݾ€ûòCÛ°× @+æÌîç¶”iÍ-:¦/4õ3Êl»’*É5Ö|'ÀmŠ I[aäÚzœ’yÑ¥sÀ0”¢Íû9×[àÛ=dN%’£f“sƒÁ/ãacÒÌ7\Ì §»Þ|®ë ëÞûa(ö¯6i›¹¤€‹deÞ„8¬±ÐÓgŸ¼ òÎ*_1zf¦óàŒãPŽAɧX9ý]Lô!óž=Üy•"¤D¿Æ,Ì’rèðwA‡™¯eqê¡>áÿd:ük(”®#™‘j-ÅLù(²AÖN87 9r¼®’Fèΰ[@»¾„(˲‚1t ÔÂ!Ê€tÝo¸änùß\©™OÓcÄ™ÖÁq¤t?KuÌ-æQ ÆlÎ\æÏ‰#´0ÔÔƒë³ljw_v.¬4òkU‰£ªBA½½×¸°.ħR3n½k:âUsf—Ïœ…ì·Þ£ÎIâ°RÆ)Áo"vCÉàQ’_Êt£L0q–*‚ÿ Ïˆ×Øµb:S¾ÿ;%ÙÚ·yÝ4Òwª¬ÌUVÈ%k®Í ?×॑É;6£å!ñ­/{LÜ–úTÙ¨ˆÉ•Æ8Ä@<ë“ýÏß’\ˇŒ¡³Ëmû}»ÝSÕ¢k~5?rªnWÑõò…‘ h§‹ |<…¿•OblzXV™ªêª?¬ UÄmoá²¢Œ+ý§Y0‰ÐC;wñÖZ—ÖñݳÄ-Ð)V@Úqaó?;o2P¹O®²½µW@k4¢¦;5º[z¦c%6¦†6LZ‡?®Ó—ë m{ƒú I;PÄõ…83è.&“WÃ9}kx±Ì¯Ñß÷NÐ »‡ìE1ܶF;­ì×”¦ûÁþç÷mEð~7áºéKë÷ÉÅ4¡Y»uoš,>áYbx´³´Ç‡h3q4næºdûØ„‰R”9ñT)Ë¢ ˜ÍÄtý¦+‹ºNè¼)ôä¾–`šQ€Åu!å//ļÈm@¿Á%å-è9¿“»Sþ”#s4 œÈcQ9ÓütK±KN¶‰!í§ºE6‚|¨Üam»eCŒžÛMK¹7yEo“[ èy­÷ZÄ¢Z$Û¾k”ݼ‰²îáòæÌšT‰ß¸œébþRzf$~ñažRòºŒœ5¨¿†²ÒIÞ~Ÿ¾ˆ¹\ÐüðÕóúeWV¸±'ê„Í5ú¿ 7B¦_•2YE€y*Øl‘‰GÒ˜ŽæÐµÅK°K MKƒ ¯Ø”Di' è)ÐÌ{Ÿ`!/ÿjÞÿ©yˆÇdÞ7– ¬EM=ƒ Ž VpâZ£Ð cçÙ9‘1›2¥±dÂåû_¿²¿.ñ®¸äî³mû,w”MÔiµì‚ÅÑa5÷®$­ä©K³ ‰º{®¬›£­ý=t»íuø©Èdêã{VæÄ¯2ü²^Ã÷æ À|2ãVTT-|aFïœÎø­];wˆ§7H-­/òhÜ4ÍÍÐ©Ž¤Ö’NŽŽá ±ânñJî¼%^ì}$:ó™ëÏAó /ܧ#bAÞ/½(›ûå-aÙÆÆi'|Ò÷=ô)–å²øg‘šû&\ b%çñõÆnØ‘eánéR¦ãûV—JÏÿxõ•k1?ÍÓÛ#•g¸ˆ} ²¼nÅÆuHsÖczK8œ~«æä—¦P‡C€¦jøEýÙ_õZy=y㽕ø+¾™îŽ'©ÊXH&ÒœÄÉß—««¨h¿0¬™ÿb±Æ)œ’Y÷·k7xð©épìFÊyGÀ¤Õ»k6Ï‘L •O:œ¿:e&HBu¶²ËA‘¹Ò¬ÒaŠUKq$Õ‰Â[ß»%N¼ƒ÷¥]†aÀÍÜĶó¥q5)´OŒžæ åklÈL+ËCžûv°xC½ eÚÑA¢ n×å”? ï•+ô¿² ¦°Lè¼RŒcÿxáÞD`—²àf‚”Ÿ•û…“ê\nfMÃy¨¦O }ÊcßSv²†8üN„OyÃù‘¾¶D5µfä+&”±øk1~‰ŒÇåïO5oØ;±5Ô ö¿ò˜ñ¨øOŸUrqé;ŒtiÛ"Q‰±HŽ#)ÜêÜZYwÊüù|×ûëªh²û%‚}jÇ.m%6ò {,¡8Ð,_Á;³.(¤¡¨Ó¥úc‚z‹R:þ"msž·ÏÞb¼ãd-[I‚rköXy½ÍR5EË¢>¦|nÔÊŽÁ³ã¤QŸ8òQküœ#O§5Ýÿ©1Ø´¦K ‰ £~h:ûS¶T¬ç£Mj’’㒚ЋLM1DjÏ ¶¥aI]|Y±Â¸U„¹ gù9¤ZÂûÛuÌ¿Ýõ| 8J7ÅAc§%.B§ú‰¸rûdìÀ 6æ’Hü?}¨éŠ©P§ÌÉå  Ò ¾e7UZ‚§¡ã!¨ ³ñÈvÊþ÷ÏU²ÁAƒÝ(ê¹âé5~.ó41ü• -ÝÃíñ•@¿÷½xÙ‡#¾b?r©B“º7çÀôV`λ”DjcµºŒ†ÛvT‰ZbûÛ2ß*Áä*b3“ž,=õ6\@$lÖO…Q6ê´sc%¤¾=„/ì9Ñ áÞª™Zp x˜G oÐxÄù1¾~ ŒÅFO€®ÌüÃ*Ÿ‰Ò4€.«<;³/˜Ûõêƒèó¡m:‡(Ýý+¼" /‰ê(z?ö{ä7ˆ5‹´aúeƒ¸=s±˜CGqý!Á€µS°Œ!Œ©a>º,èyq…H[k@ç=ƒ’dBßKUN4vRE,’ ©ù躲Eç¶·‚ñc‡pg»î³^ŠÔªDUÚB:Ñ|vsÔ–aî¯?=ƪ¤7Ù®ÂÃE‚¡÷¼2±®FÈgr ̱Ã)¶ÕÂ@gä ‹»ƒ£`RÁ×»Ò]œ œmÍ .nãP:Iá˜ç&KE7«þk—-õ•–¯¥¢6\E º{“ðj›‘dOÄÊ/ÒËHs^¾™[Êú#ݶÑÁd¥ZÜ€Õ®ÒñR¯ÿƨt×ö~ ¸ò;½ØÒqt—ì!üGßÙýlXÏ ¦¨¬vˆ¾<¿ØøÃÀ±j2›Øe³Àâ ð2àÊUɸø­SvÅ À0š;Ù~Þ¹<”_ÜKrg‰4Ôõ¬A—ì¦3í‰ (Õï5¦S’¤{¯NO`¼–h˜,üÞÛ}‚%­w¬D‚!KêîŠq;zµ{épÝí–`G Ö9±°Ç »*9,Z«¹€&s)òmÍÞ$áÒ<°ËÉyJŠ¡3‚#) “t2ñí\N ÜØž÷vB9hß³M¯*¤è`\»ÞŠ¡£HY¦Õ%˜¥°¾^3µS»r¤Ô:‚ÏBŠˆœÆÓäk{,qŽi :7·›nàBü³ÿ|¢èÝý¹QÝÚ!8Àh.½A#.µ9ŸäŸáœöu=ƒ× Ò­ü¤8ŸðL¨3€l(ŸÊ±Hní ¤h–•µBù°mÝbãÁÄÝÁ›ÙoJ˸‡qIè9KánÞyÄ@“l²iÂÇœÞ3ƒ=¢¯L 02Íâò2;MOU«¦áÅ3¬ÙjÖµ÷v¸î#gmû÷;¸gX{áT‘"Á‘óŒ(ÓqP€ÑdbVýðõûãBÞpdVzí0º!'…–™fúgŒ‚”ű§æKê@b;û^‘jZëzô¯Ä uÃÑ¿9igþ&'5ÑþìkÛÞÀó=lyz¥Ý¾MR~©ïåqÅFCý &#tƒ7¾=<Û ¸Ÿéî?Ò2“†Õ?){ìéè°M߸íw¸Ú:KzÈ…•éÑ5 Rn¿™¤=X\55o‹$)ú±K{‰‡ƒχ °Š¼BUPUâ•ïQØB~¨'z¹5Z¾™¹ÃQŠMê±C†TLÊ0*JFW³ì[áÞ ©% ™7ûù." ߥeÌòÔÅ4PX³\à©xß²ïMµB¬¤s+Už…N™D1;ܬrÂj Z)u×ï@ s™û1ìGSo-`~KÌOOé:7oyÆV|äN|48&nâÊÛ‰©m\.ì:#B…ž-Ä¡÷¥8‹Q8o¸pN³ù³ž¤Ç“K§*<5‹l,ÊR»B[È• ù)­´£<‰Šñ DW^(þy¢ÎŸ³è~ $éÑÔ­ŸÖCîvqé!;¤\UxÚìŸÞé’–l;4÷îR ³´X™R%Aw'*º‡)¶ïbk%&@|`ö'Z25‡'ÞÐ?pßÐòÄ#n,ÖˆãÑåé0;¤ð0åˆÐΡ2su@–?#osMŽÛñ ±Ro^Ø+}ò\ÔÚG´ ‹iæþC’6÷é"ÃÇû¨#ãšÄéi,2œÅÀÙi=wŸ³±õ8ꣾ­Ðг/6h,3ÍݰŸ—)Õ±œ1Ni”WJ3@Àî#¹j£¿œáЙ(>Ysª@äô³ŽFq}bƒM[žŒJ¬²6ßì´ ÑM =À„ج¢y‚s:θg¤À’ÕÙ+vÎn~}Èïé©K’³N+ªjA‡÷‚^йВÿ1ðÛÝÊs £’Bl(þ+¼õáš¡cyaÖäú0X¦z=ð@û$v¸·w¬b w»Ú°A \ítÝýs—ñ]Üÿhs˜õÖüô/tÒð“ïïìÓ¼¬µœoHêaó˜s÷+/wU¿=‰³¹P‘(íšÝd‰ ßü9‹œg|vjÌ :ª”´;©ù^ŠðÎìmŠãñSn YÄöí®!S<µbÇ`Ìî7½bPô&y~”tF®«þi4x¨LY8o/=ïI’eÀÊiÛš719…%ôaBxEú®ðƒB¸}A y-3öÞžÎÂ.™Ú–Ï´>BmT·¬'§º® 9¢w//Ê­ÙÂÙ­è®ßÑ-t< UøŽ˜¾"çlË@ã.ÚÂ@Š1¨…³“é1¢55–(MUYœ•lJolÛ%Ï ò²˜ÓÜÎ ÷õ…›åƒñîÊŸkÎ¥#"ȹìÿ<È«µw ÂaBõïS×ú{äsjDñ'½'U4(“S»óê Sfùö€‰¼y½åÍ]²FW •§]ó 3ö:)÷%S€ÄÜwhXLÑ^tE¹á^c­rP÷ÜkÝ€¬?_pÅÓÇßu@Øi•¯¸þOÂôöœôXIÀ¹¿;œÜt±Òöóš„{%!ù·7T@ÙA!bÌÜü3_@Ånìh\Ã)óì•ÊÝWþ^ËøˆnD´&a(<¬¼ãÀÎÕù°JÿpPI¨»E寋N¬"/¿‰OiûN¤ömÖ/ð­j ú’õ¡©ëÒ¹D>ÿ*Ñabñûxd³,Ž.ŠËÉÅhš‚‹j&Ä·×…1Q™ÀXS¥DØn°#¿8£ÚJ`Ñw·}0l®^ðà) ó±;ªâU¬ô@Åĸ…Ý~q¥­N¦!2²[‹Ð6ýÉÌy™è=0” õ]Jî`¬, í3µ;Å„çpÀ˘ø&f†œ"ýÉø‹Ål±˜’NÔ°t\$Ù¨·Öˆe¸â›mJ<öâ–ÒˆˆÕxŠ{åKÌ2!f+'ðL5+¼G«ú¨œ‘?w~äà£ýСŸN“.ùˆF-÷¢‹k)²pD<Ú>þèܧ)<ÕHW1Ö’s™ ”C Gx(‹–OU’%ë<‹æÌp::´’ý7¤*}{ö.Ççi…(¦9ˆ»Ä6¸òí¹¸ã壓 "šD+Û9¸Šm!·Z.æ¤+ åé¶[ Ê cuUàZcMÌ´ù. 5àY•‹«è3ô¦×«žõYÙ•¨wél|iäß÷%¦ 6ÂËè½?ãùÈŠ`ÉAÔ¾ÈùÓÒx ¹TþUœYnÅ„$,,Tñ_å-¥‹¢X…¯^uþH’½Y vør ‚ød4 ï]'ºkXø‘­…¾Å©¾×Ê"C.ÌR·¤íô™í¹R1£›û©ª’ªn(TýÜŸºìÌ>fwIž—öM?§äÛÚàÅ¢vès7¯shPÚ«ƒ"æo~?88¥‚˜íÓµXâ¦02ÃñÚ÷¾=XSî€Ê"o¼U¾` îS*šà)¡°¹ ]–ÁtRoÅemx)y—ˆEõéÖ¢Ùö+wÉøæ+~$‚Ô}ž[)¬6ÞN˜z8'¥n—‚xš_ŠÀƒ§Ó1îÖgWU鼎yø9¸nQ6«¦F+ë-Rä´úŸlw±qô R°¤7 Åpï’üG1™“¬u”¶CG¸…¹"¤ ­æ|þ¬~ 7l€ò/óJQx4/ô¹‚¶TÍïõ¨^®Õ²MÏ&¿É2YžÆ½qQ"ÁS)2é«Ðº×G·æòˆï"ÁÎS¹˵á]ˆVáÆ=B¹±ý²r¹–…aüE”¾¾¸âáÞ©‚Bèÿ+,`À6\¬–!í¥*@WB9;±é4×R€AC‹Ð$àA+Ç÷º×ј&HBÝ ~^÷ å0óõû6R½^IJuŪB׈ç³Xá˜rvòp=Å#oQÈÚ.]‚$¥S4Õ[p€ä*®èP±òvÆ|ýœÏáÒÜ|þ ¡òvº'^ñü:?ŸÑ¼|ý‰ƒáõ¸ñ@«BíeȨrت3.Ý]üô 3#Ú3púêÜš²dã`ÕÕõxGÜæy~ƒùǤÖËwÆÚº9ªæø¸4Oþj.FåÞó2„ÊkÔÁ©Z¥Ö†ÎAðÁ^^¼æuúAŽ• ¶OMân: Š<¾n#–áÜë ‹æ³Ð»ºæ‘k~yÓ´†eÖ3­ÀVˆyµXöz›?ÖY["²Õ”weÎÞç9ûÚùœ [Ý©e˜XºÍSÔ%<9ANAWÏ}Ça‹±ñŽGîÆ“Ë¬`±®sZ Yä/õh]á_Ÿ*®ÀM:À†*mc/vD!>½ãM•zdz²ÀæÂˆ ^Â%%OLV‡<Ê}%z¬Ó.(w6ö s6ÿfÅS–“ž8‘b”îPŒS°qð"eå-EBû±ðšŸ®HOuµûc;¨Cf. ,‡’—4­TP9KÖ›QV˜e¸O — Ûéšeš`J‘L½‘êΓ‚6l †VÏ|MO(l:t,x lqØ¡m÷úãEFbòrh½YX´½ü¥ë”ë¬q9[I%‡æaÁ‡"ŽëÁQ"Êz©—ËȯMyL¦mÛù‹æ†Ð¦ã6­½b…RÉ üg¤;"ÜËóA8¶ð¦Ù[JÃ4'i LË"¿Ð _rÙ×ÕskÇ˱{xcÔŠs'¶bP;"Žj(\î¾ ŸÕgí G\£\ìrQ½¥¿(!rÊ,f¸«y†Þü9›€"<99QËHp\{Ô¯üCð#ëwߊ‘î½¢Ù N{¸—upµGákgÄ¥µûPmôß"Ûó>Oþž~@ÙÌ YÅÝù²ŠÛ‚àt9cp8õ’ó10åÑ#:$®Y—žµ_€²¯·æjl²‹/NÏH¾q&wTâH#$FüDóÙZ¹è½V…žqhó1ÄY‡.Ö,á˜{Ҹ8ù:d¤ÆJ ;·“7=æx± ÈÊ.M:ùê1W|žã{½ cuˆš1$(ÁšiSϳ$1mpÿ¼sëÆè@:ÞáÆ^‹”(˘ƒi¼°Ñgžœìb`Ì¡ãH´_tËݔܼû­]¦àŠO/®è!U( •Γï0ƒ¯ò]‡èÅšƒHyjúÍNQ'í…åAXÈ•r>ÜYßÇ—NtùíÜR]®*´›!çfâ Ô•MŠjo`ÎÐyYŒ®Òü=a@|„sµÙ±‡q„µ:™¹'oA™£ø$ºúº¾œn ÍÇ„~'&|´â¬ë1@Q ×¢RWÍ/–ߺ¨c>$Î;¥•€û["¨$PM'ëGmÝmsžFØÖ…/€ã>¡R•þ4ÚNÌyé•“ŒØ,œ¼ã}L ®ý}Ê z†U®QŠç]´¿‚’ˆo…éÐ,£\Pe¤[–ÈÊi¯‹ê¤::)¶ô ~«&£¡æ3X€>÷Ý &:*Jäî(âÀ£i͵ǖº¿j#ܰyM<¨š8-ŠXé"ÿ"ݬB*vÿ'Ei ðE¨ÍÊkàѬÃ÷ÇÁ=«d%Í—}<qxv=àú$Ô‹áRIìˆ;AhÅòí¼‚ºÝ¬ŠûûÕPV÷a-¸#p-‚«=f¹Œ‹ü^ÀóIa3ŸÕF4†¯°H2-(·-l§äb¨ùi`¿5 ºƒ“²‰«ÿ>c«s² ÁÈkœJAžß¼¶¦¡ ñîÑúÇDÂKÙý…=¾ ‘s¸©U•å3Q»4o"£ÓæŽëˆ«ÁÑÑ¿²Á¾6•Ì4(Y0ÅøA©´xªú¯3JX$ÙðÊçe%ÒâÙÑ~>ƒeƒ#ÿcËoƒšz•æ"™ åLë$£p·˜"m Ä#»9ùD9-‡ §r@è§§UÎYU€£ÀþH¨üšÃÞð}Oà¸ýPRöðWË•a›Ç<Ý×:Š’> ’jžÐ’3ˆ›é=&O<"Ût®Ì̘Ÿ÷*÷ƒ`òý{j´jøH±­/HD•E»¬ ëžeÀï'!S¬†HÃü^ÛL³f$™:Á¬ dÁáTÉme4Îcµ^ÑûÑ’Êà.üjø&e™ (´ !²/ÂŽ¹Äû¥²®jè]b{N–®W­‹ßf†Y~w{Áêlÿ,ªô-ôÿ½Ü°¹á,ÜÄêXt–Ôœt_Á˜m_Ê ÿdö9Æ“hUÏz´°«NŸðð«#Ôwèå÷\ʺª˜âã°–­IŠr÷†Œ¾ Ù ïQ%4ÉçôFeÅ#k`Gúé­Üèê‘ò¼•ª#nïqºÛ¶B—»i¨±\ºó®¯wA ¨H'寿éýßš$ŠªCwÊßQ¼ä$ ’ʸÔqnòµáî~ù(î#Ô_8³ç(Šœ½K•‘spž€u±§óBÆ’_{ò¼#=1¸_p·sf©KŒ© kDQñº@½- û)â'·ÀcÓ¾ÆÁ‘ÿ@è’$ˆÁy2Û'ëÊ{Jµ°$HS_WÎéŽÙªÍ»z˜-Ž=Õºp[ðô+3ü ui壟X½iªÑLGT Máþ&ËÆîc›W´x04¸¥&e„R‰ñc’U#úiI’û/>ÈîÐÁÓÕ"wžzÎ&wä®UMpt“>bí¡øŠKW>S(*8Å;!–¦®+W ¼ëƒ®g6ìÙó‰ðëÞ’LäŠg†0ÿG° o¥,tSæ)ïÝæbUµå:®p¢ˆ3×oî³Ç‹v*1ŸãlçyeÚ pä2ë„ÂÇI1抢f±³êÒdzýÙƒ>磞ÝAdËû­Yh¢uÛä²–Ë+Y·h8áD}¿ª+4%(áìJ³rýNë€OЈ CRSÇÛŸ£•“G\‹œœ +×rÝ .U¯¨Îºcª7æÔˆ‰ßl¡C¥Þ¬Õ0ÏEs I3ˆ¼­ú£„ýé ¦Ôç÷‡ QÉí§¤¡Yå µÀéÂúh‚v/¹þ Ô¢e¾ûy¡b†3!í£¡µ ¿’•`ÜoSvüÜbéy‰“ÚÈ®ÞýOÛ†H‰s1ñ¾±Ï £KÕ!óZ-I¡»ãZ›÷-¹b%û(H!6ÛŸÆIˈ±Á8Ú=)¹_n¬ŠjP¿óy6Ëëëjo¥ÒºçUb1‹4æ”>>›”Ä©TÓ» `dY¡ýA$žÍɉîfMeæÙsffSñoÆ"SÊ—zÕC‡å±«‚Í»mªµ]¸ÉÉo/úDVç<ÚÕå¡ ?~é, ¶bKþ¦à¼×‚æO€H ¥þÅŽ<¥ïäO?ˆsùʱZáÃp^8OzØòÈ?º5ÂÓ ×ñ4 :SW Ùßù´çòøhGñ÷1VWßö(KŸù(^`ûµ&¦‘#™*¾b;gA|tÑ$Š¡Ô‘%I™]²áz‰ŠÒÌ‘ø½ÇSâÃ6ÓMì6VÃ<üÚ$²ŒW¸6ÃujÀïúàHÔoì™'ŵ4ï™§¼n6©ïÍiÞ!ʓ׿¸Üó> ­®lb_×6ÐnF>m´âP}ÀëÃÍìͲÃÁ˜UNÌÿ}¨.ïë¬ÒÉ—?%"F@ÜvÉåŒÁËñ>Åþrn{4j€;ò",m&YºÙmÐá^ÏÝK÷Õ¿«îãÐPÁÑ{¼+†¬jJ!+‘ׄo꯻n½Ý¦àx>YßFôö½ÓÿMi˜ÆîÅt-?gì*WºÈë#1ËLYÐ'ù”fþo¯¨®Ù6„!Ýüä-ÒUv¡÷ü¼ ê;ób³óð#2òFª2`|†$Ô®ëd$¶Ê­Ìf »z*¹¯Þã¼µyfoo¨¸Jž·©NêœýäªGï¿öˆ„.ü†‘¾ò¿Â÷€ÂÁŒ¯NCiÙ[¨–<Çá" JÁNÖ+ŽâKùJ„ðÀ8tÿ€tsû.jKw† ¼¸)„ô/Õ $òõYQ<äðvãì²TŠEpÙØXãåîžYؤRíA2:l©Ôÿú®O¬½—AHÙo0¥Ë»KŸ®ÕáI FÏÂŽÔ·ŽaÌ¡ ÷`ÂD–!#j)«ž.,^]Æ}¸Ã¸•–QêKeÌûe!ž:å=©|QèÇ…Tå`àƒ¼z®4š+€ðèˆýÓ†5—¾ÿ6À;¶!Ý&ÂîryYÒ0÷–eU‚æ‰Ø¬E²òex‹WA’)Ë„2ØU¶Áýq>¡ôeæb&<ã'nrWÛu=÷*Ý ‰ý>sŽ ²5š‘C­+ZÝ÷pLV„h©¾†ËH˜¦Hgã`pÞ'äð}DÑ$©}I΃­„rHà ė8XÂ_wiÞT¬7–7Å[3úñ¸Â¼ÆXÀ7ÿ+Ú#¡JöÆÄTŠòØä±Ÿœ†€nú^˜v§$ǃRQy|´,Zœ ýyWJ@Oíew ~Ȧ§aÞ†àÏ2ƒ)sóq‘¿e¨|„ÙBËx¤‰m)È:zÛ6|üãKÿ þÑC$†f(¿«æ8”åçŠ*¡×-%öj¦Ó£µ‘‘b[ ôæþǼ7DXj[ÙNË x+SAžÆ˜ \¥È "~Ø/àéJy°”ÿ1è–²ïµrIzËÆÉ[kÜeæWfÈ~P»ä¥aÅ[¿dh»(%pŠU×Ë%B|ïaI´uG„.°;ÚÁM¬(·HäMˆ¢>›p¶uòÒ¨¥äómI>6"G#ê¬Û>tºP -†nÂ…O?‹¶NùDËrd õŠp)ü±…¥uÀ±šI´ÜæWæ‰G:²É+¨÷ ã7¤„^ó´ØTBüÚ”gÉ2MR/+Ÿi„»È<„+ÃJí«A5œJ]X`HR ¡SfÊ#Ù °°•”þåª"†Ìåp.k ÔÚˆOwûT¢ïá“ùªCò}!SÕ¸ ]?]ðF”„³ž¿Š‚Î| õåϧ+âb¦Í·TÇ]ñ¶Îp¦$Ô½à%d“­4È‚ßáÝe×â®æpbnPâWô÷‚ܨ“D­94~eÜ`íOË®ŸûÓ…AfóÍÔÝ+xX´cðž“! Ùpð¿ƒù«ë£!°¦GÕŠM$4EœJ23R¼#vÉ>4‚›²&‚,×Kï‰Üù»õcÈÑ£Ãã\9‹–"D}Ž©]Ò îäÞh͆¾ÿ'78Ÿ¥`“ÁÞùŒMö4È]—˜Ÿ´~e$½ëiBâ?ÚB¿Ÿ•J}Ìt^³æ#»éÓP0¦š¢YZL2ÚÝŒdåÄêyÀMï’µ1$’®0‚A©“sˆ’ä§QòzKÔ('ùÞÜXû…Ú‹vøºÚÜOêÄÒäúB“MžÊ;ôlÑAêÇ[êqX¨’HK’xpÓ½KèE‹Á~ߵ ¢ðÌÍ9‡Ÿ?ú½¹ôÓAûø=+'öÍÀ¤¦¡,õ`dªûÄ+Ú¼€xvÖºRæj9SOF´¡h f ½@>m+>Î…¢M»ácþ¡Âg3 (¥‹'ÊM¶ígßxáö”pÇ‘;ùK";Èà”¸µ³ÅiE@YšÞIÙËRÒR•Ùe+˜·C§æ· ¤:¼f^öó{űØI¡*rˆ°Î[r"èráÍ߆×ê`šÏ6asqgâí²ŽàˆzÃ,=ô|3ßl n\Þ½$¸Tû)rW%z“O"hÚz o2ŠörÕökv÷…ÐGáú¯OÕ¸ÿUÐWêм_UË}[Ÿûz<¾­/Á|Û õlGêÜÿjº€å£&ƒ†¯ÏbôÑž>QlîVé”;·%ò}ý´#|ŽÕ@ÏÛi jÜÁUo™ºˆ6¯‚(Z»º-0fDFé˜f7šcެfq}A@ójøºŠÇ oþ+èÙÂSü®9øÆ&j9oPš™P™zq x ôQÎ}ˆDîäsŠ3´j%…‚“yFèòŸ7®5+pŽ”2êoàŠ>©rKp5y'n¤|“–ÖÕFgÌÂ;1z]îöƒF~ý­õÂ÷æÆ}J Öò;nÇ7 œ Þï¨ÆÍ{iŠX¬}2'Çqþz€9¾µ_\ ž™ä´ÀÓØÕ,SØmc9/oˆÒ~7B¬På\EZ°b/²r;ž‚–V£rSá”|JXdžLÖgÑ×m ¸?Âm¾y±‡•¢;8õ)(«Dé.ßí4šf[ú²ç³Š´Š|ðºÊx»WÁÈ?[Ïå2g y˜Pw<£È‰kG=AtÁP†š÷¥$™ AO%Ê·{ÆYìPÝcêu³ÄaºXØ5ýKï:¡ÎHH§¨ŒxÍ!2IÉŒëv¶p`ñ¥—-'sr`cŸ«nƒ®žÄ× bŽ|]xbŽ1Smg=uC(qIâ&)ý²yeýËô-ƯgúÒ¡Ó¢F 4= ÚlʰjŽãIq³ÿ¸fr-Jã´…<çš1¡w€GÓð‹ÿLëõÁ@Дj™"#Kц :”·[J«ì„z4ßÔˆ:×kC|PÍ;/•÷\ûÚÉB<ÿ H¾—'«SÙ…»4iéy!Ä‹é‘ è­ÞiÎj~Ás3ÆjÌnÔ‰µ0Z›ÔšÓË ³“ãßkdƒŸu N!˜´U˜lŸoOâa[ìI©¢yþ§×<'ð­¨fŽím ç% b?A JY¦  ÈŽgl/€«ˆ¹B=†~©Iz¦Xu8'hŸ;È& dª›´Kèžb6Kš‚7«a*³zLßG4-÷WñòË[¤à¨åÓ§œÓ {û›t1ƒµ_2¾b¯™ÊŒÖÑ…ÓÚÊÜâN˜çç-l«;x™–þJYÝn˜X"»f4p¹¡Ü7ŽëpàŒ\+`l—›Ï6 ï|¢>ÐÍ‚ÛA§Å—€Ðø@2¾Ã¼û§¯g§ÐáÈÏCmÆýjµ½µKÚåES£Þ3!¾RHáí^¥GÔ¡Öôñœ iHIuHˆÔ%÷ׇßÛÒA9ZwZx „ö ÌÕ‚¥›ÿWé’z?÷iâÀn$Žö0ðãÏckfàK2²­?—ÿàB.‰q ýÏÝL‚§Ä¾H€Åœ«óuƒÏù™dSW¸RíÒüµÂ£a1æòÏLxP¾‰^C“P”§å¡{KƒºzAtóV¤WþŠ-œDZ 1K!`vXK"e[C?øë˜Sk §vÊÀâû<‘@ñ·‹ 5k9UÞïÿÄÕ½Q]Õ‰žèN¹=Ö‚Ï¥@y-Ð ÐÄ·êNð™/¬:ån±½¿Ý3EavàðßÍÌ”gïLºX3ç9,¡òú¬9¨oíô³3ϰ{ dVÍ,ñËîќռ’+OÅ+PñÕÄãxA?ƒAÕ8ù´3JF²…oYcô,lpÉ. yrÿ 4¯î~æ•”t T¦Ô´Ä>yûB"Å •„¥Â”¡ö8––fÚžSDSHi•*3!ͧh¹%Cβ!”“ñ(Ü©AÏÄÚoejoý•9Êeµ_¶bsÔeÚÚf?È_Æ·¦6 %²Ìh„ö ”æG–Ò}ðpÓ8¨¸f¶u½ hù—{ÚÎ'¡/ž`=ÊZìKD-ïÏg_;’ó »-ýW+À±A3ü‹¿LꮹËqì„!à©3ƒA¢”z”ù24‡ªp-Б7M” ô'|0)&õi1ñôœ^ð@7?Äöó÷{ìªßÚ×õ—p?ÂbêDº… â‹6È9NÔ¢ºþ3¹öÂÿEê¹én[iûR*ýdÎÖßµ ápCÓ'YŸ*˜]øÝŠ(ßö\A¾¥š¬€Í^#ÿ¨Ôôq+xècapØRü<š†P´à½'ÙD|rלÎsÓìq›W¬œ]:EY*/º¤…DÐÀÐx\axãñêÕl+y©€§°¾TáU² bƒX¦}0ššÇ;Üèb›ú¶#^^µ†þoT&´fLàÁÆòÇ–·ÞÑ!*ôL㯊X³}etjÈÈÉ^®o¥ÛÇ(‹z×oYìÏq–ˆoFŽ”1Ý0tßlžðh\…@¯7ÜÉQ(d¤ܾìòi’Êû60Ä{zÛ2}>“7ÎkÄ8*SX~°ã®ë㛑q_‚Íüd¥‹&–ylÞ±€L3¡Kì'p2Ìpí!бLQ^1×<«MÉézT@µ[SZ¢/’± ©g?·™ ÂË©{yuì¤:I鶉˜%KQcÏ÷ÃsÐSe…í©1ˆ³' w`i'±µ³Â&ub¬„ì¦®×øaÅc’ºâï¡ÙÂPçØ…,ñ’A¯ä{Ì¡4U `78^Yœ›i/5ßÜ{?ÇAÆ@c;zxùšf“f‘¸a.ñ[µÍBªöâ,¬Ÿ ¾½‹Þ¬y#oÈAK{Èý¢/–•«ÝŸÏ{kKip¸p¾îŸþÞEÈÛŠƪbóòyÄJwÝ5_[ŒÇÐÛ^]HIx„:tÈ |nuû‡Ò阧øC¨åOƒœõ/,­ÕÚøJׯàP|n‘N%Aõòi²“6ÁQ⫎·, ðÈ ¾Ö¢ÖÂQÃ/±šô^HnÌ,`üÌçòÙ­‡[ÈŒ%µ®:À'5W&†6_DémLþ­¼·«»,ãUKjÙ4LÍ-áš;ï…=Tn'f‹¬Œ³72ðºi,a-³È¨éül»ðY膇é`ø¦b-öÙë¾{U˜uÉ=oÕ'ð>ÏírBð;¿œpð×3+µÊ =ŽWã;“#ú/î+E§)¿êÑÂä§ïÂeÌ£¸ÉÞôBÍõƒ™42ge‰CÇ æ)h Ö¾ƒÌ%Ý„Çcî›Å³µuwfµxɯ¿ñF¿»0 ‚t‘aÇ×'wÒy stš¶Ð]êï˜÷%L£W>tÉ™ÀíôKaÍ­ÑcV¼æã_Ôfµó5lã(k*K vì ×hI›6ò Ýr¡‰Z`g SFU‹ §h±ÒW¹…AÝ8×*û,¹ˆâ]”4`ì\Oa"ŠÂKÛ>¿Tà& RIV˜ì†ÄòEuóá`*©(“læ+îIšœÕæ9ãLÅS7R«ñÿlñIÐ :ÛRû’ýÊlfþZƒY1z$ëÆV}þ|ü"²¦Ä¬éd˜‰r•S E{¯ã-«Œy›ñ‰Ô•#¶4^½–j¸É“æû2b\/|÷ º|† ¾{îXKgßô‡rn§JæÕÂ…üy§2>kUq‹Û±1yNÓI¿#>©ž¨jãÅ”¼ºˆˆdKþçÇ/S/5Sv®7ec-ÖÉàDDÀIÙ4¤Fõæ´’x}Ê,8”«‡yœP"¯@ÍAEp5j{Ü÷‹=–ôƒ>M³qä.XCªc^굎¦R™GÏcé,¨c8U@\þ·, R-ʘ*þ¤O–yU˜ùNÕÏRòÑñ“ºí!õ.G…ŽÅº”é1 ÇïŠÆg$¿3èa®üÕl­V.…HN’ÀôÉðMŽƒN”Ãé1ÆÉ²DZÒÎlƒymÖ“MÐüx¾Ý) ïÁ§8,OOg˜êìê@™ïæâ™'©#¤òðŒ}¶p%q9B圡ç.h3*eÈUº-S꼿.É5žŸ?ì<“’å?˜œXØa†‚a$¿ý€éù4~gN„.4!nnZÓy Šç‘ŠÃ.“ôð<µy€ÌjE—$ÑVF eº–zQ;G[ì>êóDM¬-´lÏÒM&Ó­§ áAEH1àsA'·‚òµ,ïCß~*¬çXÔ-×^å’aº*­ >pÛl#:âϵŒaÆlp:×tóåŽö>kEÉŠä®ò^‘¹¹Î¨z<æ‹ÂܤÓ2J<¾º¸*äÄɸøÈ/¶ÑZŠÛËyy¨î_­HQF±ÙPÁ p·˜ø¶bC[4ö-/PHoмÞ}<Ù:OÚ:³J³Ñ€£ðŽ¢]e’`%­Nù/p7ƒE ‘„UÚõgŠ´Àù{–‹›çñ)_÷X•þ<ÑW^ iWöU_`^\Z[irqqpoonm€l€k€jii„hggfgŠfaYXWK-À-M\`_\YX]kpponmlkjjihgfƒed‡c€dc[WXQ:½:S^`]ZXV`lnmlkkjihgg€fee€d‚cbƒa‡`€acc\VXVF%»9W`_\YVV`llkjihgffedd€cbbƒa‚`€_^‡] ^__``a[VWWJ*¹;Y`]ZWUU`jjihgfedc€baa€`_^]^]^€]\€[ZY€ZY€Z€[ ]]^^_ZUVWN-¶>Z_\YVSS^ghosqgbaa`€_^]€\‡[ZZ‚YXXWWVWVWV‚W YZ[[]]XTUWP0 ´>Z^[XURQ[egt€€xc^€]\[ZZYYZZ…Y€XWVVUTTST‚STTUUVWXZ[ZUSSUO1²;Y]ZWTQOXbdl€}g[[ZYX€WXWVVWVUTS€RQPPOPOOPPQRRSUVXYVSPRXW'± 9W\ZVSPMT]bap€~|cXWVV„U€VUUƒTSRQQPPOO€NMLLMMNOOQQSTUUPORXO±9V\YVSPLPZ]_]k}~~}mXUT…STSTSS„R QPONNMMLLKK‚J KLLMNOOPPNPSF±0S\YVSOKLWY\\Z]qyykV€RQR‚Q…R‚QPPOONNMLKKIJIHHGH€G HHIIKKJKNOB±&MZYVROKITWWYXVUVZYSPP„OPPQˆP€O NMLLKJJIHHGFFE€DEDD€EDFIM>±BYYVRNKGNVSUVTTRQP€O NMLMNNMNNOONN‡ONNMMKJJHHGGFEDCC‚A€@>?DH:²9UYVRNJGGSRPSSQPONM€L€KƒLMNM…NMLKJJIHGFFECCBA@€? >=><:;>D7²+LYVRNJFBMROMPONMLLKJI€JIJJ€K€LM€L…MLKJJIHGFDDBBA@?>==<;;977;>3 ²@XWSOKGBDPOKJNMLK€I…HIJJKKL†MLKJIIHFEDCBA@>=<;:987437;0 ³0QXUPMGC?JNLIGKJIH‚GFFGG€HIJJKLLKL…MLLKJHHGEECBA@>=<:98742158, ³BWUQMIC>?LKHFCGHGFF„EFGGHHII€J„KLKKJJI€HFEDCA@?=<;8752/.25+ ´4PURNID?;CJHEBAD€EDBCDDEEFGG€HIIJJKKJKIHGFEDDA@?=<;:741-+/3( ´DTROKEA<9FHEB@>A€C€A@€B CDEEFFGGHIIJJI€J€IHGFEEDB@?>=;9640-*-0& µ,NSPLGA=9;FEB@>;=A@@?@@ABCDEFGHHˆIHHFEEDCA?>=<:740,*,/$ µCSQMHC?:5@EB?><9:?>==€>??@ABC€DEEF€GHHI‚HFFEEDB@?><:740,)+.$ ¶1ORNIEA;64BB?=;977:;;<<€> ?@AABCCDEEFGGHHI‚HFEEDCBA?>=:850,)+-" ·;QPKFB=825B?<;96535€: ;<<=>?@@ABB€DEFGHHGGFEEDDBA@?=;851,(*,!µ+ªÿ!FQMHC>94/7?<:864322689::;<=>??@ABBCDDEEFE…FEEDCBAA?=;851+()+ µ)ÿ-LNJF@;61-9<:864211027889::;<=??@AABBCCˆE€DCA@?>:851+'()µ*çÿ?@AA€B†CBA@??=;840+&&'µ‰ßÿ,JKGB=73.),75410/.-€,-.46889::;<<=>??@@ABA‚B A@@>=;840*€&±™ßÿ€6KIE?:61,&+5410/.-,€+,,-467899:;<<==€>?@AA@@A @@?><:74/)€% .(°ªÿ€>KGB=84.)#+42/.-,…* -267899:;<<€=>>ƒ? @@??>=;962.)€$  -@B*´ DIE@;61+&!*2/-,+*…) *,157889::;<<€=‚>??>=<;:852-($#" *;BII+´$DHC>93.)$ *0.,+*)(('())*+/477889::;;<<…=<;:751,'"!! $7CGHKOSR7®=B=83-($'%%#"!!"!!"##$%€&„'&€'%$"€ $*136;@DFHKPSK!®94/*% €!  !""#‚$*##"!  #'++)-159=ACGKOP9¬8=83-(#!ƒ‚ €!€"-##""!    $'*('*.259>@DHLPI ¬6<72,&"€€ƒ ‚!*    !$'($%(+.27;=AEJMO7¬7;60*&"ƒ‚‚ .   !#&$!"%(,/48<>CGKOE«3:5/*%!‚‚ƒ-  !## "%)-15:;@EIML(«-83.)%  €€€.  !" #&*.279=BGKN; ª*73-($ €€/   !$'+047;@EIMF«&62-'# €€‚‚0  "%)-258>CGKJ+«!31+'" €‚€€0   #'+/47DII.¬P      #',28>DII*­)     €€ €  !&,38>DHJ-­'         %,28>CHI-®7      ƒ  &,27>CHJ.®$     €€ €  %+18>CHI-¯%    €  €  %+18>CHI.¯5    € %+28>CHH*°    €€  €€ %+28>DIE"±    €  &+28?DIF#±  +   &,39?EJ>²   € € €    &,39?EJ7³   € € €  ‚  '-3:@FI1´  € €  ƒ !'.4:@GH+µ   € €  ‚ "(.4· € €  €€ #(/7=CF1¹ € € $)18>DC$º€€€ ƒ $+28?F<½ €  €ƒ  %,39@E2½ €€€ „ !&-4;BB*¼  €€€€ €  €€‚€ "'.5(’ÿ‹  –‚ÿ¨‘ ‘‘˜š›žŸŸœ˜€”ž–𡥩­®¯®­ª§£œ•ŽU™–šŸ¢¨¬¯ ®®­­¬¬©¤Ÿ–Œÿ•ª– £ª¬¬«€ª€©¨§¦¡š‘m“ ‘–žŸ£©©¨¨§§‚¦€¥¤¤££ š”ˆ‚ÿŒ ‘—¡«®¦¤¤‚£¢¢¡¡  Ÿ›šÈþ€ÿ‹™–œ›¤²´¥„ ŸŸžœ›šœ ­Úþþÿÿ‹”›š™ž ¦¥ž€žœ›š™—–••—ž±Øþþÿÿ‹š™–šœ›š››œœœ›™˜–”’‘“œ±Þ€þÿÿŠf•™••€˜—˜˜™™š›€œ ›š˜–”’‘œ³àþÿ‹—•‘•””••––—˜™š›šš™˜—•“‘’·á€þÿÿ‹f’–“‘’“”•–—˜‚™—–”’• ºæ€þÿÿ‹‹•“Œ‘‘’”•–—€˜——–””˜¥Àè€þÿÿ‹¿¿Ž”‰ŒŽŒŠ‹‘’“•‚– •””šªÅêüþþÿ¿™‘’Œ…‹‹‰ˆ‰Œ‘’“‚” “•®ÉìâÇäÿ‘ˆ‚ˆ‰€‡‰‹Ž‘’ “–Ÿ³Ïïå¼£’‡‘…†‡€†‡ˆ‰‹‘“˜¤¸ÓñéÀ£˜”… ŠŠƒ}„…„„…†‡‰“œª¿ØòìÆ£’‘”qކˆ{€ƒ„€…†‰œ­ÃÜôîË¨Ž‡Œ‘”‹Ž„‹‡€z|€‚‚ƒƒ„‡±ÉàõñÓ¯‘‡’m„‰…yx~€€‚…œ²ËãöóÚ¸—„z}ƒ‰’ˆƒˆ„~xuz}~€‚Šš±ÌäöõßÁ¡ˆzwz€†Œ’Œ x„‚}xsuz|}}…•®Êã÷õáȪ}vux}„Š‘fŒ f{wsqvyz|¦Æãöõâʯ”xusw{‚‡Œx~~zvroqvvz…œ¾ßöõâʯ•‚yvsruz€†ŠŠy|zuronqv}“µØóòàÈ­”„zvusruy~‚†‹„mzzuqnmmx­ÓóòÜé“„{wutrrtx|}…‹ƒŽuwuqmlqƒ§ÐòðÖ¼£‚{xvusqrtwuz„‹Žqnpomo}žÊñîѳ›‰zxwvurpqsqqy„Š‚iihht“ÃïìÊ«’ƒ|yxwwvsqppmkpz…Šƒeho‰¹ìëǦzyxwwvtqpojfjpz…‰‹tˆµêêâ‰}yxxwwvtrqngcfjq{‡†f ÿÿãÅèçÀž†{€xwwvsqqldacejr}†ƒŽÿÿþþüû㺚„zxxwwvtsqohbaacekt~…Ž ÿÿþþýÞ²“x€vutsrpkeb€abflu ÿþþüûÙª‹zt€stsrojebaa€`bgnw~z€ÿþúÓ …wrqrrqolhdcbaa`__adipwyŒÿÿþþüË•wl€kjhfdccbbaa`^_`bekrunŠÿþþüÄ‹l`]\]_abccbba`_^^__acgmqoŠ€ÿþþÿù»†la]\\]]^^€_‚^__acejmmU‰€ÿþ½€kc_^]…^€_`behjifŠ€ÿþ¦idc``_^]…^ __`abdgigqŒ„ÿ_`bba__†^_`abd€e–^a``__…^`abccb\™3LVZ]‚^]^__^ZP5ž 1CMRUUTQJ>(’ÿ‹  –‚ÿ¨‘ $HNRUW[YTPHA*žEQX]djp€rpmg`VJ<šKTY^hpssrrqponnmjbXK=–UMX[alpnmlkjihhgg€fe]RC$“$PZZ_jjhfedcbaa``_€^_`]TI3‘ $PYW\nsd_^\\€[ZYXWVX[YSH3MXSUby{`WVUVVUUTSRPONMMORTQO!KVRNWZc`R€PQ POMKIHFEFGHG& ?TRIOQPMLKKLMMN LJGEB@>=;;3LSIFKJI€GHIJKLLMLJGEB>;731APK@ECBCBBCEGHI€J IHEC?;5.+3JMB:A=:=>?ACDFGHIHHFD@<6-(=KF97<637:<>@BDE€FEDA=6-%ŸAH>1461.069;=?A‚C@=6,#¿3CD7+00-++/69;=>?€@?<5* (6C?1%,,)(()-48:;<==<92( 8IA/@9,!((&%&''*/479974.$+>LN<1>5'$$""#%&&'(+-.,& /1“   %57*‘   (3,‘   *,*   "(" € "€‚€ ‘ €„€ ‘ ƒ€ • ‚  —   ›€€¢€€óil32]‰ ‘— £¥¥¡Ÿ›™ÿ™šŸ¤©­¯¯®­ª§ ™‹Œœ ¦««ªª©©¨¨§¦¢›”Uÿˆ“›ž¦«¦¥€¤ £¢¢¡  žšïþÿ†“™›Ÿ«¬ ŸŸ ŸŸžœ›™™š¡ºùþÿ…˜˜™œœ›œœœš˜•““ŸÀóþÿ†“—”–€—˜š€› š˜–’‘ŸÃôþÿÿ…ª”“‘“’“•–˜™š™˜–””¡Èõÿþÿ…“ŽŽ’”–—˜—–”—¨Îøþþÿ†¿’ˆ‹Š‰Œ’””•””™¯ÔÞÙþˆ„ˆˆ‡ˆ‹Ž‘‘’”œ´ÛÞ±™”ˆ‰€„……†‡ˆŠ”¢½àå³–“Žˆ†}€‚‚ƒ„…‡‘¦Äæé»‘‡’‹‡‰„{{€‚„§ÉéíÆ˜€‰‘އ…‚zv|~€Š¥ÉëïÑ¥‚w{„‡xruz|‚œÅêñÔ®Šxtx€ŠŽ‡{}vqpuzºèïÔ¯{urv~†Š‡q{vpnq„¯áíЫ|vtrv{€‰„ˆutpmy¥Ü鯣‰|wurqtu|ˆ„ˆkkjq˜Öä»—‚zxwtqpnp|ˆ‚‰hl‹Ìáµ}yxwuqojgo}ˆˆÿë¯ËݰŠ{xxwurnfcfp†‡ÿþþúÚ¨†ywwusqkcabgr„‡ÿÿþúÒ~uttsplfba`bht~†€ÿúÈwqpolieca`_`ckvy†ÿþþù¼~e`b€c ba`_^_afmss„ÿû°yc]\]^€_€^_adhlm…ÿÿþþïwea_„^__acfhg†‚ÿqca`_ƒ^_abdfcŽPY\\]€^]^\WG‘ 0@HLJB2Š€ÿš‰ ‘— £¥¥¡Ÿ›™ÿ™šŸ¤©­¯¯®­ª§ ™‹Œœ ¦««ªª©©¨¨§¦¢›”Uÿˆ“›ž¦«¦¥€¤ £¢¢¡  žšïþÿ†“™›Ÿ«¬ ŸŸ ŸŸžœ›™™š¡ºùþÿ…˜˜™œœ›œœœš˜•““ŸÀóþÿ†“—”–€—˜š€› š˜–’‘ŸÄ÷þÿÿ…ª”“‘“’“•–˜™š™˜–””¡Èõÿþÿ…“ŽŽ’”–—˜—–”—¨Îøþþÿ†¿’ˆ‹Š‰Œ’””•””™¯ÔßÙþˆ„ˆˆ‡ˆ‹Ž‘‘’”œµÜÞ±™”ˆ‰€„……†‡ˆŠ”¢½áå³–“Žˆ†}€‚‚ƒ„…‡‘¦Äç黑‡’‹‡‰„{{€‚„¨Ééíǘ€‰‘އ…‚zv|~€Š¥ÊëñÒ¥‚w{„‡xruz|‚œÆëñÕ®Šxtx€ŠŽ‡{}vqpuz»èñÕ¯{urv~†Š‡q{vpnq„°âîЫ|vtrv{€‰„ˆutpmy¥ÞéÇ£‰|wurqtu|ˆ„ˆkkjq˜Öå¼—‚zxwtqpnp|ˆ‚‰hl‹Ìá¶}yxwuqojgo}ˆˆÿë¯ÌÞ°Š{xxwurnfcfp†‡ÿþþúÚ¨†ywwusqkcabgr„‡ÿÿþúÓ~uttsplfba`bht~†€ÿúÈwqpolieca`_`ckvy†ÿþþù¼~e`b€c ba`_^_afmss„ÿû±yc]\]^€_€^_adhlm…ÿÿþþïwea_„^__acfhg†‚ÿqca`_ƒ^_abdfcŽPY\\]€^]^\WG‘ 0@HLJB2Š€ÿš‰ HNW\bdc^YQ?3QYckqrrpokf\QELX\emljiggfedd_TH‹NWYfmb`^]]\ZYXXZZSE‰CTSXmmWTTUTSQOMLMPOA‡?PPMSSPLMNOONKHDA?>5 ˆIOFHHGFGIKLLKHC?82'ˆ*JG>@=?@CEGIIHE@8-!ˆDH;8738@AA?8+Š@:(+)((-49;<;4'=I?ˆ*=3!%$#%&(-00+ *@LFˆ89- !"$$! *5CI9‡3(")8FBˆ-%  .?E?‡&"  '9D3‡  "3C<‡    0B?ˆ  /B?ˆ    /A>‰   0A8Š   3??Š  5;Š €  $23‰  &,‰ #ˆƒ ‰  Œ€ Ž  –€¬is32²‚𢍩ª¥¡ƒ›¡€§¦¤ œ©ÿ –™  žž™—¦Ñÿ€ ”“•—™š˜•¥Öþÿ€ ”ŽŒ’•–˜°Óóÿ ††‡Šš»Ê¡’‚ ˆ‚„—Æ×›ˆU „|w}Åá¬|~Œ† {xq|µÜ¯ty…† qp¢Ï¢€vqr€†‚ ƒžÃ•{vqil€ þ÷À‹wsmebm~€ ÿð«wjhea`cnx€ÿþ©l^^€_afmÿ€ ÿÿj][[]\]\Tÿ€6?=* ƒ‚𢍩ª¥¡ƒ›¡€§¦¤ œ©ÿ –™  žž™—¦Ñÿ€ ”“•—™š˜•¥Öþÿ€ ”ŽŒ’•–˜°Óóÿ ††‡Šš»Ë¡’‚ ˆ‚„—ÆØ›ˆU „|w}Åá¬|~Œ† {xq|µÝ¯ty…† qp£Ð¢€vqr€†‚ ƒžÄ•{vqil€ þ÷ÀŒwsmebm~€ ÿð«wjhea`cnx€ÿþ©l^^€_afmÿ€ ÿÿj][[]\]\Tÿ€6?=* ƒ‚[_gkjcZO„ S^feba^[V?‚ NQYXRRPJGD& TICCDHJG>/‚ E<32;AC>+ƒ <-'&,43$@F‚ 4#! 6CU + $=C  8?‚    6?‚   58‚   1‚   (‚ €„ ‰„ich8 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,,,,,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø-WWWWWWW-WW,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,WW-W,W-,W-VW-WWWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,WWW-WW-WW3Q3QW3Q3QWWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWWWW-WWW-WWQWWW3WQWWW3Qÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-WW-WW-WWWW3W3WQWXWWXWWWW,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWWWW-W-WWXWQXWRWXXWXWXWXWX-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWXXWWWWWXWW4WXWXWWXWXWXXWX-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWWXWWXWXWXWXXWXWXWXXWXXXXX-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,WWXXWXWXXXWXWXWXWXWXXXWXXXÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWWXXXWXXXWXWXWXWXWXWXXWXX.ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,XWXXXXWXXXXWXXWXWXXWXXXX4ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWXXXXXXXXXWXWXXWXXXWXXXX.ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿW‚X‚X‚X‚XXXXXXXWXWXXXXX.ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,XXXXXXX‚XXXXWXWXXXXXXX.--ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWXX‚‚X‚XX‚X‚XXXXXXWXX...WÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWX‚XXXƒXY‚XXXX‚XWXXX....XXWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWXY‚ƒXX‚X‚Y‚XX‚XXXX/..XXWX,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXX‚‚YXƒXƒXY‚XƒXYXY/./XXXXWWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXXY‚ƒXƒXƒX‚YX‚XY.//.YX‚‚X‚X,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX‚‚ƒXƒX‚Y‚YX‚YY.///RYXƒXX‚XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXX‚YƒƒXƒXƒ^}XXY//./X^}‚_|X]XÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWXƒY‚ƒXƒXƒX}XY///XY_}^ƒ|^X|,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWX‚ƒYƒƒƒXƒ^YX//.YXY‚ƒƒXƒ|^XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXƒYƒ‚ƒXƒƒXY////XYXƒƒ}^}‚X^XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX‚ƒYƒƒƒXƒY/.//XY}^}^ƒƒ_ƒ‚|XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿW‚Yƒ‚ƒƒY}5.//XYXƒ^}ƒƒ}^}X‚XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚XƒƒƒƒYY/(/.YY‚Yƒ_}^ƒƒƒ‚}^‚WÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWƒƒƒYƒY./5XY|_}‚ƒ‚ƒƒ}^ƒ^‚XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX‚ƒ‚Y/..YXY‚Y‚_}_}_ƒƒƒ}ƒ‚XWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚ƒY./YY|ƒƒ^ƒ}_‚ƒƒƒ‚ƒ‚‚XXWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,YX./XY^}^}ƒ^}ƒ}_}ƒƒƒƒƒ‚Xøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-.YXƒ‚ƒ_}ƒXƒƒ_‚ƒƒƒƒ‚^|Xÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'5Y|_}_|ƒ^ƒƒ}ƒƒƒƒƒƒƒƒ‚‚Xÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ..Yƒ|_ƒ|ƒ_ƒ}ƒƒ‚ƒƒƒ‚ƒ‚ƒ|^,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX_Yƒƒ‚_ƒ|_ƒ‚ƒƒƒƒƒƒƒƒƒ‚‚Xÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-YYƒ}‚ƒYƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚X3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXƒƒƒƒƒ‚ƒƒ‚ƒƒ‚ƒƒƒƒƒ‰ƒƒƒƒƒWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-Xƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ§‚ƒ‚‚Xÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-Xƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ§‰ƒƒƒƒƒ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXƒƒƒƒƒ‰§ƒƒƒƒƒƒ§‰ƒƒ‰ƒ‚ƒƒ‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿV‚ƒƒƒƒƒƒƒ‰ƒ§‰ƒƒƒ§ƒƒƒƒƒƒ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXƒ‚­ƒƒ‰§ƒƒƒƒƒƒƒƒ‚ƒƒ‚WÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿW‚ƒƒƒƒƒ‰ƒƒƒ§‰ƒƒƒ‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø‚‚ƒƒ§ƒƒ­ƒƒ‚ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùWú‚X‚‚WVÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿicl8ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,,ø-,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø-WW-VW-WW,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWW-WW-WW-WW-WWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,WW-WWW-WWWWWWWWWÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,WW-WWWWXWXWXXXWXWÿÿÿÿÿÿÿÿÿÿÿÿÿXWXWWXXWXWXWXWXXX-ÿÿÿÿÿÿÿÿÿÿÿÿÿWXXWXXXWWXWXWXXXX-ÿÿÿÿÿÿÿÿÿÿÿÿÿ,WXXXWXXXWXWXXWXX-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿWXX‚XXXXWXXWXXXXÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿXXXX‚XXXWXXXWXR 'ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,X‚X‚XX‚XXXXXX.. Q,ÿÿÿÿÿÿÿÿÿÿÿÿÿVXƒX‚YX‚X‚XXX4(4XWÿÿÿÿÿÿÿÿÿÿÿÿÿWX‚XƒX‚YXYXX/(XXXXWÿÿÿÿÿÿÿÿÿÿÿÿ-‚Y‚Y‚Y‚‚YX///Y‚‚XXÿÿÿÿÿÿÿÿÿÿÿÿWXƒƒXƒXƒXY//XY‚XXXøÿÿÿÿÿÿÿÿÿÿÿÿ‚XƒƒY‚YX/..YXƒƒƒ‚XWÿÿÿÿÿÿÿÿÿÿÿÿXƒƒƒXƒY/./.Y_}‚Y^XWÿÿÿÿÿÿÿÿÿÿÿÿWXƒƒƒYX/YX}^ƒƒ‚}‚Wÿÿÿÿÿÿÿÿÿÿÿÿÿ‚ƒƒYY./YXƒƒƒƒ}^}^WÿÿÿÿÿÿÿÿÿÿÿÿÿWƒ‚Y/YXY‚Y‚Yƒƒ‚ƒ‚WÿÿÿÿÿÿÿÿÿÿÿÿÿÿXY.RY‚Yƒƒ_ƒƒƒƒ^|3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ. Y^Yƒ‚Y‚}‚ƒƒƒƒ‚,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.RYƒ}‚_}ƒƒƒƒƒ‚|Xÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ YXƒ^}ƒƒ‚ƒƒƒƒƒƒ‚Wÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-Yƒƒƒƒƒƒ‚ƒƒƒƒƒƒ‚^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ-Xƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚Wÿÿÿÿÿÿÿÿÿÿÿÿÿ-‚ƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒWÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,ƒƒƒƒƒƒƒƒƒƒƒ‰ƒƒƒWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚ƒƒ­ƒƒ­ƒƒ­‚ƒ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ƒƒƒƒƒƒ‚WÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWùWúWÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿics8ÿÿÿÿÿÿÿ,,ø,ÿÿÿÿÿÿÿÿÿÿ-WWWWWW,ÿÿÿÿÿÿÿWWWXWXXX-ÿÿÿÿÿÿ,XXXWXWXXÿÿÿÿÿÿÿWXXXWXX.ÿÿÿÿÿÿÿ‚X‚XXX..WÿÿÿÿÿÿÿXƒXYX/.XX,ÿÿÿÿÿÿXƒ‚ƒ.YY‚WÿÿÿÿÿÿXƒY/./ƒ‚‚Xÿÿÿÿÿÿ-‚Y.Y|_}‚XÿÿÿÿÿÿÿW/XYƒ_ƒƒWÿÿÿÿÿÿÿ-Yƒ‚ƒƒƒ‚,ÿÿÿÿÿÿ-ƒƒƒƒƒƒƒWÿÿÿÿÿÿÿ^ƒƒƒƒƒƒ^ÿÿÿÿÿÿÿÿÿXƒƒ‚‚Wÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿicl4ïïïïïïïüÌÜïïïïïïïïïïïïËËÑËË¿ïïïïïïïïïýÜËË¿ïïïïïïïüÝÑݽ½ïïïïïïïËÝËÑÝÑÛ¿ïïïïïïÑ»½½½»Ñïïïïïïý»ÛÛÑÝÑ‹ïïïïïïËѸÛÛÛïïïïïïïÛ½½ØÑ±Ïïïïïïïï»»»½Øïïïïïïü»»»Û»±¼ïïïïïïü¸»¸¸»Ñ½ïïïïïïý»»»»»ïïïïïñ‹¸»‹¸±»»Ïïïïïïü»»»»‹»¼ïïïïïﻸ»‹±¸¸½ïïïïï︻»±‹»»±ïïïïïﻋ¸±¸»‹»ïïïïïïûº±‹»«»»ïïïïïïû¸±±»¸»Š¸»ïïïïïïï»»‹»»º»»ïïïïïïïû»¸º‹¸¨¼ïïïïïïïñ¸¸»»º««¯ïïïïïïï»»«Š¸«‹¯ïïïïïïñ»‹º‹«¨«»ïïïïïïïË«¨««ŠºŠ½ïïïïïïü¸ºŠ»›««»¯ïïïïïïûºººººŠ¹½ïïïïïïïïûŠŠŠŠº¸ïïïïïïïïïïûººº‹¯ïïïïïïïïïïïý½½ïïïïïïïïïïïïïïïïïïïïïïïïics4ˆïïïüÍ¿ïïïïüÑÑÛ¿ïïïÛÛÛÛïïü±Ñ½ïïïû»ÛÛïïïû»»ïïïû¸±±»¿ïïû»»‹Ïïïû‹«¯ïïû¸»»¯ïïï»‹Š¯ïïﻺ»¿ïïü¸¨ª‹ïïïûº»«¯ïïïﺊ»ïïïïïïïïïïïich#HÿÿÿÿÿÿÿòéÿÿÿÒŠ¿ÿÿÿT"ÿÿþ¢”§ÿÿþ”¡Rÿÿú"J”ÿÿùU$Uÿÿê•IIÿÿÒ¢%KÿÿÔJª‡ÿÿ•( Oÿÿ©Eÿÿª¨¥?ÿÿRŠ’ÿÿJQ(?ÿÿTÖ _ÿÿ)R@Gÿÿª”‚Ÿÿÿª¦"ËÿÿURSÿÿT™—ÿÿU ÕÿÿSXUÿÿ¬Ð*©ÿÿÕAJ«ÿÿ«ª«ÿÿê *ÕÿÿÕ­Qÿÿö…U[ÿÿôkSÿÿò*ªÙÿÿø*­«ÿÿùUj×ÿÿðÕV³ÿÿæmÛWÿÿæÕUgÿÿÕUm¯ÿÿ­¶Û_ÿÿ5[Uÿÿ×j¶ÿÿÿ5]µÿÿÿíÕ­ÿÿÿæ¶·ÿÿÿým¿ÿÿÿÿUÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿ€?ÿàÿÿøÿÿüÿÿÿÿÿÿÿÿþÿÿþ?ÿÿü?ÿÿøÿÿðÿÿàÿÿÀÿÿÿàÿÿÿàÿÿÿðÿÿÿøÿÿÿøÿÿÿüÿÿÿüÿÿÿüÿÿÿþÿÿÿþÿÿþÿÿþÿÿþ?ÿÿþ?ÿÿþÿÿþÿÿþÿÿþÿÿüÿÿüÿÿüÿÿøÿÿø?ÿÿðÿÿàÿÿÿÀÿÿÿ€ÿÿÿ?ÿþÿøÿàÿ€ICN#ÿþ/ÿÿò¥ÿÿêIÿ•*¿ÿRTŸÿ¤ª¿þ•Pü(ÿþ©ÿý5QÿúªBÿýE"ÿùU ÿú² ÿûT?ýH'ÿPý!Z¿þÑj?þ¢«¿ÿª¿ÿ’­¿ÿÚµÿ Ûþúªÿü–Ûÿûuµÿú­[ÿÿmßÿÿ•_ÿÿõÿÿÿÿÿÿðþÿ€ÿÀÿÿàÿÿÀÿÿ€ÿÿÿþÿþÿÿÿÿÿÿ€ÿÿ€ÿÿÀÿÿÀÿÿÀÿÿÀÿÿÀÿÿÀÿÿÀÿÀÿ€ÿÿ€ÿÿÿÿÿþÿüÿðàics#Hþú×õWå/ìõè'ç/øWè¯òoóWíoë_ú¿ÿÿàøøðððøøøøøøðàÀt8mk@ %,BSTNGA7'  7Qr¢¼ÍÕÚèñóñîçâÖÆ¹ŸyQ9  )j©ÈäðùþÿÿÿÿÿÿÿÿÿÿÿÿÿÿþùïãÅ•`72l¦ÓôþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýóÞ­j3 a¨ÜöþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþôÛO[Àóþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüè¸h ^µìþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþñ½cO°ðþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþð³P3˜èþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýâ’3fÑûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùÊ_-žïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþç‘(NÆúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷¶> fÝþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÍY wçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÞk ìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþåx  ~íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿé|  wìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáZ)++'!  oèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú±D673+" aäÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúµPAA=5) EÙÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿû¸XKKF=1$(Áýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿû¼^RSND7) ’øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿû¿fYZUJ=- ]ëÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÁk_`[PA2# )ÇþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÅpee_TF6& ŠùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÇuijdYJ9) DáÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÇxnni]M;, ¡üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÉ|rqk_O<, NéÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÊuum`Q>- ¨ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÌ‚xwoaP>- QçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüφzwpaO=, ¶ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüЉ}ypaO<* RîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÐŒ€|qaN:)  —ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÒŽƒ~r`L8&&ÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÓ“†s`K6%  ZòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÖ—ŠƒuaK6#   ˜ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýØ›Ž‡xdL6#  ËÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÚŸ“‹|fO7#  [ñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÜ¥™‘‹yR:% ‘ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÝ©–—ϸS( ¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÞ­£Öýõ* 'Ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý಩£¤Úýÿÿíz?éÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý⶯©ªÞýÿÿÿÿßU^õÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý㺳¯¯àþÿÿÿÿÿýÃ1‘ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþä½·³µâþÿÿÿÿÿÿÿù ªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþåÁ»¹ºäþÿÿÿÿÿÿÿÿÿìd¼ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþçÄÀ½¿æþÿÿÿÿÿÿÿÿÿÿþÈ1ÆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþèÈÄÂÃèþÿÿÿÿÿÿÿÿÿÿÿÿù’!ÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþêËÈÆÈêþÿÿÿÿÿÿÿÿÿÿÿÿÿÿßE2ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþìÏËÊÍìþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿû¡6æÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþíÒÐÎÐíþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèS2ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþîÕÓÑÓïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý¤-ßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþðÚÖÕÖðþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâF-ßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþñÜÚØÙñþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿû˜ 1ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþòÞÜÛÜòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØ1&ØÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþòàÞÝÞòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôjÅÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþóáßßàóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ°²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþóâáàáôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿá< §þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþóâáâãõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöj‰üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþôãááãõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý¤aõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþôãáâäõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔ)<èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþôãââäõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïO(Ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþóãââäõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷n³þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþóâââäõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý™ úÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþòáâãäõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¿KïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþòááâåöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÖ(&Õÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþòààáãöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß2 œýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþñÞÞßâõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìITñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþðÜÝÞáôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ`#ÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþïÙÚÜàôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùvûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþîרÚÞôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùt=äÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþíÕÕ×ÝôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúwªþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþíÓÓÖÚóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿû€TíÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþíÒÑÓØòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü†­ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþíÑÏÑÕñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûƒNëÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþìÏÍÏÓïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøk¤üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþìÍÌÍÒïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöb<ÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþëÌÊËÐïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôY‚øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþêÉÈÉÎîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿíJ"ÀþÿÿÿÿÿÿÿÿÿÿÿÿÿþéÈÆÇÌîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿå9JäÿÿÿÿÿÿÿÿÿÿÿÿþèÄÃÅËíþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿɃ÷ÿÿÿÿÿÿÿÿÿÿþæÁÀÂÉìþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþª ªûÿÿÿÿÿÿÿÿþå¾½¿ÇìþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýŒ-Çþÿÿÿÿÿÿþ㻺½Åëþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷iGÚþÿÿÿÿýá¶¶ºÃëþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿëEbåÿÿÿýß³³·ÀêþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏ% $íÿýÞ¯¯³¾êþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü   0KìÜ­«¯»éþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïS  1E]y¨¨¨¬¸èþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒ& !2F]tŠš¥©µçþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýž  "3F]s‡˜¡¥²æþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôa #4H]s†•Ÿ¢®åþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔ)$5H^r„“›žªãþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü• $4H]qƒ˜™¦âþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß@ #3F[pŽ••£áþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù‡  "2EYn‹‘žàþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÙ1 !1CWj{‡Œ‹™ÞþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûŒ   /@Thxƒˆ‡•Üþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×2 ->Qct~ƒ‚‘Ûþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷ +>>>==<<;::988754310.,*'$   #'+-024578::;<=>>?@@@ABDHOZeaflpeZUPMHDDDDDDDDDDDCCCCCBBBBAA@@??>==<;:986531/,)%! $(+.024679:;<==>??@@AABBBBBCCDDCDDDDDDDEEDDDDDDDDCDCCCBBBBAA@@@??>=<<;:87532/,)&!   $(*-/02457789:;;<==>>>????@@@@@@AAAAAAAAAAAA@@@@@@@???>>>==<<;::98765320.+)&"  !#&'*+-.011344566778889999:::::::::::::::::99998887765443210/-,*)&%"   !#$%')*+,,-/001122222222222222222110/.-,++)(&%$"    h8mk #Kr•¦³«™|T+ [¥Ûöýÿÿÿÿÿþøä¶j"^Â÷ÿÿÿÿÿÿÿÿÿÿÿÿÿúÕz8°õÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÆO_ÞþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêzlìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõfíÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕL,JæÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàwE) Èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä„V6…ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæŽb?/àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿç•hCýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéšjA!Øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿë m>V÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿíªu?˜ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð¶³’ÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòÁÁôíY ÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóÊËöÿÿÓ*.éÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõÒÔ÷ÿÿÿý˜ 9ðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷ÚÜùÿÿÿÿÿê@8ïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùáâúÿÿÿÿÿÿþ2ëÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùæçûÿÿÿÿÿÿÿÿà)#ßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúèêûÿÿÿÿÿÿÿÿÿúdÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿúèëüÿÿÿÿÿÿÿÿÿÿÿ¢ ÿÿÿÿÿÿÿÿÿÿÿÿÿùèëüÿÿÿÿÿÿÿÿÿÿÿÿÊeûÿÿÿÿÿÿÿÿÿÿÿùæëüÿÿÿÿÿÿÿÿÿÿÿÿÿà$*áÿÿÿÿÿÿÿÿÿÿøãéüÿÿÿÿÿÿÿÿÿÿÿÿÿÿì4 þÿÿÿÿÿÿÿÿ÷Þåûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð:CìÿÿÿÿÿÿÿöÚáûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð; þÿÿÿÿÿõÖÝúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿë1.ÙÿÿÿÿôÒÚúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß#_ðÿÿòÌÖùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅ ˆ÷ðÅÑùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@²½ËøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøY6k˜Å÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝ$7i¾öÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ˜5eˆ·õÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä40\~¯ôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý+Tr§òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐ%)KfžñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿëR *DW”ðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòr &5:^çÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ{  …ïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿäj WÐüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø¼A#…ÞüÿÿÿÿÿÿÿÿÿÿÿÿÿúÎj 2ÊîüÿÿÿÿÿÿÿûéÀ|4 !(-1Ba…§½ÊÎǼ ‚_E<;:98641-)" )/379;=?BGHGDBAAA@@?>=;:740*!  "$&'(((((('&$#  l8mk*c•³Á¼§}E8âûÿÿÿÿÿþòÆjåþÿÿÿÿÿÿÿÿÿÿø»<¤ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝT £ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù˜1|ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú´U#4ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú¼e. ™þÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÁk0 )àÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÈo, ^úÿÿÿÿÿÿÿÿÿÿÿÿÿüÔªDÿÿÿÿÿÿÿÿÿÿÿÿÿýÝÍóœ ®ÿÿÿÿÿÿÿÿÿÿÿÿý䨸ÿó[ ºÿÿÿÿÿÿÿÿÿÿÿþëâúÿÿÿŶÿÿÿÿÿÿÿÿÿÿþðêüÿÿÿÿöT¡ÿÿÿÿÿÿÿÿÿþñíüÿÿÿÿÿÿvþÿÿÿÿÿÿÿþñîýÿÿÿÿÿÿÿÌ@ðÿÿÿÿÿÿþïíýÿÿÿÿÿÿÿÿã&Âÿÿÿÿÿýëéüÿÿÿÿÿÿÿÿÿë0d÷ÿÿÿýèäüÿÿÿÿÿÿÿÿÿÿë0·þÿýäàûÿÿÿÿÿÿÿÿÿÿÿâ%8ÜüßÛúÿÿÿÿÿÿÿÿÿÿÿÿÊ tÎÔúÿÿÿÿÿÿÿÿÿÿÿÿÿ˜ 9ÆùÿÿÿÿÿÿÿÿÿÿÿÿÿöQ 7w¼÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ 0j±öÿÿÿÿÿÿÿÿÿÿÿÿÿÿòV *[¦õÿÿÿÿÿÿÿÿÿÿÿÿÿÿü˜ (F•óÿÿÿÿÿÿÿÿÿÿÿÿÿÿý²!!‚ñÿÿÿÿÿÿÿÿÿÿÿÿÿø¨  ZÎûÿÿÿÿÿÿÿÿÿýÜu#xÊïüÿÿÿýôÖ”9  (0Bc‚š¤žŠnO>;974/)"&)+-....-,*'#s8mkn³ÎʦW@Èûÿÿÿÿö©$1ÖÿÿÿÿÿÿÿÞO £þÿÿÿÿÿÿår0çÿÿÿÿÿÿï‘UúÿÿÿÿÿõäÏ(aýÿÿÿÿùðüý‡M÷ÿÿÿúôýÿÿË#Üÿÿøóýÿÿÿã$‡ûõíýÿÿÿÿâ$!¾æüÿÿÿÿÿÊA½úÿÿÿÿÿý…4«øÿÿÿÿÿÿÒ&]éÿÿÿÿÿþØF ]Åïù÷æ¦9 2ThdN4*$Slic3r-1.2.9/var/Slic3r.ico000066400000000000000000012615361254023100400153120ustar00rootroot00000000000000 ( V€€ (~ 00 ¨%¦(  ¨NN hö^( ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  !"##%&&&'''((((((((((((((((((((((((((((((((((((('&&&%$#"!  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  !!"#$%&'((()))*+,,-..///00000000000000000000000000000000000000//..-,,+**))(((''%$#"!  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  !"##$%%&'())**+,,--...////0111223333333444444444444444444444444444444433333332211100///...--,+**)))('&&%$##"!  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ !"#$%&'()*+,,--../001111233344444555666777777777888888888888888888888888888888888777777777666555444443322110000/.---,+**)('&%$#"!  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ !"$%&()*+,-../00122334445667777888899999:::::;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;:::::99999888777666655444322100//.-,+*)('&$"! ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ !#%&')*+,-./01233455667788999::;;;;;<<<<<====>>>>>>>?????????????@@@@@@@@@@@@@@@@@@@@@@@?????????????>>>>>>>====<<<<;;;;;;::99888776655443210/..,+*)'&$"! ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  "%'(*,-./012344567788899:::;<<<====>>>>????@@@@@@AAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCBBBBBBBBBBBBBBBBBAAAAAAAAA@@@@@@????>>>>====<<;;;::999887765443210/.-+*(&%"  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  "$&)*,-/0123456678999::;;<<===>>>????@@@@AAAAAABBBBBBBCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCBBBBBBBAAAAAA@@@@????>>>>==<<;;:::998766543210.-,*('$" ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ "$&(*+-.01234567789:::;;<<==>>>???@@@@AAAABBBBBBCCCCCCCDDDDDDDDDDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDCCCCCCCBBBBBBAAAA@@@@????>>==<<;;;::98776543210.-+*(&$!  !#&(*+-./12344567899::;;<<===>>???@@@@AAAABBBBBBCCCCCCDFFEEFGGHHHGFEEEDDDEEEEEEEEEEEEEEEEEDDDEEEDDDDDDDDDDDDCCCCCCCBBBBBBAAAA@@@@???>>===<<;;::99876654321/.,+)(&#!  !$&()+,-/012345667899:::;;<<<==>>>????@@@@AAAABBCF JN%%Y++d77s99x//f33l44p::{;;|??…AA…BB‹==}00i&&]!!Z WOQ L JHFDDDDCCCDDDDDDDCCCCCCCCCCCCCCBBBBBBBBBAAAAAAA@@@@????>>====<<;;:::988776443210/-,*)'&#!  !#%'(*+-./0123345667778999:::;;<<<====>?BIS//f99s77r@@ƒII˜PP®RR¼XXÏ[[Ú\\å]]è[[Þ[[á[[ä\\ê\\ê[[ï]]î]]ð]]ê]]ÜZZÓZZÎWWÊSS½UU¼PP²OO¦GG™BB‹66qT JFDBBBBAAAAAAAAAAAAAAAAAAA@@@@@@@??????>>>>>===<<<<;;:::9998777655433210/.-+*(&%#!   "$%'()*+,-./012334455567778889: ?GM--_==xJJ”QQ²WWÊZZß\\ê[[é\\ò]]ø]]ü]]ý^^þ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]þ]]þ]]ý]]ý]]ü\\ú\\ø[[ôZZçRRÅPP®JJš@@†66q**`SHC???????>>>>>>>>>>>======<<<<<<;;;:::::99998777666554332210/.--,*)(&%#"    "#%&'()*+,--./00012236>--T>>rMM˜TT·UUÄXXÚ[[í\\÷^^ü^^þ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]þ]]ü[[øZZóZZéYYÙTTÄPP¬HH‘;;s((YD<;;;:::::::::::9999998888877666655544443221110//.--,+*)('&$#"!   !#$$%&'((+1<66THH{SS¨YYÑ]]ë]]ø]]ý^^þ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ]]þ]]û\\ö[[êWWÓMM¢::i!!L =7655555554444443332222211000//...---,+*)))('&%$#""   $228IIdTT—XX¶ZZ×]]ð]]û^^ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ]]ùZZãUUÂMMšCCr//P:1.....-----,,+****))))(''&%%$$###"!   66!LLIWWƒ[[½\\ç^^ù^^ý^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿ bbÿaaÿ^^þ]]ø\\êYYÎTT£HHt99K1'$$$###""!!  $$CCSS:XXh[[¡]]Ú^^ö^^þ__ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿ bbÿ bbÿ bbÿ bbÿ``ÿ^^ú]]íYYÏWWžLLg==:"  ??\\]]G]]^^Ó^^ñ__û``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿ bbÿ bbÿ bbÿ ccÿ ccÿ ccÿ ccÿ bbÿ__ù^^ç]]¿WW†OOJ77  ??cc]]R^^¢^^ß__ùaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿ bbÿ bbÿ bbÿ ccÿ ccÿ ddÿ eeÿeeÿ ddÿ bbþ__õ^^Ù[[žVVPHHHHbb'^^a^^§]]ä__üaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿ bbÿ bbÿ bbÿ ccÿ ddÿ ddÿ ddÿeeÿffÿeeÿ ddÿ aaù]]ÝZZˆZZ0UU ??\\!]]j\\À^^î``übbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿ``ÿaaÿaaÿaaÿbbÿ bbÿ bbÿ bbÿ ccÿ ddÿ ddÿ eeÿeeÿeeÿffÿffÿddÿ ``ô^^Ã]]x[[2UU UU__\\X]]´__î``þbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿbbÿ aaÿ bbÿ ccÿ ccÿ ddÿ ddÿ ddÿeeÿeeÿffÿffÿffÿccý aað^^Ç]]}[[2ff aa]]O]]£^^å``ýccÿccÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿ bbÿ ccÿ ccÿ ddÿ ddÿ eeÿeeÿeeÿffÿffÿffÿggÿccþ ``ñ]]Ä__s[[*__ff ZZ;\\š^^ã``û ccÿ ccÿ ccÿbbÿbbÿaaÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿbbÿ bbÿ ccÿ ccÿ ccÿ ccÿ ddÿddÿeeÿeeÿffÿffÿggÿggÿggÿccý ``í__¹]]eZZÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUZZ"\\v^^Ð``ù ccÿ ddÿ ddÿ ccÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿbbÿbbÿ bbÿ ccÿ ccÿ ccÿ ddÿ ddÿddÿeeÿeeÿffÿffÿggÿggÿhhÿggÿeeü aaç__©__PTTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿbb ZZI\\°^^ï bbþ eeÿ ddÿ ccÿ ccÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿbbÿ bbÿ bbÿ ccÿ ccÿ ccÿ ddÿ eeÿeeÿeeÿffÿffÿggÿhhÿhhÿiiÿggÿddú ``Û__‹__3TT ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^^+\\^^Øaaû ddÿ ddÿ ddÿ ddÿ ccÿ ccÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿ bbÿ ccÿ ccÿ ccÿ ddÿ ddÿ eeÿeeÿeeÿffÿffÿggÿhhÿhhÿiiÿiiÿggþ ccó ``Á^^d[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿbb \\M\\¶__ò ccþ ddÿ ddÿ ddÿ ddÿ ddÿ ccÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿbbÿbbÿ bbÿ ccÿ ccÿ ddÿ ddÿ ddÿeeÿeeÿffÿffÿggÿhhÿhhÿhhÿiiÿiiÿffý aaã__–]]9TT ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿWW]]x^^× aaú ddÿ eeÿ ddÿ ddÿ ccÿ ccÿ ccÿ bbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿbbÿ bbÿ ccÿ ccÿ ddÿ ddÿ eeÿeeÿeeÿffÿffÿggÿggÿhhÿiiÿiiÿjjÿiiþddô __Â\\a\\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿþþ þþ ÿÿ ÿÿ ÿÿ ÿÿ ÿÿ ÿÿ ÿÿ þþ þþ ÿÿÿÿþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmmYY6]]Ÿ__ë ccþeeÿ eeÿ ddÿ ddÿ ddÿ ccÿ ccÿ ccÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿbbÿbbÿ ccÿ ccÿ ccÿ ddÿ ddÿ ddÿeeÿeeÿffÿggÿggÿhhÿhhÿiiÿjjÿkkÿjjÿggü bbÞ^^Œ^^.UUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿþþ ÿÿ þþ ÿÿ ÿÿ þþ þþþþþþÿÿÿÿÿÿþþþþþþ þþ ÿÿ ÿÿ ÿÿ þþ ÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿff\\[\\à aaöeeÿeeÿeeÿ eeÿ ddÿ ddÿ ccÿ bbÿ bbÿ bbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿ bbÿ ccÿ ccÿ ccÿ ddÿ eeÿeeÿeeÿffÿffÿggÿggÿhhÿiiÿjjÿjjÿjjÿiiþddñ aa°__H\\ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿþþ þþ ÿÿ þþ þþÿÿÿÿþþþþþþÿÿÿÿþþÿÿþþþþþþÿÿÿÿÿÿþþ ÿÿ þþ þþ ÿÿþþÿÿÿÿÿÿÿÿÿÿ]]]]{^^Ý ddüffÿffÿeeÿ eeÿ eeÿ ddÿ ddÿ ccÿ bbÿ bbÿaaÿaaÿ``ÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿccÿ bbÿ ccÿ ccÿ ddÿ ddÿeeÿeeÿeeÿeeÿggÿggÿhhÿiiÿiiÿjjÿjjÿkkÿkkÿggø aaÊ\\cYYÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿþþ þþ ÿÿ ÿÿÿÿÿÿþþÿÿþþþþÿÿþþþþþþþþþþþþþþÿÿþþþþÿÿþþÿÿÿÿþþ þþ þþ þþÿÿÿÿ¶¶cc.\\›__ìeeýggÿggÿffÿffÿ eeÿ eeÿ ddÿ ddÿ ccÿ bbÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿbbÿ bbÿ ccÿ ccÿ ddÿ ddÿ ddÿeeÿeeÿggÿggÿggÿhhÿhhÿiiÿjjÿkkÿllÿllÿiiü ccÜ``ZZÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿ þþÿÿþþÿÿþþþþþþþþþþÿÿÿÿ þþ!þþ!þþ!þþ!þþ!ÿÿ ÿÿþþþþþþþþþþþþÿÿþþÿÿ ÿÿ ¶¶iiA^^² ``õggþggÿggÿggÿeeÿ ffÿ eeÿ eeÿ ddÿ ccÿ ccÿ bbÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿbbÿbbÿbbÿbbÿbbÿ bbÿ bbÿ ccÿ ddÿ ddÿ eeÿeeÿffÿffÿggÿggÿhhÿiiÿiiÿjjÿkkÿkkÿllÿjjþddê^^—^^.??ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿ ÿÿ þþÿÿþþþþÿÿþþþþþþþþ!þþ#þþ%þþ&þþ'ÿÿ(ÿÿ(ÿÿ(ÿÿ(þþ&þþ%þþ$þþ!þþþþþþþþÿÿþþÐÐuuPddÀ eeøiiÿiiÿhhÿggÿffÿ eeÿ eeÿ eeÿ ddÿ ddÿ ddÿ ccÿbbÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿbbÿbbÿ ccÿ ccÿ ddÿ ddÿ eeÿeeÿeeÿffÿggÿhhÿhhÿiiÿjjÿjjÿkkÿllÿmmÿkkÿffñ``¬ZZ;ffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿ þþÿÿþþþþÿÿþþÿÿþþ!þþ#þþ&þþ)þþ+þþ,þþ-þþ/þþ/þþ/þþ/þþ.þþ-þþ+ÿÿ(þþ%þþ#ÿÿ þþÒÒ"ƒƒYrrÁss÷rrÿmmÿjjÿiiÿggÿ ffÿ eeÿ eeÿ ddÿ ccÿ ccÿ bbÿ ccÿbbÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿbbÿ bbÿ ccÿ ccÿ ddÿ ddÿ eeÿeeÿffÿffÿffÿggÿhhÿiiÿjjÿkkÿllÿllÿmmÿllÿggö__º[[FHHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿ ÿÿ þþÿÿþþþþÿÿþþÿÿþþ!þþ$þþ'þþ+þþ.ÿÿ0þþ1þþ4þþ5þþ6þþ6þþ6þþ6þþ4þþ2þþ.þþ,þþ)øø(›˜\ƒƒ¾)‡‡õ%ÿyxÿrqÿmmÿjjÿ hhÿ ggÿ ffÿ eeÿ ddÿ ccÿ bbÿ bbÿbbÿbbÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿ bbÿ ccÿ ccÿ ddÿ ddÿ eeÿffÿffÿffÿffÿggÿhhÿiiÿjjÿkkÿllÿllÿmmÿmmÿhhù ``Ã\\M__ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ þþ þþ ÿÿþþÿÿþþþþþþþþ!þþ$þþ&þþ*þþ.þþ2ÿÿ3þþ6þþ9þþ;ÿÿ<þþ=þþ=ÿÿ<þþ;þþ8þþ6ÿÿ3ùù1´´`-––Ù3’’þ,‹‹ÿ"ÿyyÿssÿooÿ kkÿ iiÿ ggÿ ffÿ ddÿ ccÿ bbÿbbÿbbÿbbÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ__ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿ bbÿ ccÿ ccÿ ccÿ ddÿeeÿffÿffÿffÿggÿhhÿhhÿiiÿjjÿkkÿllÿllÿmmÿnnÿjjú aaÉZZRTT ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ ÿÿ þþÿÿþþþþÿÿþþþþÿÿ"þþ&þþ*þþ.þþ1þþ4þþ8þþ;þþ>ÿÿ@þþBþþCþþCþþCþþAþþ?ÿÿ<þþ9 òò=2¸¸ƒ0ššæ-‘‘ÿ(‹‹ÿƒƒÿ{{ÿttÿnnÿ kkÿ iiÿ ggÿ eeÿ ddÿccÿccÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿbbÿ ccÿ ccÿ ddÿ eeÿ eeÿeeÿffÿffÿggÿhhÿiiÿiiÿkkÿllÿllÿmmÿnnÿooÿllú bbÊ[[TTT ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ þþÿÿþþþþþþþþþþþþ$ÿÿ(þþ*þþ/þþ4þþ7þþ;þþ=þþBþþEþþGþþIþþJþþIþþGþþEþþCþþ?ððD.»º‰+™™è)’’ÿ&ÿ„„ÿ{{ÿttÿ poÿ llÿ jjÿ ggÿ ffÿddÿccÿbbÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿbbÿbbÿ ccÿ ccÿ ddÿ ddÿeeÿffÿffÿggÿggÿhhÿhhÿjjÿkkÿllÿllÿmmÿnnÿooÿllú ddÉ]]RTT ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ þþÿÿþþþþþþþþÿÿ þþ$ÿÿ(þþ,þþ1þþ5þþ9ÿÿ<þþAþþEþþIþþKþþNþþOþþOþþMþþKþþIþþE ññJ,ººŽ)œœé'““ÿ$ŽŽÿ……ÿ||ÿvvÿ qqÿ mmÿjjÿhhÿffÿddÿccÿbbÿa`ÿ__ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿccÿ ccÿ ddÿ ddÿ ddÿ eeÿeeÿffÿggÿhhÿhhÿiiÿjjÿkkÿllÿmmÿnnÿooÿ ppÿmmùccÅ\\M__ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿ ÿÿ þþ ÿÿþþþþÿÿþþþþþþ#ÿÿ(þþ,þþ1þþ5þþ9þþ>þþCþþGþþKþþOþþRþþTþþSþþRþþQþþNþþJ õõN*¿¿(žžé&–•ÿ"ÿ††ÿ~~ÿ wwÿ qqÿmmÿjjÿhhÿffÿccÿccÿaaÿ``ÿ__ÿ^^ÿ__ÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿccÿ ccÿ ccÿ ddÿ ddÿ eeÿffÿggÿggÿhhÿiiÿiiÿkkÿllÿmmÿnnÿnnÿppÿ!qqÿnnøccÁ\\HHHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþ ÿÿ þþÿÿþþþþþþÿÿÿÿ"þþ'þþ,ÿÿ0þþ6þþ;ÿÿ@þþFþþIþþNþþRÿÿUþþWþþXþþXÿÿUþþSþþO õõR(ÃÑ'¢¢è$—–ÿ ÿˆˆÿÿ wwÿqqÿmmÿjjÿggÿeeÿeeÿbbÿaaÿ``ÿ__ÿ^^ÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ]]ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿbbÿbbÿ ccÿ ccÿ ddÿ ddÿ eeÿffÿggÿggÿhhÿhhÿiiÿjjÿkkÿmmÿmmÿnnÿooÿ qqÿ#rrÿlløeeº[[@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿ ÿÿ þþÿÿþþþþÿÿþþþþ!þþ&þþ+þþ/þþ6þþ;þþAþþFþþLþþQÿÿUþþYþþ[þþ\þþ[þþ[þþXþþTööW$ÆÄ‘&£¢è#™˜ÿ ’’ÿ‰‰ÿÿ xxÿqqÿmmÿjjÿhhÿffÿccÿaaÿaaÿ``ÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿbbÿ ccÿ ddÿ ddÿ ddÿ eeÿffÿggÿggÿhhÿiiÿjjÿkkÿllÿmmÿnnÿnnÿppÿ"qqÿ$rrÿnnôcc®\\7UUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿþþ þþ ÿÿ þþÿÿÿÿþþþþþþþþ%þþ*ÿÿ0þþ6þþ;þþAþþGþþMþþSþþWþþ[þþ^þþ_ÿÿ`þþ_þþ\þþXööZ#ÈÈ‘%££è"š™þ““ÿ‰‰ÿ€€ÿ xxÿrrÿnnÿkkÿhhÿeeÿccÿbbÿaaÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ\\ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿ ccÿ ddÿ ccÿ ddÿ eeÿffÿffÿggÿhhÿiiÿiiÿjjÿkkÿllÿmmÿnnÿppÿ!qqÿ$ssÿ&ssÿllñbb ^^+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿþþ ÿÿ þþ ÿÿþþþþþþÿÿþþ#þþ)þþ/þþ5þþ;þþAþþIþþOþþTþþZþþ^ÿÿ`þþbþþbþþbÿÿ`þþ[ùö\!ÊÈ’$¤¤è ››ÿ••ÿ‹‹ÿÿ yyÿrrÿooÿkkÿhhÿeeÿccÿbbÿ``ÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿccÿ ccÿ ccÿ ccÿ ddÿ eeÿffÿffÿggÿhhÿiiÿjjÿkkÿllÿmmÿnnÿooÿ!qqÿ#rrÿ%ttÿ&ssþmmë bbŒ[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþ þþ þþÿÿþþÿÿþþÿÿ"ÿÿ(þþ/þþ5þþ;þþBþþIÿÿPþþXþþ\ÿÿ`þþcþþdþþdþþdþþbþþ]ùù_ ËÊ–#§§éœœÿ–•ÿŒŒÿ‚‚ÿ yyÿssÿnnÿkkÿhhÿffÿbbÿaaÿ``ÿ__ÿ^^ÿ^^ÿ]]ÿ]]ÿ\\ÿ[[ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ__ÿ``ÿaaÿbbÿbbÿbbÿbbÿbbÿbbÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿccÿccÿ ccÿ ccÿ ddÿ eeÿ eeÿffÿggÿhhÿhhÿiiÿjjÿkkÿllÿnnÿooÿ qqÿ"rrÿ$ssÿ'ttÿ&ssþkkâ aaxccÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþ ÿÿþþþþþþÿÿ"ÿÿ(þþ.þþ5þþ;þþBþþIþþQþþWþþ]þþbþþeÿÿfþþhþþgþþdÿÿ`ù÷cÌ̘!¨¨éœœÿ––ÿÿ ‚‚ÿzzÿssÿnnÿkkÿhhÿddÿccÿaaÿ``ÿ``ÿ__ÿ]]ÿ]]ÿ\\ÿ\\ÿ[[ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ^^ÿ__ÿ``ÿ``ÿaaÿbbÿccÿccÿccÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿccÿccÿ ccÿ ddÿ ddÿ ddÿffÿggÿhhÿhhÿiiÿjjÿkkÿllÿmmÿooÿppÿ!qqÿ#ssÿ&ttÿ(vvÿ&ttýiiÕ__[TT ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ þþþþþþþþþþ!þþ'þþ.þþ5ÿÿ<ÿÿDþþKþþRþþYÿÿ`þþeþþiþþkþþkþþjþþhÿÿf÷÷fÍÍ› ªªëÿ˜˜ÿÿ ƒƒÿ|{ÿutÿooÿkkÿggÿeeÿccÿbbÿ``ÿ__ÿ^^ÿ\\ÿ\\ÿ\\ÿ[[ÿ[[ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ__ÿ``ÿbbÿccÿ ccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿbbÿaaÿ``ÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿbbÿbbÿbbÿccÿ ccÿ ddÿ ddÿ eeÿffÿggÿhhÿiiÿiiÿjjÿkkÿllÿnnÿooÿ!qqÿ"rrÿ$ttÿ(uuÿ)vvÿ%ttúhh¾[[=??ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿþþ þþÿÿþþþþÿÿ þþ'þþ/þþ5ÿÿ<ÿÿDþþMþþTþþ\þþbþþiþþlþþmþþnþþnþþlþþgúúiÎΞªªìŸŸÿ˜˜ÿŽŽÿ „„ÿ||ÿttÿooÿkkÿihÿffÿccÿaaÿ__ÿ^^ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ[[ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ^^ÿ__ÿ__ÿaaÿbbÿ bbÿ ccÿ ccÿ ccÿccÿccÿddÿddÿddÿccÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿ ccÿ ddÿ ddÿ eeÿ ffÿggÿggÿhhÿiiÿiiÿkkÿllÿnnÿooÿ qqÿ"rrÿ#ssÿ&ttÿ)vvÿ,xxÿ"rróff¢]]&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþ ÿÿþþþþþþ!þþ'þþ/þþ4ÿÿ<þþFþþOþþVþþ\þþdþþjþþmþþpþþqþþoþþmþþjúúkÏÏ ««ì  ÿ™™ÿÿ ……ÿ||ÿuuÿppÿllÿiiÿeeÿccÿaaÿ__ÿ^^ÿ^^ÿ]]ÿ\\ÿ\\ÿ\\ÿ[[ÿ[[ÿ[[ÿ[[ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ__ÿ``ÿ bbÿ ccÿ bbÿ bbÿ bbÿ bbÿ bbÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿccÿccÿddÿddÿddÿddÿddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿaaÿbbÿbbÿccÿ ddÿ ddÿ eeÿ eeÿffÿggÿhhÿhhÿiiÿkkÿllÿnnÿooÿppÿ qqÿ#ssÿ%ttÿ)vvÿ+xxÿ,xxþppécc~ccÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþ ÿÿþþþþÿÿ ÿÿ(þþ/þþ6þþ>þþGþþOþþWþþ_þþeþþkþþoþþqþþqþþqþþpþþlúúmÑÑ¡¬¬í  ÿ™™ÿÿ ……ÿ||ÿuuÿppÿllÿhhÿeeÿccÿbbÿ__ÿ__ÿ^^ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ[[ÿ[[ÿ\\ÿ\\ÿ]]ÿ__ÿaaÿ bbÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ bbÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ ddÿ ccÿ ddÿddÿddÿddÿddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿccÿ ddÿ ddÿ eeÿ ffÿffÿggÿhhÿiiÿjjÿkkÿmmÿnnÿooÿppÿ"rrÿ%ttÿ(vvÿ*xxÿ-yyÿ,xxýnnÔ``Wmmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþ ÿÿþþþþþþ!ÿÿ(þþ/þþ7þþ?þþHþþQþþYÿÿ`þþgþþmþþpþþrþþtþþsþþrþþnøøpÒÒ¢­¬í¢¢ÿœœÿ’‘ÿ ‡‡ÿÿyyÿuuÿqpÿooÿmmÿjjÿhhÿffÿddÿbbÿbbÿbbÿbbÿccÿbbÿccÿddÿddÿ ccÿ eeÿ ddÿ ddÿ ddÿ ccÿ ddÿ ddÿ ddÿ ddÿ ccÿ bbÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ ccÿ ddÿ ccÿ ddÿ ddÿ ddÿ ccÿddÿddÿddÿddÿddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿbbÿ ccÿ ddÿ eeÿ ffÿffÿggÿhhÿiiÿjjÿkkÿllÿnnÿooÿppÿ!rrÿ$ttÿ'vvÿ*xxÿ,yyÿ/zzÿ)wwøgg±__0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþ ÿÿþþþþþþ!ÿÿ(ÿÿ0þþ8ÿÿ@þþJþþSþþ[þþbþþiþþoþþrþþvþþwþþwþþuþþqøøqÓÓ£²±ì¨¨ÿ¢¢ÿ™˜ÿ ÿ‡‡ÿ‚‚ÿ}}ÿyyÿvvÿttÿqqÿooÿmmÿkkÿkkÿkkÿjjÿkkÿmmÿooÿ ppÿ ppÿ ooÿ nnÿ llÿ llÿ llÿ llÿ kkÿ llÿ jjÿ jjÿ ggÿ eeÿ ddÿ ddÿ ddÿ ccÿ ccÿ ddÿ ccÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿddÿddÿddÿ ddÿ ddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿ ccÿ ccÿ ddÿ eeÿ eeÿffÿggÿiiÿjjÿkkÿllÿmmÿooÿppÿ rrÿ"ssÿ&uuÿ)wwÿ+yyÿ.zzÿ/zzÿ$ssídd‹__ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ ÿÿþþþþþþ!þþ)þþ1þþ9þþBþþKþþTþþ\þþdþþlþþqþþuÿÿxþþzþþyþþwþþsúútÕÕ¥µµì««ÿ¥¤ÿœ›ÿ ’’ÿŠŠÿ„„ÿ~ÿzzÿxxÿuuÿrrÿppÿnnÿmmÿmmÿnnÿppÿ qqÿ rrÿ rrÿ qqÿ qqÿ ppÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ qqÿ ppÿ ppÿ nnÿ nnÿ mmÿ kkÿ hhÿ ggÿ ggÿ eeÿ ddÿ ddÿ ddÿ ddÿ ddÿ ccÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿaaÿbbÿ ccÿ ddÿ ddÿ eeÿ eeÿffÿggÿiiÿjjÿjjÿkkÿmmÿnnÿooÿqqÿ!rrÿ%ttÿ(wwÿ+yyÿ-zzÿ/{{ÿ/zzþppØ aaY__ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþþþ#þþ+þþ2þþ:þþCþþMÿÿUþþ]þþgþþnþþsÿÿxþþzþþ|þþ|þþyþþvøøvÖÖ¤¶¶ì¬¬ÿ¦¥ÿœœÿ ““ÿŒ‹ÿ…„ÿ€€ÿ||ÿxxÿuuÿrrÿqqÿqqÿrrÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ ppÿ ooÿ mmÿ llÿ iiÿ ggÿ eeÿ ddÿ ddÿ ccÿ ccÿ ddÿ ddÿ ddÿddÿ ddÿ ddÿ ddÿ ddÿ ddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ__ÿ``ÿ``ÿ``ÿaaÿbbÿbbÿccÿ ddÿ ddÿ eeÿ ffÿffÿhhÿiiÿjjÿkkÿllÿnnÿooÿqqÿ!rrÿ$ttÿ'vvÿ*xxÿ,zzÿ.{{ÿ1||ÿ-yyøkk²__3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ þþ ÿÿþþþþþþ#þþ+ÿÿ3þþ;ÿÿDþþNþþVÿÿ`þþhþþoþþuþþzþþ~þþ~þþ~þþ|ÿÿxúúxØØ¦¹¹ì­­ÿ§§ÿžžÿ ••ÿÿ††ÿÿ||ÿxxÿwwÿwwÿ wwÿ vvÿ ttÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ qqÿ rrÿ rrÿ rrÿ rrÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ ooÿ mmÿ kkÿ iiÿ ggÿ eeÿ eeÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿbbÿccÿ ccÿ ddÿ eeÿ eeÿffÿggÿhhÿiiÿjjÿllÿmmÿooÿppÿ!rrÿ#ssÿ&ttÿ)wwÿ+yyÿ-zzÿ0||ÿ3}}ÿ*vvìee…]]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ ÿÿþþþþþþ$þþ,þþ4ÿÿ<þþFþþOþþYþþbþþiþþpÿÿxþþ}þþÿÿ€ÿÿ€þüþþ|úø}Üܨººì¯¯ÿ¨¨ÿ  ÿ ––ÿŽŽÿˆˆÿ‚‚ÿ€€ÿ ÿ ||ÿ {{ÿ yyÿ vvÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ rrÿ ssÿ ssÿ ssÿ rrÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ qqÿ ppÿ nnÿ mmÿ jjÿ iiÿ hhÿ eeÿ ddÿ ddÿ ccÿ ddÿ ddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿ ccÿ ddÿ ddÿ eeÿffÿggÿhhÿiiÿjjÿkkÿmmÿnnÿppÿ rrÿ#ssÿ%uuÿ(wwÿ*xxÿ-zzÿ0||ÿ3}}ÿ3}}ü qqÌ __Hffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþ þþÿÿÿÿþþ%þþ-þþ5þþ=þþFþþQþþZþþbþþkþþtþþzþþ~ýýýý‚ýýƒýý‚þþ~üü~ÜÛª¼¼ì°°ÿªªÿ¡¡ÿ ˜˜ÿ‘‘ÿŒŒÿ ‰ˆÿ ……ÿ ‚‚ÿ ~ÿ ||ÿ yyÿ wwÿ uuÿ ttÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ qqÿ qqÿ ooÿ mmÿ kkÿ hhÿ ddÿ ddÿ ddÿ ddÿ ddÿ ddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿccÿ ccÿ ddÿ eeÿffÿggÿhhÿiiÿjjÿjjÿllÿnnÿppÿqqÿ"ssÿ%ttÿ'wwÿ*xxÿ,yyÿ/{{ÿ2}}ÿ5ÿ.yyõiiŸaa"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþþþÿÿÿÿþþ&þþ.þþ5þþ>þþHþþRþþ[þþeþþnþþvþü|ýýýýƒýý„ýý…ýýƒýýûûÜÜ«¼¼í±±ÿ««ÿ¥¤ÿÿ••ÿ Žÿ ŠŠÿ ††ÿ ƒ‚ÿ ÿ ||ÿ zzÿ xxÿ vvÿ uuÿ ttÿ ssÿ rrÿ rrÿ ssÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ qqÿ rrÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ rrÿ qqÿ mmÿ jjÿ ggÿ eeÿ ddÿ ddÿ ddÿ ddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿbbÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿccÿccÿ ddÿ ddÿ ffÿggÿggÿiiÿiiÿjjÿllÿnnÿooÿqqÿ"rrÿ$ttÿ&vvÿ)xxÿ+yyÿ.{{ÿ1||ÿ5ÿ7ÿ)vvÞdd`__ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþþþþþþþþþ&þþ.þþ6þþ?þþIþþSþþ^þþhþþpþþwüü~ýýƒýý…ýý‡þý‡ýý†ýýƒûûƒÜܬ½¼íµµÿ±°ÿ©¨ÿŸŸÿ ——ÿ ÿ ‹‹ÿ ‡‡ÿ ƒƒÿ ÿ ||ÿ zzÿ xxÿ wwÿ uuÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ rrÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ rrÿ qqÿ qqÿ mmÿ kkÿ hhÿ ffÿ ddÿ ddÿddÿddÿddÿddÿccÿccÿccÿccÿccÿccÿbbÿccÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿbbÿccÿ ccÿ ddÿ ffÿggÿggÿiiÿiiÿjjÿllÿmmÿooÿqqÿ!rrÿ#ttÿ&uuÿ)wwÿ+yyÿ-zzÿ0{{ÿ4~~ÿ7€€ÿ4~~øll«[['ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿþþÿÿ þþ'þþ/þþ8þþBþþLþþVÿÿ`þþiþþqþþyÿÿ€ýý…ýýˆýýŠýýŠþý‰ýý†ûû…ÝÝ®ÀÀí¸·ÿ²²ÿ©©ÿ¡ ÿ ˜˜ÿ ‘‘ÿ ŒŒÿ ˆˆÿ ƒƒÿ €€ÿ }}ÿ zzÿ xxÿ wwÿuuÿ ttÿssÿssÿssÿssÿssÿrrÿrrÿrrÿrrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ rrÿ rrÿ rrÿ ssÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ ppÿ nnÿ jjÿ ggÿ ddÿddÿddÿddÿddÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿ``ÿaaÿaaÿbbÿbbÿ ccÿ ddÿ eeÿffÿggÿhhÿiiÿjjÿkkÿmmÿnnÿppÿ!rrÿ#ssÿ%uuÿ(wwÿ+xxÿ-zzÿ/{{ÿ2}}ÿ5ÿ9‚‚þ+wwäeeiff ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþÿÿ ÿÿ(þþ1þþ:þþCþþMþþWþþaþþjþþsþþ{þþ‚þý‡ýý‹ýýŒýýýý‹ýý‡ùùˆÞÞ¯ÁÁí¹¹ÿ³³ÿ«ªÿ¢¢ÿššÿ ““ÿ ŽŽÿ ‰‰ÿ ……ÿ ‚‚ÿ ~~ÿ {{ÿ yyÿwwÿvvÿuuÿttÿssÿssÿrrÿssÿssÿrrÿssÿrrÿrrÿssÿssÿssÿssÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿ ttÿ ssÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ mmÿ iiÿ hhÿeeÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿaaÿaaÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ``ÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿ ddÿ eeÿ ffÿggÿggÿiiÿjjÿkkÿllÿnnÿooÿ rrÿ"ssÿ$ttÿ'vvÿ+yyÿ,zzÿ/{{ÿ2}}ÿ5€€ÿ9‚‚ÿ8û$rr¶ aa/ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþþþ!þþ*þþ2þþ:ÿÿDþþNþþXþþbþþkþþtþþ}þþƒýý‰ýýŒýýŽýýýýýýŠùùŠÝݱÁÁï¹¹ÿ´´ÿ¬¬ÿ¤¤ÿ œœÿ ””ÿ ÿ ‹‹ÿ ††ÿ ‚‚ÿ ~~ÿ ||ÿ yyÿ xxÿvvÿuuÿttÿttÿssÿssÿssÿssÿssÿssÿssÿssÿssÿssÿssÿrrÿssÿssÿssÿssÿ ssÿ ttÿ ssÿ ttÿ ttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ qqÿ rrÿ qqÿ qqÿ rrÿ nnÿiiÿeeÿddÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿ``ÿ__ÿ__ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿ ccÿ ddÿ eeÿffÿggÿiiÿjjÿkkÿllÿnnÿooÿqqÿ!ssÿ$ttÿ'vvÿ+yyÿ,zzÿ.{{ÿ1}}ÿ5ÿ8ÿ;ƒƒÿ0zzçhhnUU ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþÿÿ"þþ*þþ2ÿÿ<þþEþþOþþYþþcþþnþþwþþ~ýý…ýý‹ýýŽýý‘ýý’ýýýýŒûûŒáß³ÃÃï»»ÿ¶¶ÿ®®ÿ¥¥ÿÿ ––ÿ ÿ ŒŒÿ ‡‡ÿ ƒƒÿ€€ÿ}}ÿzzÿxxÿwwÿvvÿttÿttÿttÿssÿttÿssÿssÿssÿssÿssÿssÿssÿssÿrrÿrrÿrrÿssÿssÿttÿttÿ ttÿ ttÿ ttÿ ttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ qqÿllÿggÿddÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿ``ÿ__ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿ ccÿ ddÿ eeÿ ffÿggÿhhÿjjÿkkÿllÿmmÿooÿppÿ rrÿ#ttÿ&vvÿ*xxÿ+yyÿ.{{ÿ1}}ÿ5ÿ7ÿ;ƒƒÿ:‚‚ú#ss³bb,ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþÿÿ"þþ*ÿÿ3ÿÿ<þþFþþOþþZþþeþþoÿÿxýý€þý‡ýýýý‘ýý”ýý”ýý’ýýûûááµÅÄð½¼ÿ¸·ÿ¯¯ÿ§§ÿŸŸÿ ™™ÿ ““ÿ ŽŽÿ‰‰ÿ…„ÿÿ~~ÿ{{ÿyyÿxxÿvvÿvvÿuuÿssÿssÿssÿrrÿssÿssÿssÿssÿssÿssÿssÿssÿssÿssÿrrÿssÿttÿttÿttÿttÿ ttÿ ttÿ ttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿppÿmmÿiiÿffÿddÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿbbÿbbÿccÿ ddÿ ddÿ eeÿggÿhhÿiiÿjjÿkkÿmmÿnnÿppÿqqÿ"ssÿ%uuÿ)wwÿ+yyÿ.{{ÿ1}}ÿ4ÿ7ÿ:ƒƒÿ=……ÿ0{{áfffTT ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþÿÿ"þþ*þþ2ÿÿ<þþFÿÿPþþ[ÿÿfþþpþüyýýÿýˆþýýý’ýý•ýý–ýý”ýý’ûû’ãá¸ÆÆð¾½ÿ¹¹ÿ±±ÿ¨¨ÿ¡¡ÿ ššÿ ””ÿ ÿ ŠŠÿ……ÿ‚‚ÿ~~ÿ||ÿyyÿxxÿwwÿvvÿuuÿttÿttÿssÿssÿssÿttÿttÿttÿssÿssÿssÿttÿttÿssÿssÿssÿttÿttÿttÿttÿttÿttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿqqÿooÿkkÿffÿddÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿ ccÿ ddÿ eeÿ ffÿggÿiiÿjjÿkkÿmmÿnnÿppÿqqÿ"ssÿ$uuÿ(wwÿ+yyÿ.{{ÿ0}}ÿ3ÿ7ÿ:ƒƒÿ=……ÿ9‚‚÷pp¦dd&ÿÿÿÿÿÿÿÿÿÿÿÿþþþþ þþ ÿÿþþþþþþ#þþ+ÿÿ3þþ=þþGþþQþþ\ÿÿfþþpþþyýý‚ýý‰ýýýý“ýý–ýý—ýý—ýý”ùù•ãâ¹ÈÇðÀ¿ÿº¹ÿ²²ÿªªÿ¢¢ÿ ššÿ ••ÿ ÿ‹‹ÿ††ÿ‚‚ÿÿ||ÿzzÿyyÿwwÿvvÿuuÿuuÿttÿvvÿttÿvvÿuuÿuuÿttÿttÿttÿvvÿttÿttÿttÿuuÿttÿttÿttÿuuÿttÿttÿttÿttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿppÿmmÿggÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿaaÿaaÿaaÿbbÿbbÿccÿ ccÿ ccÿ eeÿ ffÿggÿiiÿjjÿkkÿmmÿnnÿppÿqqÿ!ssÿ#ttÿ&vvÿ*yyÿ-{{ÿ0||ÿ3~~ÿ6ÿ:ƒƒÿ=……ÿ>……þ0||àiio[[ÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþþþþþþþþþ$þþ+þþ4þþ=þþGþþRþþ\ÿÿfþþqþþ{ýýƒýýŠýý‘ýý•ýý˜ýýšýý™ýý–ùù—äã¹ÉÈðÁÀÿ»»ÿ³³ÿ««ÿ££ÿ œœÿ ––ÿ‘‘ÿŒŒÿ‡‡ÿƒƒÿ€€ÿ}}ÿ||ÿyyÿwwÿwwÿwwÿvvÿvvÿvvÿwwÿvvÿuuÿuuÿuuÿttÿvvÿvvÿuuÿuuÿuuÿttÿttÿttÿuuÿttÿttÿttÿuuÿttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿrrÿrrÿppÿllÿiiÿeeÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿaaÿaaÿ``ÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿccÿ ccÿ ccÿ ddÿ ffÿggÿhhÿiiÿkkÿllÿnnÿooÿqqÿ!ssÿ#ttÿ%uuÿ)xxÿ,zzÿ/||ÿ2~~ÿ6€€ÿ9ƒƒÿ<„„ÿ?††ÿ<……û&vvÀ ee???ÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ ÿÿþþþþþþ#þþ*þþ4þþ>þþIþþSþþ]þþhþþrþþ{ýý„ýý‹ýý’ýý–ýýšýýœýý›ýý™ûû˜åã»ÊÉðÂÂÿ½¼ÿµµÿ­­ÿ¥¥ÿ žžÿ ™™ÿ “’ÿŽŽÿ‰‰ÿ……ÿ‚‚ÿ€€ÿ}}ÿ{{ÿxxÿyyÿxxÿvvÿwwÿwwÿwwÿvvÿvvÿvvÿvvÿwwÿwwÿvvÿvvÿuuÿuuÿuuÿuuÿvvÿuuÿuuÿuuÿuuÿvvÿvvÿ uuÿ ttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿrrÿrrÿqqÿqqÿnnÿiiÿeeÿccÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿ ddÿ ccÿ ddÿ eeÿffÿhhÿiiÿjjÿkkÿmmÿooÿqqÿ rrÿ"ttÿ%uuÿ(wwÿ+yyÿ.{{ÿ2~~ÿ5€€ÿ8‚‚ÿ<„„ÿ?††ÿA‡‡ÿ7înnˆ hhÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ ÿÿþþþþþþ#þþ+þþ4þþ?þþHþþSþþ^þþiþþrüü|þý„ýýýý“ýý˜ýýœýýýýýý›ûûšåå¼ÌËñÄÃÿ¿¿ÿ··ÿ¯¯ÿ¨¨ÿ ¡¡ÿ ››ÿ••ÿÿ‹‹ÿ‡‡ÿƒƒÿÿÿ{{ÿzzÿyyÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿvvÿuuÿ uuÿ ttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿrrÿrrÿrrÿrrÿppÿnnÿiiÿeeÿccÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿaaÿaaÿaaÿbbÿccÿddÿ ccÿ ccÿ eeÿffÿhhÿiiÿjjÿkkÿmmÿooÿqqÿ rrÿ"ttÿ$uuÿ'wwÿ*yyÿ.{{ÿ2~~ÿ5€€ÿ8‚‚ÿ<„„ÿ?††ÿAˆˆÿAˆˆý-zzÈii?UUÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ ÿÿþþþþþþ#þþ,þþ5þþ>þþHþþTþþ_þþiþþrþþ{þþ…ýýŽýý”ýý™ýýýý ýý ýýûûžæå½ÍÍñÆÆÿÁÀÿ¹¹ÿ²²ÿªªÿ ¢¢ÿ œœÿ——ÿ’’ÿÿ‰‰ÿ††ÿ‚‚ÿÿ}}ÿ{{ÿyyÿyyÿyyÿxxÿwwÿxxÿwwÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿvvÿ uuÿ uuÿ vvÿ uuÿ uuÿ ttÿ ttÿ ssÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ rrÿrrÿrrÿrrÿqqÿppÿmmÿhhÿddÿbbÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿaaÿaaÿbbÿccÿ ccÿ ddÿ eeÿffÿggÿiiÿjjÿkkÿmmÿooÿppÿrrÿ!ssÿ$uuÿ&vvÿ*xxÿ-zzÿ0}}ÿ4ÿ7ÿ<„„ÿ?††ÿAˆˆÿDŠŠÿ9ƒƒëpptbb ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþ ÿÿþþþþþþ#þþ+þþ4þþ>þþHþþTþþ^þþhþþrþþ}ýý‡ýýŽýý•ýý›ýý ýý¢ýý¢ýý úú ææ¿ÐÏñÇÆÿÃÂÿ¼»ÿ´³ÿ««ÿ ¤¤ÿ ŸŸÿ™™ÿ””ÿÿ‹‹ÿ††ÿ‚‚ÿ€€ÿ~~ÿ||ÿzzÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿwwÿxxÿxxÿxxÿxxÿxxÿwwÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿvvÿwwÿ vvÿ vvÿ vvÿ uuÿ ttÿ ttÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿqqÿrrÿrrÿqqÿqqÿqqÿppÿmmÿffÿccÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿbbÿaaÿbbÿccÿ ccÿ ddÿ eeÿffÿggÿhhÿiiÿkkÿllÿnnÿppÿrrÿ!ssÿ$uuÿ%vvÿ)xxÿ,zzÿ0||ÿ4ÿ7ÿ;„„ÿ?††ÿAˆˆÿDŠŠÿA‡‡ú(xxª dd&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ ÿÿþþþþÿÿ"þþ+ÿÿ3þþ=þþHþþRþþ]þþiþþtüüýý‡ýýýý—ýýýý¡ýý£ýý£ýý¢ûû¢èçÁÐÏòÉÈÿÅÄÿ½½ÿµµÿ­­ÿ¦¦ÿ   ÿššÿ••ÿÿ‹‹ÿ‡‡ÿ„„ÿÿ~~ÿ||ÿzzÿyyÿxxÿwwÿwwÿxxÿxxÿwwÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ uuÿ uuÿ uuÿ uuÿ ttÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ rrÿrrÿrrÿrrÿqqÿqqÿqqÿooÿjjÿccÿbbÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿccÿbbÿbbÿccÿccÿ ddÿ ddÿ ffÿggÿhhÿiiÿjjÿllÿnnÿppÿrrÿ!ssÿ#uuÿ%vvÿ)xxÿ,zzÿ0||ÿ3ÿ6ÿ:ƒƒÿ>††ÿAˆˆÿDŠŠÿEŠŠþ3~~ÕjjOffÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ ÿÿ ÿÿþþþþÿÿ"þþ)ÿÿ3þþ=þþFþþQþþ]þþiþþtüü~þý‡ýýýý˜ýýžýý¢ýý¤ýý¥ýý£ûû¤ éèÂÑÐóËÊÿÆÅÿ¾¾ÿ¶¶ÿ¯¯ÿ¨¨ÿ¡¡ÿ››ÿ––ÿ‘‘ÿŒŒÿˆˆÿ„„ÿÿ~~ÿ||ÿ{{ÿzzÿyyÿxxÿxxÿxxÿwwÿwwÿwwÿxxÿwwÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ uuÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ rrÿqqÿrrÿrrÿqqÿqqÿqqÿqqÿqqÿllÿffÿccÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿccÿccÿbbÿbbÿccÿ ddÿ eeÿ ffÿggÿhhÿiiÿjjÿllÿmmÿooÿqqÿ!ssÿ#ttÿ%vvÿ)xxÿ,zzÿ/||ÿ2~~ÿ5€€ÿ9ƒƒÿ=……ÿA‡‡ÿC‰‰ÿGŒŒÿ@††ò$tt†TTÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþÿÿ"þþ)þþ2þþ;þþEþþQþþ]þþhþþsþþ~ÿÿˆýý’ýý™ýýŸýý£ýý¥ýý¦ýý¦ûû§ êèÅÓÒóÌËÿÆÆÿ¿¿ÿ··ÿ°¯ÿ¨¨ÿ¢¢ÿœœÿ––ÿ‘‘ÿŒŒÿˆˆÿ„„ÿÿÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿxxÿxxÿwwÿwwÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ vvÿ uuÿ ttÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿrrÿrrÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿppÿiiÿddÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿccÿccÿccÿbbÿccÿ ddÿ ddÿ ffÿggÿhhÿiiÿjjÿkkÿmmÿnnÿppÿrrÿ#ttÿ%vvÿ(wwÿ+zzÿ/||ÿ2~~ÿ5€€ÿ9‚‚ÿ<„„ÿ@‡‡ÿC‰‰ÿF‹‹ÿDŠŠü)vv² cc)ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿþþþþþþ!ÿÿ(þþ2þþ;þþFþþQþþ]þþhþþtþþýýŠýý“ýýšýý ýý¤ýý§þý©ýý©ûû©êêÆÓÒóÌËÿÇÆÿÀÀÿ¹¸ÿ°°ÿ ©©ÿ¢¢ÿœœÿ——ÿ’’ÿÿ‰‰ÿ……ÿ‚‚ÿÿ}}ÿ||ÿ{{ÿyyÿyyÿxxÿxxÿxxÿwwÿwwÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿxxÿxxÿ wwÿ wwÿ wwÿ vvÿ ttÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿrrÿppÿmmÿggÿddÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿaaÿaaÿbbÿccÿccÿccÿccÿccÿccÿ eeÿ ffÿggÿggÿiiÿjjÿkkÿmmÿnnÿppÿrrÿ"ttÿ$vvÿ'wwÿ*yyÿ/||ÿ1~~ÿ4ÿ8‚‚ÿ;„„ÿ>††ÿB‰‰ÿF‹‹ÿHŒŒþ4ÖhhNÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþ ÿÿ ÿÿÿÿþþþþ!ÿÿ(þþ1þþ;þþFþþQþþ]þþiþþuÿÿ€ýý‹ýý”ýýœýý¢ýý¦ýý©ýý«ýýªüüª êéÇÕÓóÍÌÿÈÈÿÁÁÿ¹¹ÿ²²ÿ ««ÿ¤¤ÿžžÿ˜˜ÿ““ÿŽŽÿŠŠÿ……ÿ‚‚ÿ€€ÿ~~ÿ||ÿzzÿzzÿyyÿxxÿxxÿxxÿwwÿxxÿwwÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿxxÿwwÿwwÿwwÿ wwÿ vvÿ uuÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿooÿllÿffÿccÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿaaÿaaÿbbÿccÿccÿccÿddÿccÿccÿ ddÿ ffÿ ffÿggÿiiÿjjÿkkÿllÿnnÿppÿqqÿ!ssÿ$uuÿ&wwÿ*yyÿ.{{ÿ1~~ÿ4ÿ8‚‚ÿ:ƒƒÿ>††ÿAˆˆÿE‹‹ÿIÿ=……îpp}UUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿÿÿþþþþþþ'þþ1þþ:þþEþþQþþ^þþjþþuýýùùõóžûûŸýý£ýý¨ýý«ýý¬ýýªúú¬ êéÈÕÔôÏÎÿÊÉÿÂÂÿººÿ³³ÿ¬¬ÿ¥¥ÿŸŸÿ™™ÿ””ÿÿŠŠÿ††ÿƒƒÿ€€ÿ~~ÿ||ÿ{{ÿzzÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ vvÿ vvÿ uuÿ uuÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿooÿjjÿddÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿaaÿaaÿbbÿbbÿccÿccÿddÿddÿddÿddÿ ddÿ eeÿ ffÿggÿhhÿjjÿkkÿmmÿmmÿooÿqqÿ!ssÿ#ttÿ&vvÿ*yyÿ-{{ÿ0}}ÿ4€€ÿ7‚‚ÿ:ƒƒÿ=††ÿA‡‡ÿDŠŠÿHŒŒÿE‹‹ù'xxª``%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿþþÿÿþþþþ'þþ1þþ;þþEþþQþþ^þþjúøzÛÙ¥ ÓÑË êê²ûûŸýý¥ýýªýý¬ýý­ýý¬úú® ëêÉ×ÖôÐÏÿËÉÿÃÂÿ¼»ÿ´´ÿ­­ÿ¦¦ÿ  ÿ››ÿ••ÿÿ‹‹ÿ‡‡ÿ„„ÿÿÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ uuÿ ttÿ ttÿ ssÿ ssÿ rrÿ rrÿ qqÿ qqÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿmmÿggÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿccÿccÿddÿddÿ ddÿ ddÿ eeÿ eeÿ ffÿggÿhhÿjjÿkkÿllÿmmÿooÿqqÿ ssÿ#ttÿ&vvÿ(xxÿ-{{ÿ0}}ÿ4ÿ7ÿ:ƒƒÿ=……ÿ@‡‡ÿDŠŠÿGŒŒÿIþ3ÐiiFUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþþþÿÿþþÿÿ(þþ1þþ:þþFþþRôôdËËš ¸¸ÚÀÀöÎÎç íì²ýý¡ýý§ýý«ýý®ýý¯ýý®üü¯ êêËØ×ôÐÏÿËÊÿÄÃÿ¼¼ÿ´´ÿ­­ÿ§§ÿ¡¡ÿ››ÿ••ÿÿŒŒÿ‡‡ÿ……ÿ‚‚ÿÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ uuÿ uuÿ uuÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿooÿiiÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿbbÿccÿccÿddÿ ddÿ ddÿ ddÿ eeÿ eeÿ ffÿggÿhhÿiiÿkkÿllÿmmÿooÿppÿ rrÿ#ttÿ%vvÿ(xxÿ,zzÿ0}}ÿ3ÿ7ÿ:ƒƒÿ=……ÿ?‡‡ÿC‰‰ÿE‹‹ÿJÿ;……çoopjj ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþþþÿÿþþþþ'þþ/þþ:îîM¸¸Ž££Ý¬¬ù»ºÿÂÂþÎÍè ìë´ýý£ýý¨ýý­ýý°ýý±ýý°üü± ììÌÙØôÑÐÿÌËÿÅÄÿ½½ÿµµÿ¯¯ÿ¨¨ÿ¡¡ÿ››ÿ––ÿ‘‘ÿŒŒÿ‰‰ÿ……ÿ‚‚ÿÿ~~ÿ||ÿ{{ÿzzÿzzÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ uuÿ ttÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿppÿkkÿeeÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿaaÿbbÿbbÿccÿccÿccÿ ddÿ ddÿ ddÿ eeÿ ffÿ ffÿggÿhhÿhhÿjjÿllÿmmÿooÿppÿrrÿ"ttÿ%vvÿ'wwÿ+zzÿ.||ÿ3ÿ6ÿ:ƒƒÿ=……ÿ?‡‡ÿB‰‰ÿE‹‹ÿIÿDŠŠö%tt› ddÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ þþÿÿþþþþ&ìì6¦¦|Û ––û££ÿ±±ÿ»»ÿÂÁþÎÍè ììµýý¤ýý©ýý¯ýý²ýý³ýý²üü³ íìÍÚÙôÒÑÿÍÌÿÅÅÿ½½ÿ··ÿ¯¯ÿ¨¨ÿ¢¢ÿœœÿ––ÿ‘‘ÿÿ‰‰ÿ††ÿ‚‚ÿ€€ÿ~~ÿ||ÿ{{ÿzzÿzzÿyyÿxxÿxxÿwwÿxxÿxxÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ vvÿ uuÿ ttÿ uuÿ ssÿ rrÿ rrÿ rrÿ qqÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿooÿmmÿhhÿbbÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿbbÿccÿccÿccÿ ddÿ ddÿ eeÿ ffÿ ffÿ ffÿggÿhhÿiiÿjjÿllÿmmÿooÿppÿrrÿ"ttÿ%vvÿ'wwÿ*yyÿ-{{ÿ2~~ÿ6ÿ9ƒƒÿ<……ÿ>††ÿB‰‰ÿE‹‹ÿHÿHý1||Àcc6ÿÿÿÿÿÿÿÿÿÿþþÿÿ þþ þþÿÿéé#——gÔ ††ûÿššÿ¥¥ÿ±±ÿ»»ÿÂÁþÏÎèìì¶ûû§ýý¬ýý±ýý´ýýµýý´üüµ íìÎÙÙõÓÑÿÍÌÿÆÅÿ¾¾ÿ·¶ÿ¯¯ÿ¨¨ÿ¢¢ÿÿ——ÿ’’ÿŽŽÿ‰‰ÿ††ÿƒƒÿ€€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿxxÿwwÿxxÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ uuÿ uuÿ uuÿ ssÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿppÿppÿooÿkkÿddÿbbÿaaÿbbÿbbÿbbÿbbÿbbÿccÿccÿccÿ ddÿ ddÿ eeÿ ffÿ ffÿ ffÿ ggÿhhÿiiÿjjÿkkÿmmÿnnÿppÿrrÿ"ttÿ%vvÿ'wwÿ*yyÿ-{{ÿ1~~ÿ5€€ÿ8‚‚ÿ<„„ÿ>‡‡ÿB‰‰ÿE‹‹ÿHŒŒÿJŽŽÿ<……âmmkUU ÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ þþÜ܈ˆTuuÉ {{úƒƒÿŠŠÿ‘‘ÿ››ÿ¦¦ÿ±±ÿ»»ÿÃÂþÏÎéìë¸ýû¨ýý®ýý³ýýµýý·ýý¶üü¶ ííÎÛÚôÔÒÿÎÍÿÇÆÿ¿¾ÿ¸·ÿ°°ÿ©©ÿ¤¤ÿÿ——ÿ““ÿŽŽÿŠŠÿ††ÿƒƒÿ€€ÿÿ~~ÿ}}ÿ{{ÿzzÿzzÿyyÿxxÿyyÿxxÿxxÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ wwÿ vvÿ vvÿ vvÿ vvÿ ttÿ ttÿ ssÿ ssÿ rrÿ rrÿ qqÿqqÿqqÿppÿqqÿqqÿqqÿqqÿqqÿqqÿppÿppÿqqÿppÿmmÿhhÿccÿaaÿbbÿbbÿbbÿbbÿccÿccÿccÿ ddÿ ddÿ eeÿ ffÿ ggÿ ggÿ ggÿhhÿiiÿjjÿkkÿmmÿnnÿooÿqqÿ"ttÿ%vvÿ'wwÿ*yyÿ-{{ÿ0}}ÿ4€€ÿ8‚‚ÿ<……ÿ>‡‡ÿB‰‰ÿDŠŠÿGŒŒÿKŽŽÿF‹‹ø'vv¦ ``%ÿÿÿÿÿÿÿÿÿÿÿÿþþ×× {{Bjj¿ qqøyyÿ~~ÿ„ƒÿŠŠÿ’’ÿ››ÿ¥¥ÿ±±ÿ¼¼ÿÄÃþÏÏé ëëºýüªýý¯ýý´ýý·ýý¸ýý·ýü· ííÏÛÚõÔÓÿÏÎÿÈÇÿÀ¿ÿ¹¸ÿ±±ÿ««ÿ¤¤ÿÿ™™ÿ””ÿÿŠŠÿ‡‡ÿƒƒÿÿÿ~~ÿ||ÿ{{ÿ{{ÿzzÿyyÿyyÿxxÿxxÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ vvÿ vvÿ ttÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿqqÿppÿppÿppÿooÿppÿooÿhhÿccÿbbÿbbÿaaÿbbÿccÿccÿccÿ ddÿ ddÿ eeÿ ffÿ ggÿggÿggÿhhÿiiÿjjÿkkÿmmÿnnÿooÿqqÿ"ttÿ%vvÿ'wwÿ*yyÿ-{{ÿ0}}ÿ4ÿ7‚‚ÿ;„„ÿ>††ÿAˆˆÿCŠŠÿGŒŒÿJŽŽÿKý7€€ÌggEUUÿÿÿÿÿÿÿÿÿÿÔÔmm1cc° jjörrÿvvÿyyÿ~~ÿ„„ÿ ŠŠÿ ’’ÿ››ÿ¦¦ÿ²²ÿ½¼ÿÄÃþÑÐë îí¼ýü¬ýý±ýý¶ýý¹ýýºýýºüü¹ ðîÏÜÛôÕÔÿÐÏÿÉÈÿÂÁÿº¹ÿ³³ÿ¬¬ÿ¥¥ÿŸŸÿššÿ””ÿÿ‹‹ÿˆˆÿ„„ÿÿÿ~~ÿ||ÿ||ÿ{{ÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿyyÿxxÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ uuÿ ttÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿqqÿqqÿppÿqqÿqqÿqqÿqqÿqqÿppÿppÿppÿppÿppÿnnÿkkÿeeÿbbÿaaÿbbÿbbÿccÿccÿddÿ ddÿ eeÿ eeÿ ffÿggÿhhÿggÿiiÿjjÿkkÿllÿmmÿooÿqqÿ!ssÿ$uuÿ'wwÿ)yyÿ,{{ÿ0}}ÿ3ÿ6ÿ:„„ÿ>††ÿAˆˆÿDŠŠÿGŒŒÿIÿLÿAˆˆæ!rrdmmÿÿÿÿÿÿll!^^ ffòmmþooÿrrÿuuÿyyÿ~~ÿ „„ÿ ‹‹ÿ ’’ÿ œœÿ§§ÿ³²ÿ¾½ÿÆÅþÓÒì íì¾ýý­ýý³ýý¸ýý»ýý¼ýý»üüº ððÐÝÜôÕÔÿÑÏÿÊÉÿÃÂÿ»»ÿ³³ÿ­­ÿ¦¦ÿ  ÿššÿ••ÿÿŒŒÿˆˆÿ……ÿ‚‚ÿ€€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ uuÿ uuÿ ttÿ uuÿ ttÿ ssÿ rrÿ rrÿqqÿqqÿqqÿppÿppÿqqÿqqÿqqÿppÿppÿppÿppÿppÿooÿooÿmmÿffÿccÿbbÿccÿccÿccÿddÿ ddÿ ddÿ eeÿ ffÿggÿhhÿhhÿiiÿjjÿkkÿmmÿnnÿooÿqqÿ rrÿ$uuÿ'wwÿ*yyÿ-{{ÿ0}}ÿ3ÿ6ÿ:„„ÿ=……ÿ?ˆˆÿDŠŠÿGŒŒÿIÿLÿB‰‰íssy[[ÿÿkk]]ƒ bbìjjþllÿmmÿooÿqqÿttÿyyÿ ~~ÿ „„ÿ ‹‹ÿ ’’ÿ ÿ¨¨ÿ´³ÿ¾½ÿÆÅþÔÒì îí¿ýý°ýýµýýºýý½ýý½ýý¼üü» ðïÑÝÝôÕÔÿÑÐÿÊÉÿÃÂÿ»»ÿ´´ÿ­­ÿ§§ÿ¡¡ÿ››ÿ••ÿ‘‘ÿÿ‰‰ÿ……ÿƒƒÿ€€ÿÿ}}ÿ||ÿ{{ÿzzÿzzÿyyÿyyÿyyÿyyÿyyÿyyÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿxxÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ uuÿ uuÿ vvÿ vvÿ ttÿ ssÿ rrÿ rrÿ qqÿqqÿqqÿppÿqqÿqqÿqqÿppÿppÿppÿppÿppÿooÿooÿooÿmmÿggÿccÿbbÿccÿccÿddÿ ddÿ eeÿ eeÿ ffÿ ggÿhhÿhhÿiiÿjjÿllÿmmÿnnÿooÿqqÿ rrÿ#ttÿ'wwÿ*yyÿ-{{ÿ/}}ÿ2ÿ6ÿ9ƒƒÿ<……ÿ?‡‡ÿC‰‰ÿF‹‹ÿIÿLÿG‹‹ö&vv™ ggTT ^^d ``ßggýjjÿjjÿkkÿllÿnnÿppÿttÿ yyÿ ~~ÿ ……ÿ ‹‹ÿ ““ÿ ÿ¨¨ÿ´´ÿ¿¾ÿÇÆþÔÑî ïíÂýý²ýý·ýý¼ýý¾þý¾ýý½üü¼ ñðÑÝÜõ×ÖÿÒÑÿËÊÿÃÂÿ»»ÿ´´ÿ®®ÿ§§ÿ¡¡ÿœœÿ——ÿ’’ÿÿ‰‰ÿ††ÿ„„ÿÿÿ~~ÿ||ÿ||ÿ{{ÿzzÿzzÿyyÿzzÿzzÿyyÿxxÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ uuÿ vvÿ vvÿ vvÿ uuÿ ssÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿqqÿppÿppÿppÿppÿppÿooÿppÿppÿppÿnnÿiiÿccÿbbÿccÿddÿ ddÿ eeÿ eeÿ ffÿggÿhhÿiiÿjjÿjjÿkkÿmmÿnnÿooÿppÿrrÿ"ttÿ%vvÿ)xxÿ,zzÿ/||ÿ2~~ÿ6ÿ9ƒƒÿ<……ÿ>††ÿB‰‰ÿE‹‹ÿHŒŒÿKÿKŽŽü1}}¹ ee-ffXXK__Îggüjjÿiiÿjjÿjjÿkkÿkkÿmmÿppÿ ttÿ yyÿ ÿ ……ÿ‹‹ÿ ””ÿ žžÿ ©©ÿµ´ÿÀ¿ÿÈÇþÕÓî îîÄýý´ýý¹ýý½þý¿þý¿ýý¾ýü¾ ððÓÞÝõ×ÖÿÒÑÿËÊÿÃÂÿ¼¼ÿµµÿ®®ÿ¨¨ÿ¢¢ÿÿ——ÿ’’ÿŽŽÿŠŠÿ‡‡ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿzzÿzzÿyyÿyyÿyyÿxxÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ wwÿ vvÿ vvÿ vvÿ ttÿ ssÿ ssÿ rrÿ qqÿ qqÿqqÿqqÿppÿppÿqqÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿppÿooÿjjÿeeÿccÿddÿ ddÿ eeÿ eeÿ ffÿ ggÿhhÿiiÿjjÿjjÿkkÿmmÿnnÿooÿppÿrrÿ"ttÿ%vvÿ(xxÿ,zzÿ/||ÿ2~~ÿ5ÿ9ƒƒÿ;……ÿ>‡‡ÿAˆˆÿDŠŠÿHŒŒÿKŽŽÿLþ0}}Ëee?UU[[2]]¹eeøjjÿjjÿiiÿiiÿhhÿiiÿiiÿkkÿmmÿ ppÿ ttÿ xxÿ ~~ÿ„„ÿŒŒÿ••ÿ ŸŸÿ ªªÿ·¶ÿÁÀÿÉÈþÕÔïïïÅýü¶ýýºýý¾ÿüÀüüÁýüÀüüÀ ððÓÝÝõØ×ÿÓÑÿÌÊÿÄÃÿ½½ÿ¶¶ÿ¯¯ÿ¨¨ÿ££ÿžžÿ˜˜ÿ““ÿÿ‹‹ÿ‡‡ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ||ÿ||ÿ{{ÿ{{ÿzzÿzzÿzzÿyyÿyyÿxxÿxxÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ vvÿ vvÿ vvÿ vvÿ uuÿ ssÿ ttÿ rrÿ qqÿ qqÿqqÿppÿppÿqqÿqqÿppÿppÿppÿppÿooÿooÿooÿppÿppÿppÿqqÿllÿeeÿccÿ ddÿ eeÿ eeÿ ffÿ ggÿhhÿiiÿjjÿkkÿkkÿllÿmmÿnnÿppÿrrÿ"ttÿ%vvÿ(xxÿ+zzÿ/||ÿ2~~ÿ5€€ÿ8ƒƒÿ;……ÿ>‡‡ÿAˆˆÿDŠŠÿGŒŒÿJŽŽÿLÿ:ƒƒÚjjTff[[\\ccójjþjjÿiiÿhhÿhhÿhhÿggÿhhÿiiÿkkÿ mmÿ ppÿ ssÿ yyÿÿ††ÿÿ••ÿ  ÿ ¬¬ÿ··ÿÂÁÿÊÉþÖÕïïîÇüü¸ýý»ýüÀüüÂýýÂüüÁüüÀ ññÓÞÞõØ×ÿÓÒÿÌËÿÅÅÿ¾¾ÿ¶¶ÿ¯¯ÿ©©ÿ¤¤ÿžžÿ™™ÿ””ÿÿ‹‹ÿˆˆÿ……ÿƒƒÿ€€ÿ~~ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿzzÿyyÿyyÿyyÿyyÿyyÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ wwÿ vvÿ vvÿ uuÿ ttÿ ttÿ ssÿ rrÿ qqÿ qqÿqqÿqqÿppÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿqqÿqqÿmmÿffÿ ccÿ eeÿ ffÿ ffÿ ggÿhhÿiiÿjjÿkkÿkkÿllÿmmÿooÿppÿrrÿ!ttÿ%vvÿ'wwÿ*yyÿ.{{ÿ2~~ÿ5€€ÿ8‚‚ÿ;……ÿ>††ÿAˆˆÿDŠŠÿGŒŒÿIŽŽÿLÿAˆˆèppkqq qq ]]p ccéjjþjjÿiiÿhhÿhhÿhhÿggÿffÿggÿggÿiiÿ kkÿ mmÿ ooÿ ttÿyyÿ€€ÿ††ÿŽŽÿ——ÿ¡¡ÿ ­­ÿ¹¸ÿÄÃÿÌÊþÖÕðïîÉýý¹þý½ýýÁüüÄüüÄüüÂüû ñðÔßÞõØ×ÿÔÓÿÍÌÿÆÅÿ¾¾ÿ··ÿ±±ÿ««ÿ¤¤ÿŸŸÿššÿ••ÿÿŒŒÿ‰‰ÿ††ÿƒƒÿÿ€€ÿÿ}}ÿ||ÿ{{ÿzzÿzzÿzzÿyyÿyyÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿwwÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ wwÿ wwÿ vvÿ vvÿ vvÿ ssÿ ssÿ ssÿ rrÿ qqÿ qqÿ qqÿqqÿppÿppÿqqÿppÿppÿppÿppÿppÿppÿqqÿppÿppÿppÿppÿppÿnnÿ ggÿ eeÿ ffÿ ffÿ ggÿhhÿiiÿjjÿkkÿllÿllÿmmÿnnÿppÿrrÿ"ssÿ%uuÿ'wwÿ*yyÿ.{{ÿ1~~ÿ5€€ÿ8ƒƒÿ;……ÿ>††ÿAˆˆÿDŠŠÿGŒŒÿIŽŽÿLÿAˆˆñppff[[@``Ëiiükkÿjjÿiiÿhhÿggÿggÿggÿffÿffÿggÿggÿ hhÿ jjÿ llÿ ooÿttÿzzÿ€€ÿ‡‡ÿÿ˜˜ÿ¢¢ÿ ®®ÿ»ºÿÆÅÿÌÌÿØ×ðñïËýý»üü¿ýýÂüüÅüüÅüüÄüü ñðÕßßõÙØÿÔÔÿÎÍÿÇÆÿÀ¿ÿ¹¹ÿ²²ÿ««ÿ¦¦ÿ  ÿ››ÿ––ÿ‘‘ÿŽŽÿŠŠÿ‡‡ÿ……ÿ‚‚ÿÿÿ~~ÿ}}ÿ{{ÿ{{ÿzzÿyyÿyyÿzzÿyyÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿ vvÿ wwÿ wwÿ wwÿ wwÿ vvÿ uuÿ uuÿ ssÿ rrÿ qqÿ qqÿ qqÿqqÿqqÿppÿqqÿppÿppÿppÿppÿppÿppÿqqÿppÿppÿppÿppÿppÿqqÿ ooÿ jjÿ ggÿ ffÿ ggÿhhÿiiÿjjÿkkÿllÿmmÿmmÿnnÿppÿrrÿ!ssÿ$uuÿ'wwÿ*yyÿ.||ÿ1~~ÿ5€€ÿ8‚‚ÿ;„„ÿ>††ÿ@ˆˆÿDŠŠÿGŒŒÿIÿLÿE‹‹õ%tt• bb__ ]]£ggõllÿkkÿiiÿiiÿggÿggÿffÿffÿeeÿeeÿffÿ ffÿ ggÿ hhÿ iiÿ llÿppÿuuÿ{{ÿ‚‚ÿ‰‰ÿ‘‘ÿ››ÿ¦¦ÿ ³²ÿ¿¾ÿÉÈÿÐÏÿÛÙññðÌüü¼ýýÀüüÅüüÆüüÆüüÄüüà ññÕßÞöÚÙÿÕÔÿÏÎÿÈÇÿÀÀÿººÿ³³ÿ­­ÿ§§ÿ¡¡ÿœœÿ——ÿ””ÿÿ‹‹ÿˆˆÿ††ÿƒƒÿÿ€€ÿÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿyyÿyyÿyyÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿ vvÿ wwÿ wwÿ wwÿ wwÿ vvÿ vvÿ uuÿ ssÿ rrÿ rrÿ qqÿ qqÿqqÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿqqÿrrÿ ppÿ jjÿ ffÿ ggÿhhÿiiÿjjÿkkÿmmÿmmÿmmÿnnÿppÿrrÿ!ssÿ$uuÿ'wwÿ*yyÿ.||ÿ1~~ÿ4€€ÿ7‚‚ÿ;„„ÿ>††ÿ@ˆˆÿCŠŠÿF‹‹ÿIÿKŽŽÿKŽŽü5À dd8NN ^^weeçllþmmÿkkÿjjÿiiÿggÿffÿffÿeeÿeeÿeeÿddÿ eeÿ ffÿ ffÿ ggÿ jjÿooÿttÿ{{ÿÿˆˆÿÿ™™ÿ¢¢ÿ¬¬ÿ ··ÿÁÁÿËÊÿÒÑÿÛÛòññÍüü¿üüÃüüÆüüÇüüÇýüÄüüÃððÖßÞöÚÚÿÖÕÿÐÎÿÈÇÿÂÁÿ»»ÿ´´ÿ®®ÿ©©ÿ¢¢ÿÿ™™ÿ••ÿÿŒŒÿ‰‰ÿ‡‡ÿƒƒÿ‚‚ÿÿÿ}}ÿ}}ÿ||ÿ{{ÿzzÿzzÿzzÿzzÿyyÿyyÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿ vvÿ vvÿ wwÿ wwÿ wwÿ vvÿ vvÿ vvÿ uuÿ ssÿ ssÿ qqÿ qqÿqqÿqqÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿppÿqqÿqqÿ qqÿ rrÿ ppÿ jjÿ ggÿhhÿiiÿjjÿkkÿmmÿmmÿmmÿmmÿooÿqqÿ!ssÿ%vvÿ'wwÿ)xxÿ.{{ÿ1~~ÿ4€€ÿ7‚‚ÿ:„„ÿ>††ÿ@ˆˆÿC‰‰ÿF‹‹ÿHŒŒÿJŽŽÿMÿ@††ÝllWffUUYYJ bbÐkkümmÿllÿkkÿjjÿiiÿggÿffÿffÿeeÿddÿddÿ ddÿ ffÿ hhÿ iiÿ kkÿ ooÿssÿwwÿ||ÿÿ††ÿÿ””ÿ››ÿ¤¤ÿ®®ÿ ¸¸ÿ ÃÂÿÍËÿÓÒÿÝÜòððÐüüÁüüÄüüÇüüÈüüÇüüÆüüÄðï×ßßöÛÚÿÖÕÿÐÏÿÊÉÿÃÂÿ¼¼ÿ¶¶ÿ°°ÿªªÿ¤¤ÿŸŸÿ››ÿ––ÿ‘‘ÿŽŽÿŠŠÿ‡‡ÿ„„ÿƒƒÿÿ€€ÿÿ}}ÿ||ÿ{{ÿ{{ÿzzÿzzÿzzÿyyÿyyÿzzÿyyÿyyÿxxÿxxÿwwÿwwÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ wwÿ wwÿ vvÿ vvÿ vvÿ vvÿ ssÿ ssÿ rrÿ qqÿqqÿqqÿqqÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿqqÿppÿqqÿ qqÿ rrÿ rrÿ ssÿ ppÿ jjÿggÿiiÿjjÿkkÿmmÿnnÿnnÿnnÿooÿqqÿ!ssÿ%vvÿ'wwÿ*yyÿ-{{ÿ0}}ÿ4€€ÿ7‚‚ÿ:„„ÿ=††ÿ@ˆˆÿCŠŠÿF‹‹ÿHÿJŽŽÿMÿA‡‡ánnaUUZZ``©hhömmÿmmÿllÿkkÿjjÿhhÿhhÿffÿeeÿffÿggÿ jjÿ llÿ mmÿ nnÿ ooÿ ppÿ qqÿrrÿuuÿxxÿ||ÿÿ‡‡ÿŽŽÿ••ÿÿ¦¦ÿ°°ÿººÿ ÅÄÿÎÍÿÔÓÿÛÛóññÏüüÁüüÅüüÈüüÊüüÉüüÇûûÆððØàßöÛÚÿ×ÖÿÑÐÿËÉÿÄÃÿ¾½ÿ··ÿ±±ÿ¬¬ÿ¦¦ÿ¡¡ÿœœÿ˜˜ÿ““ÿÿŒŒÿ‰‰ÿ‡‡ÿ„„ÿƒƒÿÿÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿyyÿyyÿyyÿyyÿxxÿwwÿwwÿwwÿxxÿwwÿxxÿxxÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ vvÿ wwÿ vvÿ vvÿ vvÿ vvÿ ttÿ ttÿ rrÿ rrÿ qqÿqqÿqqÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿqqÿqqÿppÿ qqÿ rrÿ rrÿ ssÿ ssÿ qqÿllÿiiÿjjÿkkÿmmÿnnÿnnÿnnÿooÿqqÿ rrÿ#uuÿ'wwÿ)yyÿ-{{ÿ1}}ÿ4€€ÿ6ÿ:„„ÿ=††ÿ?‡‡ÿCŠŠÿE‹‹ÿHÿKŽŽÿMÿBˆˆémmr\\ HH^^dffâmmþmmÿmmÿllÿkkÿjjÿhhÿiiÿjjÿkkÿmmÿmmÿ ooÿ ooÿ ooÿ nnÿ ooÿ ooÿppÿqqÿssÿuuÿxxÿ}}ÿ‚‚ÿˆˆÿÿ——ÿ  ÿ¨¨ÿ²±ÿ¼»ÿ ÆÅÿÎÍÿÔÓÿ ÝÝòññÐüüÂüüÆüüÉüüÊüüÊüüÈüüÇððØààöÜÜÿØ×ÿÒÑÿËÊÿÅÅÿ¿¿ÿ¹¹ÿ³³ÿ®®ÿ¨¨ÿ££ÿžžÿššÿ••ÿ’’ÿŽŽÿ‹‹ÿˆˆÿ††ÿ„„ÿ‚‚ÿÿÿ~~ÿ}}ÿ{{ÿzzÿzzÿzzÿyyÿyyÿyyÿyyÿxxÿwwÿxxÿwwÿwwÿwwÿxxÿwwÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ ttÿ ssÿ rrÿ qqÿqqÿqqÿqqÿppÿppÿppÿppÿppÿppÿppÿppÿ qqÿqqÿqqÿ qqÿ rrÿ ssÿ ssÿ ssÿ ssÿqqÿkkÿjjÿkkÿmmÿnnÿooÿnnÿooÿqqÿ!ssÿ"ttÿ&wwÿ*yyÿ-{{ÿ1}}ÿ3ÿ6ÿ:ƒƒÿ<……ÿ>‡‡ÿB‰‰ÿE‹‹ÿHÿJŽŽÿMÿBˆˆïoo~__ZZ0 cc¹kkùnnÿmmÿmmÿllÿllÿnnÿooÿppÿppÿppÿppÿ ooÿ ooÿ nnÿ nnÿ nnÿ nnÿnnÿnnÿppÿqqÿssÿvvÿyyÿ~~ÿƒƒÿŠŠÿ‘‘ÿ™™ÿ¡¡ÿªªÿ³³ÿ½½ÿ ÇÆÿÏÎÿÕÕÿ ÝÜóòñÑüûÄüüÇüüÊüüËüüËüüÉûûÈòðØáàöÝÜÿÙØÿÓÒÿÍÌÿÇÆÿÀÀÿººÿµµÿ¯¯ÿ©©ÿ¤¤ÿŸŸÿ››ÿ——ÿ““ÿÿŒŒÿ‰‰ÿ‡‡ÿ……ÿƒƒÿÿÿ~~ÿ||ÿ{{ÿ{{ÿ{{ÿzzÿyyÿyyÿyyÿyyÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ uuÿ ttÿ rrÿ qqÿ qqÿqqÿqqÿppÿqqÿqqÿppÿppÿppÿppÿqqÿ qqÿ qqÿqqÿ rrÿ rrÿ ssÿ ssÿ ssÿ ttÿttÿrrÿmmÿkkÿmmÿnnÿooÿooÿooÿqqÿ ssÿ#uuÿ'wwÿ*yyÿ.{{ÿ1~~ÿ4€€ÿ7ÿ:ƒƒÿ<……ÿ?‡‡ÿB‰‰ÿE‹‹ÿGŒŒÿJŽŽÿLÿF‹‹õ%ttŽ ]]____†hhínnþnnÿooÿooÿqqÿttÿssÿssÿqqÿppÿppÿooÿ ooÿ ooÿ nnÿ nnÿ nnÿ nnÿmmÿmmÿnnÿooÿqqÿssÿvvÿzzÿ€€ÿ……ÿ‹‹ÿ““ÿ››ÿ¢¢ÿ««ÿµ´ÿ¾½ÿ ÈÇÿÐÏÿÖÕÿ ßÞóñðÓüûÅüüÈýüËüüÍüüÍýüÊûûÉòðÙãáöÝÝÿÙÙÿÔÔÿÎÍÿÈÇÿÂÁÿ¼¼ÿ¶¶ÿ±±ÿ««ÿ¦¦ÿ¡¡ÿÿ˜˜ÿ””ÿ‘‘ÿŽŽÿ‹‹ÿˆˆÿ‡‡ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ}}ÿ||ÿ||ÿ{{ÿzzÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ uuÿ ssÿ qqÿ qqÿ qqÿqqÿ qqÿqqÿqqÿppÿppÿppÿppÿqqÿqqÿ qqÿqqÿ rrÿ rrÿ rrÿ ssÿ ssÿttÿttÿuuÿssÿmmÿmmÿnnÿooÿooÿppÿqqÿ ssÿ$uuÿ'wwÿ)yyÿ-{{ÿ1~~ÿ3€€ÿ7ÿ9ƒƒÿ=……ÿ?‡‡ÿB‰‰ÿDŠŠÿFŒŒÿIŽŽÿLÿJŽŽù+xx˜ ccUU^^I ddÓnnüqqÿssÿvvÿuuÿvvÿuuÿttÿrrÿqqÿqqÿppÿ ppÿ ooÿ nnÿ mmÿ mmÿ mmÿmmÿmmÿmmÿmmÿnnÿooÿqqÿttÿxxÿ||ÿÿ‡‡ÿÿ••ÿœœÿ¤¤ÿ­­ÿµµÿ¿¿ÿ ÉÈÿÑÐÿ×Öÿ àßóòñÓüüÆüüÉüüÍüüÏýüÍýüËüüÊòòÙãáöÞÞÿÛÛÿÕÕÿÏÎÿÉÈÿÄÃÿ¾½ÿ¸¸ÿ³³ÿ­­ÿ©©ÿ¤¤ÿŸŸÿššÿ––ÿ““ÿÿŒŒÿŠŠÿ‡‡ÿ……ÿƒƒÿÿÿ~~ÿ}}ÿ{{ÿ{{ÿ{{ÿzzÿyyÿyyÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿvvÿwwÿwwÿwwÿ wwÿ vvÿ vvÿ uuÿ uuÿ uuÿ vvÿ vvÿ ttÿ ssÿ qqÿ qqÿ qqÿ qqÿqqÿqqÿppÿppÿppÿppÿqqÿqqÿ qqÿqqÿ rrÿ rrÿ ssÿ ssÿ ssÿ ssÿttÿuuÿvvÿssÿnnÿnnÿppÿppÿppÿqqÿ ssÿ#uuÿ'wwÿ)xxÿ,zzÿ0}}ÿ3€€ÿ7‚‚ÿ:„„ÿ=……ÿ?‡‡ÿB‰‰ÿDŠŠÿF‹‹ÿIÿLÿJŽŽù+yyž ^^[[ccnnóvvÿwwÿxxÿwwÿvvÿvvÿuuÿttÿrrÿrrÿqqÿppÿ ppÿ ooÿ nnÿ mmÿ mmÿllÿllÿllÿllÿmmÿmmÿooÿppÿqqÿuuÿyyÿ}}ÿÿˆˆÿÿ––ÿÿ¥¥ÿ­­ÿ·¶ÿÀ¿ÿ ÉÈÿÒÑÿØ×ÿáàóòñÔûûÇýüÊýüÍüüÎüüÎýüÌüûËóòÙ äãöàßÿÜÛÿÖÕÿÐÏÿËÊÿÅÄÿ¿¿ÿººÿµ´ÿ°°ÿ««ÿ¦¦ÿ¡¡ÿœœÿ˜˜ÿ””ÿ‘‘ÿŽŽÿ‹‹ÿˆˆÿ……ÿ„„ÿÿ€€ÿÿ}}ÿ||ÿ||ÿ{{ÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿwwÿvvÿwwÿwwÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ uuÿ vvÿ vvÿ uuÿ ttÿ qqÿ qqÿ qqÿ rrÿ qqÿqqÿqqÿqqÿppÿqqÿqqÿ qqÿ qqÿ qqÿ rrÿ rrÿ ssÿ ssÿttÿttÿuuÿuuÿvvÿwwÿttÿppÿppÿppÿppÿqqÿ!ssÿ$uuÿ'wwÿ)yyÿ,{{ÿ0}}ÿ3€€ÿ7‚‚ÿ:„„ÿ=††ÿ?‡‡ÿB‰‰ÿE‹‹ÿG‹‹ÿIÿKÿJŽŽù,yy¡``??gg[ooÞwwþzzÿyyÿxxÿwwÿvvÿvvÿuuÿttÿssÿrrÿqqÿ ppÿ ooÿ ooÿ nnÿ mmÿ mmÿllÿkkÿllÿllÿllÿllÿnnÿnnÿppÿssÿvvÿyyÿ~~ÿ„„ÿŠŠÿÿ——ÿŸŸÿ§§ÿ¯®ÿ··ÿÁÀÿ ÌÊÿÔÓÿÚÙÿâáóòòÔüüÇüüËüüÎüüÏüüÏüüÍüüËóòÚ åäöàßÿÝÜÿ×ÖÿÒÐÿÌËÿÆÅÿÁÀÿ»»ÿ·¶ÿ²²ÿ¬¬ÿ¨¨ÿ££ÿŸŸÿššÿ––ÿ““ÿÿŒŒÿˆˆÿ‡‡ÿ……ÿ‚‚ÿÿ~~ÿ}}ÿ||ÿ||ÿ{{ÿzzÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿwwÿvvÿvvÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ vvÿ ttÿ rrÿ ppÿ qqÿ rrÿ qqÿ qqÿ qqÿqqÿqqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ ssÿ ssÿttÿuuÿuuÿuuÿvvÿxxÿxxÿuuÿppÿppÿppÿqqÿ!ssÿ$uuÿ'wwÿ*yyÿ-{{ÿ0}}ÿ2ÿ6ÿ:„„ÿ=……ÿ?‡‡ÿB‰‰ÿE‹‹ÿGŒŒÿIÿKÿJŽŽù,zz¦__ bbnn¢vv÷zzÿzzÿyyÿxxÿwwÿvvÿuuÿuuÿttÿssÿrrÿqqÿ ooÿ ooÿ ooÿ nnÿ mmÿ llÿllÿkkÿllÿllÿllÿllÿmmÿnnÿooÿrrÿttÿvvÿ{{ÿÿ……ÿ‹‹ÿ’’ÿ™™ÿ  ÿ§§ÿ°°ÿ»ºÿ ÅÄÿÎÍÿÖÕÿÛÚÿ âáóôóÕüüÉüüÌüüÏüüÐüüÐüüÎüüÌôòÛ æåöàßÿÝÜÿØ×ÿÓÒÿÍÌÿÇÆÿÂÁÿ¾½ÿ¸¸ÿ³³ÿ®®ÿ©©ÿ¥¥ÿ  ÿ››ÿ˜˜ÿ””ÿÿÿ‹‹ÿˆˆÿ……ÿƒƒÿÿÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿyyÿyyÿxxÿxxÿxxÿxxÿwwÿwwÿvvÿvvÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ vvÿ uuÿ uuÿ ttÿ ssÿ ppÿ qqÿ rrÿ qqÿ qqÿ qqÿqqÿqqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ ssÿ ssÿttÿuuÿuuÿvvÿwwÿxxÿyyÿyyÿttÿppÿppÿqqÿ!ssÿ$uuÿ'wwÿ*yyÿ-{{ÿ0}}ÿ3ÿ5ÿ9ƒƒÿ=††ÿ?ˆˆÿC‰‰ÿE‹‹ÿGŒŒÿIŽŽÿLÿJú.{{® bb'llWrrÚzzýzzÿyyÿxxÿwwÿwwÿvvÿuuÿttÿssÿssÿrrÿqqÿ ppÿ ooÿ ooÿ mmÿ mmÿllÿllÿkkÿkkÿkkÿkkÿllÿmmÿnnÿooÿppÿrrÿttÿxxÿ||ÿÿ††ÿŒŒÿ““ÿ™™ÿ¡¡ÿ««ÿµµÿ ½½ÿ ÆÅÿÏÍÿ×ÕÿÛÚÿ ãâóôóÖüûÊüüÍüüÐüüÑüüÑüüÏüüÎõôÜ æå÷âáÿÞÞÿÙÙÿÔÓÿ ÎÍÿ ÉÇÿÄÃÿ¿¿ÿº¹ÿ´´ÿ¯¯ÿ««ÿ¦¦ÿ¡¡ÿÿ™™ÿ••ÿ‘‘ÿŽŽÿ‹‹ÿ‰‰ÿ††ÿ„„ÿÿ€€ÿÿ~~ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿxxÿxxÿwwÿwwÿvvÿvvÿvvÿvvÿvvÿvvÿ vvÿ vvÿ vvÿ vvÿ uuÿ uuÿ uuÿ ttÿ ssÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ qqÿ rrÿ ssÿ ttÿttÿuuÿvvÿvvÿwwÿxxÿxxÿyyÿxxÿttÿqqÿppÿ!ssÿ%vvÿ(xxÿ*yyÿ-{{ÿ0}}ÿ3ÿ5ÿ9ƒƒÿ=††ÿ@ˆˆÿC‰‰ÿE‹‹ÿGŒŒÿIŽŽÿLÿKŽŽý3||Æ ff<ll!oo«xxö||ÿzzÿyyÿxxÿwwÿwwÿvvÿuuÿttÿssÿssÿrrÿ qqÿ ppÿ ppÿ ooÿ nnÿ nnÿmmÿllÿllÿllÿkkÿkkÿllÿmmÿmmÿnnÿooÿppÿrrÿuuÿxxÿ||ÿÿ††ÿÿ••ÿžžÿ ¦¦ÿ®®ÿ¶¶ÿ ¾½ÿ ÇÆÿÏÎÿ×ÖÿÛÛÿ ãâôôôÖüûËüüÎüüÐüüÒüüÒüüÑüûÐôôÞ èçøãâÿßßÿÛÙÿÕÔÿ ÏÎÿ ÊÉÿÅÅÿÀÀÿ»»ÿ·¶ÿ²²ÿ­­ÿ¨¨ÿ££ÿžžÿššÿ––ÿ““ÿÿŒŒÿ‰‰ÿ‡‡ÿ„„ÿƒƒÿÿÿ}}ÿ||ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿxxÿxxÿwwÿvvÿvvÿuuÿvvÿvvÿ vvÿ vvÿ vvÿ uuÿ uuÿ uuÿ uuÿ uuÿ ttÿ ssÿ rrÿ qqÿ ppÿ qqÿ qqÿ qqÿ rrÿ rrÿ rrÿ qqÿ rrÿ rrÿ rrÿ ssÿttÿttÿuuÿuuÿvvÿwwÿxxÿxxÿyyÿyyÿyyÿttÿqqÿ!ssÿ%vvÿ'wwÿ*yyÿ-{{ÿ1~~ÿ3ÿ5ÿ9ƒƒÿ<……ÿ?‡‡ÿB‰‰ÿE‹‹ÿGŒŒÿIÿKÿLŽŽþ5Î eeFUU™™mmbuuâ||þ||ÿ{{ÿyyÿxxÿwwÿvvÿuuÿttÿssÿssÿrrÿqqÿ qqÿ ppÿ ppÿ ooÿ nnÿnnÿmmÿmmÿnnÿmmÿllÿllÿllÿllÿmmÿmmÿnnÿooÿppÿrrÿuuÿyyÿ}}ÿ‚‚ÿŠŠÿ ““ÿ ššÿ   ÿ §§ÿ¯¯ÿ·¶ÿ ¿¾ÿ ÇÆÿÐÏÿ×ÖÿÜÛÿ ääóôôÖüüËüüÎüüÑüüÓüüÓüüÒüüÑõõß éèøäãÿàßÿÛÚÿÖÕÿ ÑÐÿ ÌËÿÇÆÿÂÁÿ½½ÿ¸¸ÿ³³ÿ®®ÿ©©ÿ¤¤ÿŸŸÿœœÿ˜˜ÿ““ÿÿÿŠŠÿ‡‡ÿ††ÿ„„ÿÿÿ~~ÿ}}ÿ||ÿ{{ÿzzÿzzÿyyÿyyÿxxÿwwÿwwÿvvÿvvÿvvÿvvÿvvÿ vvÿ uuÿ uuÿ uuÿ uuÿ uuÿ uuÿ ssÿ ssÿ ssÿ qqÿ ppÿ ppÿ qqÿ qqÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿttÿuuÿuuÿuuÿvvÿxxÿxxÿxxÿyyÿzzÿ||ÿyyÿttÿ ssÿ$uuÿ'wwÿ*yyÿ.||ÿ1~~ÿ3ÿ6ÿ:„„ÿ<……ÿ?ˆˆÿB‰‰ÿE‹‹ÿGŒŒÿIŽŽÿLÿLý4~~Ë aaDUUll!oo¬zz÷}}ÿ||ÿ{{ÿzzÿyyÿwwÿvvÿuuÿttÿssÿssÿrrÿqqÿ qqÿ ppÿ ooÿ nnÿ nnÿnnÿmmÿmmÿmmÿnnÿmmÿllÿllÿmmÿllÿmmÿmmÿnnÿooÿppÿssÿvvÿzzÿ€€ÿ ‰‰ÿ ÿ ””ÿ ššÿ ¡¡ÿ ¨¨ÿ°°ÿ ¸¸ÿ À¿ÿ ÈÇÿÑÏÿØ×ÿÝÜÿ ääóõõÖýüËüüÏüüÒüüÔüüÔûûÔüüÓôôá éèøåäÿáàÿÜÛÿ ××ÿ ÒÑÿ ÍÌÿ ÈÇÿÄÃÿ¿¾ÿ¹¹ÿ´´ÿ¯¯ÿªªÿ¥¥ÿ  ÿœœÿ˜˜ÿ””ÿ‘‘ÿŽŽÿ‹‹ÿ‰‰ÿ††ÿƒƒÿÿ€€ÿ~~ÿ}}ÿ||ÿzzÿ{{ÿzzÿyyÿyyÿxxÿwwÿwwÿvvÿvvÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ uuÿ uuÿ uuÿ ssÿ ssÿ ssÿ qqÿ ppÿ ppÿ qqÿ qqÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ ssÿttÿuuÿuuÿuuÿvvÿxxÿyyÿyyÿzzÿ{{ÿ{{ÿ||ÿ yyÿ!ttÿ$uuÿ'wwÿ*zzÿ.||ÿ1~~ÿ3ÿ6ÿ:„„ÿ<……ÿ?‡‡ÿB‰‰ÿE‹‹ÿGŒŒÿIŽŽÿLÿLý2~~É bbAUUll\uuà}}þ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿuuÿttÿssÿrrÿrrÿqqÿ qqÿ ppÿ ooÿ nnÿ nnÿnnÿnnÿnnÿnnÿnnÿmmÿmmÿllÿmmÿllÿllÿllÿmmÿnnÿooÿqqÿttÿyyÿ ÿ ††ÿ ŠŠÿ ÿ ••ÿ œœÿ ££ÿ ©©ÿ ±±ÿ ¹¸ÿ ÁÀÿ ÉÇÿÑÏÿÙØÿÝÝÿ äãôõô×üûÍüüÐüüÓüüÕûûÖüüÔüüÓôôâ éèùæåÿâáÿÞÝÿ ÙØÿ ÔÒÿ ÎÍÿ ÊÉÿÅÄÿÀ¿ÿººÿµµÿ°°ÿ««ÿ¦¦ÿ¢¢ÿžžÿššÿ••ÿ’’ÿŽŽÿ‹‹ÿ‰‰ÿ††ÿƒƒÿ‚‚ÿ€€ÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿyyÿwwÿwwÿwwÿvvÿuuÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ uuÿ uuÿ uuÿ uuÿ ssÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ ssÿ ssÿ rrÿ rrÿ ssÿ ssÿttÿuuÿvvÿvvÿvvÿwwÿyyÿzzÿ{{ÿzzÿ{{ÿ||ÿ!||ÿ"xxÿ$uuÿ'wwÿ*zzÿ.||ÿ1~~ÿ4€€ÿ7‚‚ÿ:„„ÿ=††ÿ@ˆˆÿB‰‰ÿE‹‹ÿGŒŒÿJŽŽÿLÿKý0}}Ç dd=qqoo—zzõ ~~ÿ}}ÿ||ÿzzÿzzÿyyÿxxÿwwÿvvÿttÿttÿrrÿrrÿ qqÿ qqÿ ppÿ ooÿ nnÿnnÿooÿnnÿnnÿnnÿnnÿmmÿmmÿmmÿmmÿmmÿllÿllÿllÿmmÿnnÿooÿttÿ {{ÿÿ ‚‚ÿ ††ÿ ‹‹ÿ ‘‘ÿ ——ÿ ÿ ££ÿ ««ÿ ²²ÿ ¹¹ÿ ÁÀÿ ÉÈÿÒÑÿÙØÿÞÝÿ ääôôôØüûÎüüÑüüÔûûÖûûÖüüÕüûÕôôã êéù æåÿãâÿ ßÞÿ ÙÙÿ ÔÓÿ ÐÏÿ ËÊÿÆÅÿÁÀÿ¼¼ÿ¶¶ÿ±±ÿ¬¬ÿ¨¨ÿ££ÿŸŸÿššÿ––ÿ’’ÿÿŒŒÿ‰‰ÿ††ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿwwÿwwÿwwÿvvÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ uuÿ uuÿ uuÿ uuÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ ssÿ ssÿ ssÿ rrÿ ssÿ ssÿttÿuuÿvvÿvvÿwwÿxxÿyyÿzzÿ{{ÿ{{ÿ||ÿ||ÿ!}}ÿ"||ÿ%xxÿ'wwÿ*yyÿ.||ÿ1~~ÿ4€€ÿ7‚‚ÿ:„„ÿ=††ÿ@ˆˆÿC‰‰ÿE‹‹ÿGŒŒÿJŽŽÿLÿLü/||à dd8ii?ttÌ}}ü~~ÿ}}ÿ||ÿzzÿzzÿyyÿxxÿwwÿvvÿuuÿttÿrrÿrrÿ qqÿ ppÿ ppÿ ooÿooÿooÿnnÿooÿooÿnnÿnnÿmmÿmmÿmmÿmmÿmmÿllÿllÿllÿllÿmmÿqqÿ wwÿzzÿ||ÿ ÿ ƒƒÿ ‡‡ÿ ŒŒÿ ‘‘ÿ ——ÿ žžÿ ¥¥ÿ ¬¬ÿ ³³ÿ ººÿ ÂÁÿ ËÊÿÓÒÿÚÙÿßÞÿ åäôõôØüüÏüüÒüüÕûû×ûû×ûû×ûû×öôä ëêù ææÿããÿ ßÞÿ ÚÚÿ ÖÕÿ ÑÐÿ ÌËÿ ÇÆÿÂÁÿ½¼ÿ··ÿ²²ÿ®®ÿ©©ÿ¤¤ÿŸŸÿ››ÿ——ÿ““ÿÿŒŒÿ‰‰ÿ††ÿ„„ÿ‚‚ÿ€€ÿÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿxxÿwwÿvvÿuuÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ uuÿ uuÿ ttÿ ssÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿttÿuuÿuuÿvvÿwwÿxxÿyyÿzzÿzzÿ{{ÿ}}ÿ }}ÿ"}}ÿ#~~ÿ%}}ÿ(yyÿ+zzÿ.||ÿ1~~ÿ4€€ÿ6‚‚ÿ:„„ÿ=††ÿ@ˆˆÿCŠŠÿE‹‹ÿHÿJŽŽÿLÿKü,zz¿ dd3ÿÿjj mmƒxxï"ÿ ~~ÿ}}ÿ||ÿzzÿzzÿyyÿxxÿwwÿvvÿuuÿttÿssÿrrÿ qqÿ ppÿ ppÿ ppÿppÿppÿppÿooÿnnÿnnÿnnÿmmÿmmÿmmÿmmÿllÿllÿllÿllÿllÿnnÿ ttÿwwÿxxÿzzÿ||ÿÿ ƒƒÿ ˆˆÿ ÿ ““ÿ ™™ÿ ŸŸÿ ¦¦ÿ ­­ÿ ´´ÿ ¼»ÿ ÄÃÿ ÌËÿÔÓÿÛÚÿàßÿ æåóôôÙüüÐûûÔûû×ûûØûûÙûûØûûØ÷öä ëêù çæÿääÿ àßÿ ÜÛÿ ×Öÿ ÒÑÿ ÍÌÿ ÈÇÿÃÂÿ¾½ÿ¹¸ÿ³³ÿ®®ÿ©©ÿ¤¤ÿ  ÿœœÿ˜˜ÿ””ÿÿŒŒÿ‰‰ÿ‡‡ÿ……ÿƒƒÿÿÿ}}ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿwwÿwwÿvvÿuuÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ uuÿ uuÿ ttÿ ssÿ ssÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ rrÿ ssÿ ssÿ ttÿ ssÿttÿuuÿvvÿvvÿwwÿxxÿyyÿzzÿ{{ÿ{{ÿ||ÿ }}ÿ"~~ÿ#~~ÿ%€€ÿ(}}ÿ+zzÿ.||ÿ1~~ÿ4€€ÿ7‚‚ÿ:„„ÿ=††ÿ@ˆˆÿCŠŠÿE‹‹ÿHŒŒÿJŽŽÿLÿLŽŽû,xx¸ ``-ii. qq¿||û"ÿ }}ÿ}}ÿ||ÿzzÿyyÿyyÿxxÿwwÿvvÿuuÿttÿssÿ rrÿ qqÿ ppÿ ppÿ qqÿ ppÿppÿppÿooÿooÿnnÿnnÿmmÿmmÿmmÿllÿllÿllÿllÿllÿnnÿ rrÿttÿuuÿwwÿxxÿzzÿ||ÿ€€ÿ„„ÿˆˆÿ ŽŽÿ ““ÿ ™™ÿ   ÿ §§ÿ ®®ÿ µµÿ½¼ÿ ÅÄÿ ÍËÿÔÓÿÛÛÿààÿ ææóõõÙüüÑûûÕûûØûûÚûûÚûûÙûûØöôå ìëù èçÿ åäÿ áàÿ ÜÜÿ Ø×ÿ ÓÒÿ ÎÍÿ ÉÈÿÃÂÿ¿¾ÿ¹¹ÿ´´ÿ¯¯ÿªªÿ¦¦ÿ¡¡ÿÿ˜˜ÿ””ÿÿÿŠŠÿ‡‡ÿ„„ÿ‚‚ÿÿÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿwwÿwwÿvvÿuuÿuuÿuuÿ ttÿuuÿ uuÿ uuÿ uuÿ ttÿ rrÿ rrÿ qqÿ qqÿ rrÿ qqÿ rrÿ rrÿ rrÿ ssÿ ssÿttÿttÿuuÿuuÿvvÿwwÿxxÿyyÿzzÿzzÿ{{ÿ||ÿ}}ÿ!~~ÿ"~~ÿ$ÿ%€€ÿ(€€ÿ+}}ÿ.||ÿ1~~ÿ4€€ÿ7‚‚ÿ:„„ÿ=††ÿ@ˆˆÿCŠŠÿE‹‹ÿGŒŒÿJŽŽÿLÿKŽŽú.zz¯bb'mmlllwwæ"þ!ÿ ~~ÿ||ÿ||ÿzzÿyyÿyyÿxxÿwwÿvvÿuuÿttÿttÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿppÿooÿnnÿooÿnnÿnnÿmmÿmmÿmmÿmmÿllÿllÿllÿnnÿ rrÿttÿuuÿuuÿvvÿwwÿyyÿ{{ÿ~~ÿÿ„„ÿ‰‰ÿŽŽÿ ””ÿ ššÿ ¡¡ÿ ¨¨ÿ ¯¯ÿ ·¶ÿ ¾¾ÿ ÆÄÿ ÎÍÿÖÕÿÝÜÿáàÿ ççóõõÚûûÓüüÖûûÙûûÛûûÛûûÚûúÚööå íìù éèÿ ååÿ ááÿ ÝÜÿ Ø×ÿ ÓÒÿ ÎÍÿ ÉÈÿÄÃÿÀ¿ÿ»ºÿ¶¶ÿ°°ÿ««ÿ¦¦ÿ¢¢ÿÿ™™ÿ””ÿÿÿ‰‰ÿ††ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ}}ÿ||ÿzzÿyyÿxxÿxxÿwwÿwwÿwwÿvvÿuuÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ uuÿ ssÿ rrÿ ssÿ rrÿ rrÿ qqÿ qqÿ rrÿ rrÿ ssÿ ssÿttÿttÿuuÿuuÿvvÿwwÿxxÿyyÿzzÿzzÿ{{ÿ}}ÿ}}ÿ!~~ÿ#~~ÿ%ÿ&€€ÿ)‚‚ÿ,ÿ/~~ÿ2~~ÿ4€€ÿ7‚‚ÿ:„„ÿ=††ÿAˆˆÿB‰‰ÿE‹‹ÿHŒŒÿJŽŽÿLÿKŽŽü0||¾ ee2ÿÿnn rr¬}}ø#€€ÿ!ÿ ~~ÿ||ÿ{{ÿzzÿzzÿyyÿxxÿwwÿvvÿuuÿttÿttÿ ssÿ rrÿ qqÿ qqÿqqÿqqÿppÿppÿooÿooÿooÿnnÿmmÿmmÿmmÿmmÿllÿllÿllÿ rrÿttÿttÿuuÿvvÿvvÿwwÿxxÿyyÿ||ÿ~~ÿÿ……ÿŠŠÿÿ––ÿ œœÿ ¢¢ÿ ªªÿ ±±ÿ ¸¸ÿ ¿¾ÿ ÇÆÿ ÏÎÿ×ÖÿÝÝÿâáÿ èçóõõÜüüÔûû×ûûÚûûÜûûÜûûÛûûÚ÷öæ îíù ééÿ ææÿ ââÿ ÞÝÿ ÙØÿ ÔÓÿ ÏÎÿ ÊÉÿ ÅÄÿÀÀÿ»»ÿ¶¶ÿ±±ÿ¬¬ÿ§§ÿ¢¢ÿÿ™™ÿ••ÿÿÿ‰‰ÿ††ÿ„„ÿ‚‚ÿ€€ÿÿ}}ÿ{{ÿzzÿyyÿyyÿxxÿwwÿwwÿwwÿvvÿuuÿuuÿuuÿuuÿ uuÿ uuÿ uuÿ ttÿ ssÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿttÿttÿuuÿvvÿvvÿwwÿxxÿyyÿzzÿzzÿ{{ÿ||ÿ }}ÿ!~~ÿ$ÿ%€€ÿ'€€ÿ*‚‚ÿ-„„ÿ/ÿ2ÿ5€€ÿ7‚‚ÿ;„„ÿ=††ÿAˆˆÿB‰‰ÿF‹‹ÿHÿJŽŽÿLÿLý0}}Á ee7ÿÿmmAvvÕ#~~ý#ÿ!ÿ ~~ÿ||ÿ{{ÿzzÿzzÿyyÿxxÿwwÿvvÿuuÿttÿttÿ ssÿ rrÿ qqÿ qqÿ qqÿqqÿppÿppÿooÿooÿooÿnnÿnnÿmmÿmmÿmmÿllÿllÿ ppÿttÿttÿuuÿuuÿvvÿvvÿvvÿwwÿxxÿzzÿ||ÿÿƒƒÿ‡‡ÿŒŒÿ’’ÿ˜˜ÿ žžÿ ¤¤ÿ ¬¬ÿ ³³ÿ º¹ÿ ÁÀÿ ÉÈÿ ÑÏÿØ×ÿßÞÿãâÿ ééôõõÜüüÔûûÙûûÜûûÝûûÝûûÜûûÜ÷÷æ îíøêêÿèçÿ äãÿ ßÞÿ ÚÙÿ ÕÔÿ ÐÏÿ ËÊÿ ÆÅÿÁÀÿ»»ÿ¶¶ÿ±±ÿ¬¬ÿ§§ÿ££ÿžžÿ™™ÿ””ÿÿÿ‰‰ÿ††ÿ„„ÿ‚‚ÿ€€ÿÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿwwÿwwÿvvÿvvÿuuÿuuÿuuÿ ttÿ uuÿ uuÿ ttÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿ rrÿ ssÿ ssÿssÿuuÿuuÿvvÿvvÿwwÿwwÿxxÿzzÿ{{ÿ||ÿ}}ÿ ~~ÿ"ÿ$€€ÿ&€€ÿ'€€ÿ*‚‚ÿ-„„ÿ0……ÿ3ÿ5ÿ8‚‚ÿ;……ÿ=‡‡ÿAˆˆÿC‰‰ÿF‹‹ÿHÿJŽŽÿLÿLû-{{´ bb,mmmmmzzê%€€ÿ#ÿ!ÿ ~~ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿwwÿvvÿuuÿttÿttÿ ssÿ rrÿ rrÿ qqÿ qqÿppÿppÿppÿooÿooÿooÿnnÿnnÿmmÿmmÿmmÿmmÿppÿttÿttÿttÿuuÿuuÿuuÿuuÿvvÿvvÿwwÿyyÿ{{ÿ}}ÿ€€ÿƒƒÿˆˆÿÿ““ÿ™™ÿ   ÿ §§ÿ ®®ÿ µµÿ ¼»ÿ ÃÂÿ ËÊÿ ÒÑÿÙÙÿàßÿääÿ êéôöõÝüüÖûûÚûûÝûûÞûûÞûûÝûûÜ÷÷å îîùìëÿéèÿåäÿààÿÜÛÿ×ÖÿÒÐÿÍÌÿÇÆÿÁÀÿ¼¼ÿ¶¶ÿ±±ÿ««ÿ§§ÿ££ÿžžÿ˜˜ÿ””ÿÿÿŠŠÿ‡‡ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿwwÿwwÿwwÿvvÿvvÿuuÿ ttÿ ttÿ uuÿ ttÿ ttÿ rrÿ rrÿ qqÿ rrÿ rrÿ rrÿ rrÿssÿ ssÿssÿttÿuuÿuuÿvvÿwwÿwwÿxxÿzzÿ{{ÿ||ÿ}}ÿ!~~ÿ#ÿ%€€ÿ'€€ÿ'€€ÿ*‚‚ÿ.„„ÿ1††ÿ3……ÿ6‚‚ÿ8ƒƒÿ;……ÿ>‡‡ÿAˆˆÿC‰‰ÿF‹‹ÿHÿKŽŽÿLÿJŽŽù+xx¥__ dd ss¤"~~÷&ÿ#€€ÿ"ÿ ~~ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿwwÿvvÿuuÿttÿttÿ ssÿ ssÿ rrÿ qqÿqqÿppÿppÿppÿooÿooÿooÿnnÿnnÿnnÿmmÿmmÿooÿssÿuuÿuuÿuuÿuuÿuuÿvvÿuuÿvvÿvvÿwwÿxxÿzzÿ||ÿÿƒƒÿ……ÿŠŠÿÿ••ÿ››ÿ ¢¢ÿ ©©ÿ °°ÿ ¶¶ÿ ½½ÿ ÅÄÿ ÌËÿ ÓÒÿÛÚÿáàÿåäÿ ëêõöõßüüØûûÜûûÞûûßûûßûûÞûûÜ÷÷æ ïïùìëÿééÿæåÿáàÿÝÜÿØ×ÿÓÓÿÏÎÿÊÈÿÄÃÿ¾¾ÿ¹¹ÿ²²ÿ­­ÿ¨¨ÿ¢¢ÿÿ™™ÿ••ÿ‘‘ÿÿŠŠÿ‡‡ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ||ÿ{{ÿzzÿyyÿwwÿwwÿwwÿwwÿwwÿvvÿuuÿ ttÿ ttÿuuÿ uuÿ ttÿ rrÿ qqÿ qqÿ qqÿ rrÿ rrÿrrÿrrÿssÿssÿttÿuuÿuuÿvvÿwwÿwwÿyyÿzzÿ{{ÿ||ÿ ~~ÿ!ÿ#ÿ%€€ÿ(ÿ(ÿ*‚‚ÿ.……ÿ1††ÿ3‡‡ÿ6……ÿ8ƒƒÿ<……ÿ?‡‡ÿAˆˆÿCŠŠÿF‹‹ÿHÿKŽŽÿMÿG‹‹ö"tt“ ccmmFuuÕ&‚‚ý&ÿ#€€ÿ"ÿ~~ÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿwwÿuuÿuuÿuuÿttÿ ssÿ ssÿ rrÿ qqÿppÿppÿppÿppÿooÿooÿooÿnnÿnnÿnnÿmmÿnnÿ ttÿuuÿuuÿuuÿuuÿuuÿvvÿvvÿvvÿvvÿvvÿwwÿxxÿyyÿ{{ÿ~~ÿ€€ÿ„„ÿ‡‡ÿ‹‹ÿ‘‘ÿ——ÿÿ¤¤ÿ ««ÿ ±±ÿ ¸¸ÿ ¿¿ÿ ÆÅÿ ÍÌÿ ÕÔÿ ÜÛÿâáÿæåÿ ëêö÷÷àûûÚûûÝûûßûúàûúàûûÞûûÝ÷÷æ ïïùìëÿêéÿæåÿáàÿÝÜÿØ×ÿÔÓÿÐÎÿËÊÿÅÅÿÀÀÿ»»ÿµµÿ®®ÿ§§ÿ¢¢ÿžžÿ™™ÿ••ÿÿÿ‰‰ÿ††ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ||ÿ{{ÿzzÿxxÿwwÿwwÿwwÿwwÿwwÿvvÿuuÿuuÿuuÿ uuÿ ttÿ ssÿ ssÿ qqÿ qqÿrrÿ rrÿrrÿssÿssÿssÿttÿuuÿvvÿuuÿwwÿxxÿyyÿzzÿ{{ÿ||ÿ ~~ÿ"ÿ%€€ÿ&ÿ(‚‚ÿ)‚‚ÿ+ƒƒÿ.……ÿ1††ÿ4ˆˆÿ7ˆˆÿ9††ÿ<††ÿ?‡‡ÿA‰‰ÿCŠŠÿF‹‹ÿHÿKŽŽÿNÿB‰‰îmm~__ff oo|||î(ƒƒÿ&ÿ#€€ÿ"ÿ }}ÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿwwÿvvÿuuÿuuÿttÿ ssÿ ssÿ rrÿ qqÿqqÿppÿppÿppÿooÿooÿooÿnnÿnnÿnnÿnnÿ rrÿvvÿuuÿvvÿvvÿvvÿuuÿvvÿvvÿvvÿuuÿvvÿwwÿxxÿyyÿzzÿ~~ÿ€€ÿƒƒÿ††ÿ‰‰ÿŽŽÿ””ÿ™™ÿ  ÿ¦¦ÿ ­­ÿ ³³ÿ ººÿ ÁÀÿ ÈÇÿ ÏÎÿ ÖÕÿ ÝÜÿãâÿ çæÿ ìëö÷÷áúúÛûúÞûûßûúàûûàûûßúúÞ÷÷ç îîúìëÿêéÿæåÿáàÿÜÜÿØ×ÿÔÓÿÏÎÿËÉÿÅÄÿÁÀÿ»»ÿ¶¶ÿ¯¯ÿ©©ÿ££ÿÿ˜˜ÿ””ÿÿÿ‰‰ÿ††ÿ„„ÿÿÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿxxÿwwÿvvÿvvÿvvÿvvÿuuÿ ttÿ uuÿuuÿrrÿ qqÿ qqÿqqÿqqÿrrÿssÿssÿttÿttÿuuÿvvÿvvÿwwÿxxÿyyÿzzÿ{{ÿ||ÿ!~~ÿ#ÿ%€€ÿ'ÿ)‚‚ÿ)‚‚ÿ+ƒƒÿ/……ÿ1††ÿ4ˆˆÿ7‰‰ÿ:ˆˆÿ=††ÿ@‡‡ÿBˆˆÿD‰‰ÿF‹‹ÿIÿKŽŽÿNÿB‰‰ælllqq jj$tt±%ù(ƒƒÿ&ÿ$€€ÿ"€€ÿ ~~ÿ}}ÿ||ÿzzÿyyÿxxÿxxÿwwÿvvÿvvÿuuÿttÿ ssÿ ssÿ rrÿ qqÿqqÿppÿppÿooÿooÿooÿnnÿnnÿnnÿnnÿ qqÿuuÿvvÿvvÿvvÿvvÿuuÿvvÿvvÿvvÿvvÿvvÿvvÿwwÿyyÿyyÿzzÿ||ÿ~~ÿÿ„„ÿ‡‡ÿŒŒÿÿ––ÿ››ÿ¢¢ÿ¨¨ÿ ¯¯ÿ ¶¶ÿ ¼¼ÿ ÄÂÿ ÊÉÿ ÑÏÿ Ø×ÿ ÞÞÿ äãÿ ççÿ íìöø÷âûûÜûûÞûúàûúáûúáûûàûûÞ÷÷è ïîúìëÿééÿæåÿááÿÝÜÿØ×ÿÔÓÿÏÎÿÊÉÿÆÄÿÁÀÿ»»ÿ¶¶ÿ°°ÿ««ÿ¥¥ÿžžÿ˜˜ÿ““ÿÿŒŒÿ‰‰ÿ††ÿƒƒÿÿ~~ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿwwÿwwÿvvÿvvÿvvÿuuÿuuÿuuÿttÿttÿrrÿqqÿqqÿrrÿrrÿrrÿssÿttÿuuÿuuÿvvÿwwÿwwÿxxÿyyÿzzÿ{{ÿ||ÿ!~~ÿ#€€ÿ&€€ÿ(ÿ*ÿ*ƒƒÿ,ƒƒÿ/……ÿ2††ÿ4ˆˆÿ7‰‰ÿ:‹‹ÿ=‰‰ÿ@ˆˆÿB‰‰ÿEŠŠÿGŒŒÿIÿLŽŽÿMþ;ƒƒ×iiUffllKyyÙ)‚‚þ)ƒƒÿ&ÿ$€€ÿ#€€ÿ!~~ÿ}}ÿ||ÿzzÿyyÿxxÿxxÿwwÿwwÿvvÿuuÿuuÿ ssÿ ssÿ rrÿqqÿqqÿppÿppÿooÿooÿooÿnnÿnnÿnnÿooÿvvÿvvÿvvÿvvÿvvÿvvÿvvÿvvÿvvÿvvÿvvÿwwÿwwÿxxÿyyÿyyÿzzÿ||ÿ~~ÿ€€ÿƒƒÿ……ÿˆˆÿÿ’’ÿ——ÿÿ££ÿªªÿ ±±ÿ ¸·ÿ ¾¾ÿ ÅÄÿ ËÊÿ ÒÑÿ ÙØÿ ßÞÿ ääÿ èçÿ îíö÷÷ãûûÜûûßûúáûúâûúâûûàúúà÷öé ïîúìëÿêéÿæåÿâáÿÝÜÿØ×ÿÔÓÿÏÎÿËÉÿÆÅÿÀÀÿ»»ÿ¶¶ÿ±±ÿ¬¬ÿ¦¦ÿ  ÿššÿ””ÿÿŒŒÿˆˆÿ……ÿƒƒÿ€€ÿ~~ÿ}}ÿ{{ÿ{{ÿyyÿxxÿwwÿwwÿwwÿvvÿvvÿvvÿuuÿttÿssÿttÿrrÿqqÿqqÿrrÿrrÿrrÿssÿttÿuuÿuuÿvvÿwwÿwwÿxxÿyyÿzzÿ{{ÿ}}ÿ!~~ÿ#€€ÿ&ÿ)‚‚ÿ*‚‚ÿ+ƒƒÿ-„„ÿ0……ÿ2‡‡ÿ5ˆˆÿ8ŠŠÿ;ŒŒÿ>‹‹ÿAˆˆÿC‰‰ÿEŠŠÿHŒŒÿJÿLÿLý2}}Ë ``BUUff nnl ~~ê+„„ÿ)ƒƒÿ'‚‚ÿ%€€ÿ#ÿ!~~ÿ}}ÿ||ÿ{{ÿyyÿxxÿyyÿwwÿwwÿvvÿuuÿuuÿ ttÿ ssÿ rrÿ qqÿqqÿppÿppÿooÿooÿooÿnnÿnnÿnnÿ ttÿwwÿvvÿwwÿwwÿwwÿwwÿvvÿvvÿvvÿvvÿvvÿvvÿwwÿyyÿzzÿzzÿ{{ÿ||ÿ}}ÿÿÿƒƒÿ††ÿŠŠÿÿ””ÿ™™ÿŸŸÿ¥¥ÿ¬¬ÿ ³³ÿ ¹¹ÿ À¿ÿ ÆÅÿ ÍÌÿ ÔÓÿ ÚÙÿ àßÿ ååÿ éèÿîíöø÷ãûûÝûûßûúáûúâûúâûúáûûà÷öé ïîúììÿêéÿæåÿááÿÝÜÿÙØÿÔÓÿÐÏÿËÊÿÅÅÿÀÀÿ»»ÿ¶¶ÿ±±ÿ««ÿ§§ÿ¡¡ÿ››ÿ••ÿÿ‹‹ÿˆˆÿ……ÿ‚‚ÿ€€ÿ~~ÿ||ÿ{{ÿzzÿyyÿxxÿxxÿwwÿvvÿvvÿvvÿuuÿuuÿttÿttÿrrÿrrÿrrÿssÿrrÿrrÿssÿttÿuuÿvvÿwwÿwwÿxxÿxxÿzzÿ{{ÿ||ÿ }}ÿ"ÿ$€€ÿ'ÿ)‚‚ÿ+‚‚ÿ,‚‚ÿ.„„ÿ0……ÿ3‡‡ÿ6‰‰ÿ8ŠŠÿ<ŒŒÿ?ÿA‹‹ÿC‰‰ÿF‹‹ÿIÿKŽŽÿMÿNþ4~~ËbbAUUff rrŠ$ó+„„ÿ)ƒƒÿ'‚‚ÿ%ÿ#€€ÿ ~~ÿ}}ÿ||ÿ{{ÿzzÿxxÿxxÿwwÿwwÿvvÿvvÿuuÿ ttÿ ssÿ rrÿ qqÿqqÿppÿppÿppÿooÿooÿnnÿnnÿ rrÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿvvÿvvÿvvÿxxÿyyÿzzÿ{{ÿ{{ÿ{{ÿ{{ÿ||ÿ}}ÿ~~ÿ€€ÿ‚‚ÿ……ÿˆˆÿÿ‘‘ÿ––ÿ››ÿ¡¡ÿ¨¨ÿ®®ÿ ´´ÿ »ºÿ ÂÁÿ ÈÇÿ ÏÍÿ ÕÔÿ ÛÚÿ áàÿ æåÿ éèÿ îíö÷÷ãûûÝûûßûúáûúâûúãûúâûûà÷öé ïîúìëÿêéÿæåÿááÿÝÜÿØØÿÔÓÿÏÎÿËÉÿÅÄÿÀÀÿ»»ÿ¶¶ÿ°°ÿ««ÿ¦¦ÿ  ÿœœÿ——ÿ’’ÿŒŒÿ‡‡ÿ„„ÿ‚‚ÿ€€ÿ~~ÿ||ÿ{{ÿyyÿxxÿxxÿwwÿwwÿvvÿvvÿvvÿuuÿttÿttÿssÿssÿssÿssÿrrÿssÿssÿttÿuuÿvvÿwwÿxxÿxxÿyyÿzzÿ{{ÿ||ÿ }}ÿ#ÿ%€€ÿ'‚‚ÿ)ƒƒÿ+ƒƒÿ,ƒƒÿ.„„ÿ1……ÿ3‡‡ÿ7‰‰ÿ9‹‹ÿ=ÿ@ŽŽÿBŽŽÿD‹‹ÿG‹‹ÿIÿKŽŽÿMÿMü3}}½ dd8hh'vvµ*ƒƒû+……ÿ)ƒƒÿ&‚‚ÿ$ÿ#ÿ ~~ÿ}}ÿ||ÿ{{ÿzzÿxxÿxxÿxxÿxxÿwwÿvvÿuuÿ ttÿ ttÿ ssÿ qqÿppÿppÿppÿppÿooÿooÿnnÿppÿvvÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿxxÿyyÿ{{ÿ{{ÿ{{ÿ{{ÿ{{ÿ||ÿ{{ÿ}}ÿ~~ÿÿÿƒƒÿ‡‡ÿŠŠÿŽŽÿ““ÿ˜˜ÿÿ¤¤ÿªªÿ°°ÿ ·¶ÿ ½¼ÿ ÃÂÿ ÊÈÿ ÐÏÿ ÖÕÿ ÜÛÿ ááÿ ææÿ éèÿ îíöøøãûûÝûûßûúâûúãûúãûúâûûàø÷è ïîú íìÿêéÿæåÿâáÿÝÝÿÙØÿÔÓÿÏÎÿÊÉÿÅÄÿÀÀÿ»»ÿµµÿ¯¯ÿ««ÿ¦¦ÿ  ÿœœÿ˜˜ÿ““ÿÿ‡‡ÿ„„ÿÿÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿwwÿvvÿvvÿvvÿvvÿuuÿssÿssÿrrÿssÿssÿssÿssÿttÿttÿuuÿvvÿwwÿxxÿyyÿyyÿzzÿ{{ÿ||ÿ!}}ÿ#ÿ&€€ÿ(‚‚ÿ*ƒƒÿ,„„ÿ.ƒƒÿ/„„ÿ2††ÿ4‡‡ÿ7ŠŠÿ:‹‹ÿ=ÿAÿBÿEÿHÿIÿKŽŽÿMÿLŽŽú+yyªcc$ooI||×-……þ,……ÿ*„„ÿ'‚‚ÿ%ÿ#€€ÿ ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿxxÿxxÿxxÿwwÿvvÿuuÿ ttÿ ttÿ ssÿ rrÿqqÿppÿppÿooÿooÿooÿnnÿ ssÿxxÿwwÿwwÿwwÿwwÿwwÿxxÿxxÿxxÿyyÿyyÿzzÿzzÿ{{ÿ{{ÿ{{ÿ{{ÿ{{ÿ||ÿ||ÿ||ÿ}}ÿÿ€€ÿ‚‚ÿ……ÿˆˆÿ‹‹ÿÿ””ÿššÿŸŸÿ¥¥ÿ««ÿ²²ÿ ¸¸ÿ ¾½ÿ ÅÃÿ ËÊÿ ÑÐÿ ×Öÿ ÜÜÿ âáÿ çæÿ ééÿ îíö÷÷äúúÞûûàûúãûúãûúãûûáûûàööé ïïù ìëÿêéÿæåÿâáÿÝÝÿÙØÿÔÓÿÏÎÿÊÉÿÅÄÿ¿¿ÿººÿ´´ÿ¯¯ÿªªÿ¥¥ÿ  ÿœœÿ˜˜ÿ””ÿŽŽÿˆˆÿ„„ÿÿÿ}}ÿ{{ÿyyÿyyÿxxÿwwÿvvÿvvÿvvÿvvÿuuÿttÿssÿssÿssÿssÿssÿttÿssÿttÿuuÿvvÿwwÿxxÿyyÿzzÿzzÿ{{ÿ }}ÿ!~~ÿ#ÿ&ÿ(‚‚ÿ*ƒƒÿ,„„ÿ.„„ÿ0„„ÿ3††ÿ6ˆˆÿ8ŠŠÿ;ŒŒÿ>ÿAÿCÿE‘‘ÿHÿJŽŽÿLŽŽÿNÿG‹‹ò pp \\qq rrr&ê/‡‡ÿ-……ÿ*„„ÿ(‚‚ÿ%ÿ#€€ÿ ~~ÿ}}ÿ||ÿ||ÿ{{ÿ{{ÿyyÿxxÿxxÿwwÿuuÿuuÿ uuÿ ttÿ ssÿ rrÿqqÿppÿppÿooÿooÿooÿppÿvvÿxxÿxxÿxxÿxxÿwwÿxxÿxxÿxxÿyyÿ{{ÿzzÿzzÿ{{ÿ{{ÿ{{ÿ{{ÿ||ÿ||ÿ||ÿ}}ÿ}}ÿ}}ÿ~~ÿÿÿƒƒÿ††ÿ‰‰ÿÿ‘‘ÿ––ÿ››ÿ¡¡ÿ§§ÿ­­ÿ³³ÿ ¹¸ÿ ¿¾ÿ ÅÄÿ ÌËÿ ÑÑÿ ×Öÿ ÝÜÿ ãâÿ çæÿ êéÿ îí÷øøäûúßûúáûúãûúãûúâûúáúùá÷÷è ïîù ììÿêéÿæåÿâáÿÞÝÿÙØÿÔÓÿÏÎÿÊÉÿÄÄÿ¿¿ÿ¹¹ÿ´´ÿ¯¯ÿªªÿ¤¤ÿ  ÿœœÿ——ÿ““ÿŽŽÿŠŠÿƒƒÿ€€ÿ}}ÿ||ÿzzÿyyÿxxÿwwÿwwÿwwÿvvÿvvÿuuÿttÿrrÿssÿssÿssÿssÿttÿttÿuuÿuuÿvvÿwwÿxxÿyyÿzzÿ{{ÿ||ÿ }}ÿ"ÿ%€€ÿ'ÿ)‚‚ÿ*ƒƒÿ-……ÿ/……ÿ0„„ÿ3††ÿ6ˆˆÿ9ŠŠÿ<ŒŒÿ?ŽŽÿAÿC‘‘ÿF’’ÿH’’ÿJÿLÿO‘‘ÿBˆˆåkkmff mmtt›+„„÷.‡‡ÿ,……ÿ*„„ÿ(ƒƒÿ&‚‚ÿ"€€ÿ!ÿ}}ÿ}}ÿ||ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿuuÿ uuÿ ttÿ ssÿrrÿqqÿppÿppÿooÿooÿooÿttÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿyyÿzzÿzzÿ||ÿ||ÿ{{ÿ||ÿ||ÿ||ÿ{{ÿ||ÿ||ÿ||ÿ||ÿ}}ÿ~~ÿ~~ÿÿÿ‚‚ÿ……ÿ‡‡ÿ‹‹ÿÿ““ÿ˜˜ÿÿ££ÿ©©ÿ¯¯ÿµµÿ »»ÿ ÁÀÿ ÇÆÿ ÍÌÿ ÒÑÿ Ø×ÿ ÞÝÿ ãâÿ ççÿ êéÿ îí÷÷÷åûúßûúáûúãûúãûúâûúáúúà÷÷è ïïù ìëÿêéÿæåÿâáÿÝÝÿÙØÿÔÓÿÏÍÿÊÉÿÄÄÿ¿¾ÿ¹¹ÿ´´ÿ®®ÿ©©ÿ££ÿŸŸÿ››ÿ——ÿ’’ÿŽŽÿ‰‰ÿƒƒÿÿ}}ÿ||ÿzzÿyyÿxxÿwwÿwwÿvvÿvvÿuuÿttÿssÿttÿssÿssÿttÿttÿuuÿuuÿvvÿwwÿxxÿyyÿyyÿzzÿ{{ÿ||ÿ!}}ÿ#ÿ&€€ÿ(ÿ*ƒƒÿ,ƒƒÿ.……ÿ0……ÿ1……ÿ4‡‡ÿ7‰‰ÿ:ŠŠÿ=ŒŒÿ?ŽŽÿAÿD‘‘ÿG““ÿH““ÿJ‘‘ÿMÿOþ9ƒƒÑggOll/zz¾/††ü/‡‡ÿ,……ÿ+„„ÿ(ƒƒÿ'‚‚ÿ$€€ÿ!ÿ~~ÿ}}ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿuuÿ uuÿ ttÿ ssÿ rrÿrrÿqqÿppÿppÿooÿ qqÿwwÿxxÿxxÿxxÿxxÿxxÿxxÿyyÿzzÿ||ÿ||ÿ||ÿ||ÿ||ÿ||ÿ||ÿ||ÿ||ÿ||ÿ||ÿ}}ÿ}}ÿ}}ÿ~~ÿÿÿÿÿƒƒÿ††ÿ‰‰ÿŒŒÿÿ••ÿššÿ  ÿ¥¥ÿªªÿ°°ÿ¶¶ÿ ¼¼ÿ ÂÁÿ ÈÇÿ ÎÌÿ ÓÒÿ ØØÿ ÞÝÿ ãâÿ ççÿ êéÿ ïî÷øøåûûßûúâûúãûúãûúâûúáúúà÷÷è ïîù ìëÿêéÿæåÿâáÿÝÜÿØ×ÿÔÒÿÎÍÿÉÈÿÄÃÿ¾¾ÿ¹¹ÿ´´ÿ®®ÿ¨¨ÿ££ÿžžÿššÿ••ÿ’’ÿÿ‰‰ÿ‚‚ÿ~~ÿ||ÿ{{ÿyyÿxxÿwwÿwwÿwwÿvvÿvvÿttÿttÿttÿssÿssÿttÿuuÿuuÿvvÿvvÿwwÿxxÿxxÿyyÿzzÿ{{ÿ||ÿ!}}ÿ#ÿ&€€ÿ)‚‚ÿ+ƒƒÿ-„„ÿ0„„ÿ1……ÿ2††ÿ5‡‡ÿ7‰‰ÿ:‹‹ÿ=ÿ@ŽŽÿBÿD‘‘ÿG““ÿI””ÿJ““ÿM‘‘ÿMû/zz· __3ÿÿqqH!~~Ø1‡‡þ0‡‡ÿ-††ÿ+„„ÿ(ƒƒÿ'‚‚ÿ%ÿ"ÿ~~ÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿwwÿvvÿuuÿ uuÿ ttÿ ssÿ rrÿrrÿqqÿqqÿppÿooÿuuÿyyÿxxÿyyÿxxÿxxÿxxÿyyÿzzÿ{{ÿ||ÿ||ÿ||ÿ||ÿ||ÿ}}ÿ||ÿ||ÿ||ÿ||ÿ||ÿ}}ÿ}}ÿ~~ÿ~~ÿÿÿ€€ÿÿ‚‚ÿ……ÿ‡‡ÿŠŠÿŽŽÿ““ÿ——ÿœœÿ¡¡ÿ§§ÿ««ÿ±±ÿ··ÿ ¾½ÿ ÃÂÿ ÉÇÿ ÎÍÿ ÓÒÿ ÙØÿ ÞÝÿ ããÿ èçÿ êéÿîî÷øøåûûßûúáûúãûúãûúâûúáúúà÷÷è ïîù ìëÿêéÿæåÿááÿÜÜÿØ×ÿÓÒÿÎÍÿÉÇÿÃÃÿ¾¾ÿ¹¸ÿ³³ÿ­­ÿ¨¨ÿ¢¢ÿÿ™™ÿ””ÿÿŒŒÿˆˆÿƒƒÿ}}ÿ{{ÿzzÿyyÿxxÿwwÿwwÿwwÿwwÿuuÿttÿttÿrrÿssÿttÿttÿuuÿvvÿvvÿwwÿxxÿyyÿyyÿzzÿ||ÿ ||ÿ"}}ÿ$ÿ'ÿ*‚‚ÿ+ƒƒÿ.„„ÿ1……ÿ1††ÿ3††ÿ6ˆˆÿ8‰‰ÿ;‹‹ÿ>ÿAŽŽÿCÿE‘‘ÿG““ÿI””ÿL••ÿN““ÿJø%ttžZZ ooR$Þ2ˆˆÿ0‡‡ÿ-††ÿ+„„ÿ(ƒƒÿ'‚‚ÿ&ÿ#€€ÿ ÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿvvÿuuÿ uuÿ ttÿ ttÿ ssÿrrÿqqÿqqÿppÿ ssÿyyÿyyÿyyÿyyÿxxÿyyÿzzÿ{{ÿzzÿ||ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ||ÿ}}ÿ}}ÿ}}ÿ}}ÿ~~ÿ~~ÿ~~ÿÿÿÿÿ‚‚ÿ„„ÿ††ÿ‰‰ÿŒŒÿÿ””ÿ˜˜ÿÿ¢¢ÿ§§ÿ¬¬ÿ³³ÿ¹¹ÿ¾¾ÿ ÄÃÿ ÉÈÿ ÎÍÿ ÔÓÿ ØØÿ ÞÞÿ äãÿ èçÿ êêÿïî÷øøåûûßûûáûúãûúãûúâûúáûúàööé ïîù ìëÿêéÿæåÿáàÿÜÜÿ××ÿÓÒÿÎÌÿÈÇÿÃÂÿ½½ÿ¸·ÿ²²ÿ¬¬ÿ§§ÿ¡¡ÿœœÿ˜˜ÿ““ÿÿ‹‹ÿˆˆÿ‚‚ÿ||ÿzzÿyyÿxxÿwwÿwwÿwwÿwwÿvvÿvvÿttÿrrÿssÿttÿttÿuuÿuuÿwwÿwwÿxxÿyyÿyyÿ{{ÿ ||ÿ!}}ÿ$~~ÿ&€€ÿ(ÿ*‚‚ÿ,„„ÿ/……ÿ2††ÿ2††ÿ4‡‡ÿ7‰‰ÿ9ŠŠÿ<‹‹ÿ>ÿAŽŽÿCÿF’’ÿH““ÿJ””ÿL••ÿO••ÿIô#ss XX ppm(è3‰‰ÿ1ˆˆÿ.††ÿ,……ÿ)ƒƒÿ'‚‚ÿ%ÿ#€€ÿ €€ÿÿ~~ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿvvÿuuÿ uuÿ ttÿ ttÿ ssÿrrÿqqÿqqÿqqÿvvÿzzÿzzÿyyÿyyÿyyÿyyÿzzÿ{{ÿ{{ÿ}}ÿ}}ÿ}}ÿ}}ÿ~~ÿ~~ÿ}}ÿ}}ÿ}}ÿ~~ÿ~~ÿ}}ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ€€ÿ‚‚ÿƒƒÿ……ÿ‡‡ÿŠŠÿÿ‘‘ÿ••ÿ™™ÿžžÿ££ÿ©©ÿ®®ÿ´´ÿ¹¹ÿ¿¾ÿ ÄÃÿ ÉÈÿ ÏÍÿ ÔÒÿ ÙØÿ ßÞÿ äãÿ èçÿ ëêÿïî÷ø÷æûûßûúáûúâûúâûúâûúáúùá÷÷è ïîù ìëÿééÿåäÿáàÿÜÛÿØ×ÿÒÑÿÍÌÿÈÇÿÃÂÿ½¼ÿ··ÿ±±ÿ««ÿ¥¥ÿ  ÿ››ÿ––ÿ‘‘ÿÿŠŠÿ††ÿÿ{{ÿzzÿyyÿxxÿxxÿwwÿwwÿvvÿvvÿttÿssÿssÿttÿuuÿuuÿvvÿwwÿxxÿxxÿyyÿzzÿ {{ÿ"||ÿ#~~ÿ$ÿ&€€ÿ)ÿ*‚‚ÿ-„„ÿ1††ÿ3‡‡ÿ2††ÿ4‡‡ÿ7‰‰ÿ:ŠŠÿ=ŒŒÿ?ŽŽÿAÿDÿG’’ÿI““ÿJ””ÿM––ÿN––ÿB‹‹çoorjj qqtt‘-„„ô4‰‰ÿ1ˆˆÿ.††ÿ,……ÿ)„„ÿ'ƒƒÿ%ÿ#€€ÿ €€ÿÿ~~ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿwwÿvvÿ uuÿ ttÿ ssÿ ssÿrrÿqqÿqqÿ ssÿyyÿ{{ÿzzÿzzÿyyÿyyÿyyÿzzÿ{{ÿ||ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿ€€ÿÿƒƒÿ„„ÿ††ÿˆˆÿ‹‹ÿŽŽÿ’’ÿ––ÿ››ÿ  ÿ¥¥ÿªªÿ¯¯ÿµµÿººÿÀ¿ÿ ÅÄÿ ÊÉÿ ÏÍÿ ÔÓÿ ÙØÿ ÞÝÿ äãÿ èçÿ ëêÿîîøøøæûúàûúáûúâûúâûúâûúáûûß÷öè ïîù ëëÿéèÿåäÿáàÿÜÛÿ×ÖÿÑÐÿÌËÿÇÅÿÁÀÿ»»ÿµµÿ¯¯ÿ©©ÿ££ÿžžÿ™™ÿ””ÿÿŒŒÿˆˆÿ„„ÿ€€ÿ{{ÿyyÿxxÿxxÿwwÿwwÿvvÿvvÿuuÿuuÿssÿttÿuuÿuuÿvvÿwwÿxxÿyyÿyyÿzzÿ {{ÿ"}}ÿ$~~ÿ&ÿ'€€ÿ*ÿ,ƒƒÿ.„„ÿ1††ÿ3‡‡ÿ3††ÿ6ˆˆÿ8‰‰ÿ:‹‹ÿ>ŒŒÿ@ŽŽÿBÿE‘‘ÿH““ÿI””ÿK••ÿM––ÿO––þ9††ÐiiMmm#yy®1‡‡ú4‰‰ÿ2ˆˆÿ/‡‡ÿ,……ÿ)„„ÿ(ƒƒÿ%ÿ#ÿ!€€ÿÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ uuÿ ttÿ ttÿ ssÿrrÿqqÿqqÿvvÿ{{ÿ{{ÿzzÿzzÿzzÿzzÿ{{ÿ{{ÿ{{ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ}}ÿ~~ÿ~~ÿ~~ÿÿÿ€€ÿ€€ÿÿ‚‚ÿ„„ÿ……ÿ‡‡ÿŠŠÿŒŒÿÿ””ÿ˜˜ÿœœÿ¡¡ÿ¦¦ÿ««ÿ°°ÿµµÿ»ºÿÀÀÿ ÆÄÿ ÊÉÿ ÏÎÿ ÔÓÿ ÙØÿ ÞÞÿ äãÿ èçÿ ëêÿ ïîøø÷æûúßûúáûúâûúâûúâûûàûúß÷÷ç îíùëêÿéèÿåäÿààÿÛÚÿÖÕÿÑÐÿËÊÿÆÅÿÀÀÿººÿ³³ÿ­­ÿ§§ÿ¡¡ÿœœÿ——ÿ’’ÿŽŽÿŠŠÿ‡‡ÿ„„ÿÿzzÿxxÿxxÿwwÿwwÿvvÿuuÿttÿttÿttÿttÿuuÿvvÿvvÿwwÿxxÿyyÿzzÿ{{ÿ!||ÿ#}}ÿ%~~ÿ'ÿ)ÿ*‚‚ÿ-„„ÿ/……ÿ2††ÿ4‡‡ÿ4‡‡ÿ7ˆˆÿ9ŠŠÿ<ŒŒÿ?ÿAŽŽÿCÿF‘‘ÿH’’ÿJ””ÿM––ÿO——ÿM••ù+€€® bb, qq8 ||É5ŠŠý5ŠŠÿ3ˆˆÿ0‡‡ÿ,……ÿ*ƒƒÿ(ƒƒÿ%‚‚ÿ#ÿ!€€ÿ €€ÿ~~ÿ}}ÿ{{ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ uuÿ ttÿ ttÿ ssÿrrÿqqÿ ssÿzzÿ{{ÿ{{ÿ{{ÿzzÿzzÿ{{ÿ}}ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿ~~ÿ~~ÿ}}ÿ~~ÿ~~ÿÿÿÿ€€ÿ€€ÿ€€ÿÿ‚‚ÿƒƒÿ„„ÿ††ÿ‰‰ÿ‹‹ÿŽŽÿ‘‘ÿ••ÿ™™ÿÿ¢¢ÿ§§ÿ¬¬ÿ°°ÿ¶µÿ»»ÿÁÀÿ ÆÄÿ ËÉÿ ÐÎÿ ÕÔÿ ÚÙÿ ßÞÿ äãÿ éèÿ ëêÿ ïï÷ø÷æûúßûúáûúâûúâûúâûúáûúßööè îîù ëêÿéèÿääÿßÞÿÚÙÿÕÔÿÐÎÿÊÉÿÄÄÿ¿¾ÿ¹¸ÿ²²ÿ¬¬ÿ¦¦ÿŸŸÿššÿ••ÿÿŒŒÿ‰‰ÿ††ÿƒƒÿ~~ÿyyÿxxÿwwÿwwÿvvÿvvÿttÿttÿttÿuuÿvvÿvvÿwwÿxxÿxxÿyyÿ{{ÿ!||ÿ"||ÿ$~~ÿ&ÿ(€€ÿ*ÿ+‚‚ÿ.„„ÿ0††ÿ3‡‡ÿ4ˆˆÿ5ˆˆÿ7‰‰ÿ9ŠŠÿ=ŒŒÿ@ŽŽÿBÿD‘‘ÿG’’ÿI““ÿK••ÿM––ÿP——ÿEìxx…hhªª rrP'€€Ú7‹‹ÿ5ŠŠÿ3‰‰ÿ0‡‡ÿ-……ÿ*„„ÿ(ƒƒÿ&ƒƒÿ$‚‚ÿ!ÿ €€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿuuÿ ttÿ ttÿ ssÿrrÿqqÿvvÿ{{ÿ{{ÿ{{ÿ{{ÿzzÿ{{ÿ||ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿ~~ÿÿÿÿÿÿÿ€€ÿÿÿ‚‚ÿƒƒÿ……ÿ††ÿ‡‡ÿ‰‰ÿŒŒÿÿ““ÿ––ÿššÿŸŸÿ££ÿ¨¨ÿ¬¬ÿ±±ÿ¶¶ÿ¼»ÿÁÀÿ ÆÅÿ ËÊÿ ÐÏÿ ÕÔÿ ÚÙÿ ßßÿ ääÿ èèÿ ëêÿ îîø÷÷æûûÞûûàûúâûúãûúâûúàûúßööè îîù ëêÿèçÿäãÿßÞÿÚÙÿÔÓÿÎÍÿÉÈÿÃÃÿ½½ÿ··ÿ±±ÿ««ÿ¤¤ÿžžÿ˜˜ÿ““ÿŽŽÿŠŠÿˆˆÿ……ÿ‚‚ÿ}}ÿyyÿxxÿwwÿwwÿvvÿvvÿuuÿuuÿuuÿvvÿwwÿxxÿxxÿyyÿzzÿ!{{ÿ"}}ÿ#}}ÿ%~~ÿ'ÿ*ÿ+‚‚ÿ,ƒƒÿ/……ÿ2††ÿ4‡‡ÿ5ˆˆÿ6ˆˆÿ8‰‰ÿ;‹‹ÿ>ŒŒÿAŽŽÿCÿE‘‘ÿH““ÿI““ÿL••ÿN––ÿO——þ;‹‹×rr[__ qqj+„„ç8‹‹ÿ5ŠŠÿ3‰‰ÿ1‡‡ÿ.††ÿ+„„ÿ(„„ÿ&ƒƒÿ$‚‚ÿ!ÿ €€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿuuÿ uuÿ ttÿ ssÿ rrÿ ssÿyyÿ||ÿ||ÿ{{ÿ{{ÿ{{ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ€€ÿÿÿÿ€€ÿ€€ÿ€€ÿÿÿ‚‚ÿƒƒÿ„„ÿ……ÿ††ÿˆˆÿ‹‹ÿÿ‘‘ÿ““ÿ——ÿ››ÿŸŸÿ¤¤ÿ¨¨ÿ­­ÿ²²ÿ··ÿ¼»ÿÁÀÿ ÆÅÿ ËÊÿ ÐÏÿ ÖÔÿ ÛÚÿ àßÿ ääÿ éèÿ ëêÿ ïîøøøåûûÞûûàûúâûúâûúâûúáûûßööè îíùëêÿèçÿãâÿÞÝÿÙØÿÓÒÿÍÌÿÈÇÿÂÁÿ»»ÿµµÿ¯¯ÿ¨¨ÿ¢¢ÿœœÿ––ÿ‘‘ÿŒŒÿ‰‰ÿ††ÿ„„ÿÿ}}ÿyyÿwwÿvvÿuuÿvvÿuuÿuuÿvvÿvvÿwwÿxxÿxxÿyyÿzzÿ!{{ÿ#}}ÿ$~~ÿ&ÿ(€€ÿ*ÿ,‚‚ÿ-ƒƒÿ0……ÿ3‡‡ÿ5ˆˆÿ6ˆˆÿ7‰‰ÿ9ŠŠÿ<‹‹ÿ?ÿAŽŽÿDÿF’’ÿH““ÿJ””ÿL••ÿN––ÿL••ú.ƒƒµ nn5ss uuz1‡‡ï8ŒŒÿ5ŠŠÿ4‰‰ÿ1ˆˆÿ.††ÿ+……ÿ)„„ÿ&ƒƒÿ$‚‚ÿ"ÿ €€ÿÿ~~ÿ||ÿ{{ÿzzÿzzÿxxÿxxÿvvÿuuÿ uuÿ ttÿ ssÿ ssÿ uuÿ||ÿ||ÿ||ÿ||ÿ||ÿ}}ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿÿÿ€€ÿ€€ÿ€€ÿÿÿÿÿ€€ÿ€€ÿ€€ÿÿÿ‚‚ÿƒƒÿ„„ÿ††ÿˆˆÿŠŠÿŒŒÿÿ‘‘ÿ••ÿ˜˜ÿœœÿŸŸÿ££ÿ©©ÿ®®ÿ²²ÿ··ÿ¼¼ÿÁÀÿ ÆÅÿ ËÊÿ ÑÏÿ ÖÕÿ ÛÚÿ àßÿ åäÿ éèÿ ëêÿ ïîøøøåûûÞûúáûúâûúâûúâûûàûûÞ÷öç íìúêéÿçæÿãâÿÞÝÿØ×ÿÒÑÿÌËÿÆÅÿÀ¿ÿ¹¹ÿ³³ÿ­­ÿ¦¦ÿŸŸÿ™™ÿ””ÿÿ‹‹ÿˆˆÿ……ÿ ƒƒÿ€€ÿ||ÿxxÿvvÿwwÿwwÿwwÿvvÿvvÿvvÿwwÿyyÿyyÿyyÿ!zzÿ"||ÿ$}}ÿ%~~ÿ&ÿ)€€ÿ+ÿ-ƒƒÿ/„„ÿ1††ÿ3‡‡ÿ5ˆˆÿ6‰‰ÿ7‰‰ÿ:ŠŠÿ=ŒŒÿ@ÿBÿE‘‘ÿG’’ÿH““ÿK””ÿM––ÿO——ÿI’’ô&||“ kk\\ wwz2ˆˆñ9ŒŒÿ6‹‹ÿ4‰‰ÿ1ˆˆÿ/‡‡ÿ,††ÿ)„„ÿ'„„ÿ$‚‚ÿ"ÿ €€ÿÿ~~ÿ||ÿ{{ÿ{{ÿzzÿxxÿxxÿwwÿvvÿ uuÿ ttÿ ssÿ ssÿxxÿ||ÿ||ÿ||ÿ}}ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿÿÿÿÿÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿ‚‚ÿ‚‚ÿƒƒÿ……ÿ‡‡ÿ‰‰ÿ‹‹ÿÿÿ““ÿ––ÿ˜˜ÿœœÿ  ÿ¤¤ÿ©©ÿ®®ÿ³³ÿ¸¸ÿ½¼ÿÂÁÿÆÅÿ ÌËÿ ÑÐÿ ÖÕÿ ÛÚÿ àßÿ åäÿ éèÿ ëêÿ îî÷øøäûûÞûûàûûáûúâûúáûûßûûÝööç íìùêéÿçæÿâáÿÜÜÿ×ÖÿÑÐÿËÊÿÅÄÿ¾¾ÿ¸¸ÿ±±ÿªªÿ££ÿžžÿ˜˜ÿ““ÿŽŽÿŠŠÿ‡‡ÿ „„ÿ!ƒƒÿ€€ÿ{{ÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿxxÿyyÿyyÿzzÿ!{{ÿ#||ÿ$}}ÿ&~~ÿ'ÿ*ÿ,‚‚ÿ.„„ÿ0……ÿ2‡‡ÿ4ˆˆÿ6‰‰ÿ7‰‰ÿ9‰‰ÿ<‹‹ÿ?ŒŒÿAŽŽÿCÿF‘‘ÿH’’ÿI““ÿL••ÿN––ÿP——ÿCæwwxffffxx‡2‰‰ó9ÿ7‹‹ÿ4‰‰ÿ2‰‰ÿ0ˆˆÿ,††ÿ)……ÿ'„„ÿ%ƒƒÿ#ÿ!€€ÿÿ~~ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ uuÿ ttÿ ssÿ ttÿ{{ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ~~ÿÿÿÿÿÿÿÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿ‚‚ÿ‚‚ÿƒƒÿ„„ÿ††ÿ‡‡ÿ‰‰ÿ‹‹ÿŽŽÿÿ““ÿ––ÿ™™ÿÿ¡¡ÿ¥¥ÿªªÿ®®ÿ³³ÿ¸¸ÿ½¼ÿÂÁÿÇÆÿ ÌËÿ ÐÏÿ ÕÕÿ ÚÚÿ àßÿ åäÿ éèÿ ëêÿ îî÷÷÷äûûÝûûßûúáûúáûúàûûÞûûÜööæ îíøééÿæåÿáàÿÜÛÿÕÕÿÏÎÿÊÉÿÃÃÿ½½ÿ¶¶ÿ¯¯ÿ¨¨ÿ¢¢ÿœœÿ—–ÿ‘‘ÿŒŒÿˆˆÿ ††ÿ!„„ÿ!‚‚ÿ€€ÿzzÿxxÿxxÿwwÿwwÿwwÿxxÿyyÿyyÿzzÿ!{{ÿ"||ÿ#||ÿ%}}ÿ'ÿ)€€ÿ+ÿ-ƒƒÿ/„„ÿ1††ÿ4‡‡ÿ5ˆˆÿ8ŠŠÿ8‰‰ÿ:ŠŠÿ=‹‹ÿ?ÿBŽŽÿDÿF‘‘ÿI““ÿJ””ÿN––ÿO——ÿO––ý5††Ç llIff ss{{›4ŠŠö:ÿ7‹‹ÿ4‰‰ÿ2‰‰ÿ0‡‡ÿ,††ÿ*††ÿ(„„ÿ%ƒƒÿ#‚‚ÿ!ÿÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ uuÿ uuÿ ttÿvvÿ}}ÿ}}ÿ}}ÿ}}ÿ~~ÿ~~ÿÿÿÿÿÿ€€ÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€€ÿ€€ÿÿÿÿ‚‚ÿƒƒÿ……ÿ††ÿ‡‡ÿ‰‰ÿ‹‹ÿŽŽÿÿ““ÿ––ÿ™™ÿÿ¡¡ÿ¥¥ÿ©©ÿ®®ÿ³³ÿ¸¸ÿ½½ÿÂÁÿÇÆÿ ÌËÿ ÑÐÿ ÕÕÿ ÚÚÿ àßÿ äãÿèçÿ êéÿ îîöøøãûûÝûúßûúàûúàûûßûûÞûûÜ÷÷å ííøéèÿåäÿàßÿÚÚÿÔÓÿÎÍÿÈÇÿÂÁÿ»»ÿ´´ÿ¬¬ÿ¦¦ÿ  ÿš™ÿ””ÿÿ‹‹ÿ ˆˆÿ!††ÿ"„„ÿ"‚‚ÿ ÿ{{ÿyyÿxxÿwwÿxxÿyyÿyyÿzzÿ!{{ÿ"||ÿ#||ÿ$}}ÿ&~~ÿ)€€ÿ+ÿ,‚‚ÿ.ƒƒÿ1……ÿ3††ÿ5ˆˆÿ7‰‰ÿ9ŠŠÿ9‰‰ÿ<ŠŠÿ>ŒŒÿ@ÿCÿE‘‘ÿG’’ÿJ““ÿK••ÿN––ÿP——ÿM““õ'}}¡kk&pp"{{±8ŠŠú:ÿ8ŒŒÿ5ŠŠÿ2‰‰ÿ0‡‡ÿ-††ÿ+††ÿ(„„ÿ&ƒƒÿ$‚‚ÿ"ÿÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ uuÿ uuÿ ttÿyyÿ~~ÿ}}ÿ}}ÿ}}ÿ~~ÿÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ€€ÿ€€ÿÿ‚‚ÿÿ‚‚ÿ‚‚ÿƒƒÿ……ÿ††ÿ‡‡ÿ‰‰ÿŒŒÿŽŽÿÿ““ÿ––ÿššÿÿ¡¡ÿ¥¥ÿªªÿ¯¯ÿ³³ÿ¸¸ÿ½½ÿÂÂÿÇÆÿ ÌËÿ ÐÏÿ ÕÔÿ ÚÙÿ ßÞÿ ããÿèçÿ êéÿ îîö÷÷ãûûÜûûÞûûßûúàûûßûûÝúúÛ÷÷ä ììøèçÿäãÿßßÿÚÙÿÓÒÿÍÌÿÆÆÿ¿¿ÿ¹¸ÿ±±ÿ««ÿ¤¤ÿÿ——ÿ““ÿŽŽÿ!ŠŠÿ"ˆˆÿ"……ÿ#ƒƒÿ#‚‚ÿ}}ÿzzÿxxÿxxÿxxÿyyÿyyÿ zzÿ"{{ÿ#||ÿ$}}ÿ&~~ÿ(ÿ*ÿ,‚‚ÿ.ƒƒÿ/„„ÿ2††ÿ4‡‡ÿ6ˆˆÿ9ŠŠÿ:ŠŠÿ:ŠŠÿ=ŒŒÿ@ÿBŽŽÿDÿF‘‘ÿH’’ÿJ””ÿL••ÿO——ÿQ——þAÝssj[[ oo0'‚‚Ä:ü;ÿ9ŒŒÿ6ŠŠÿ3‰‰ÿ0ˆˆÿ.‡‡ÿ+††ÿ(„„ÿ&ƒƒÿ$‚‚ÿ"ÿ €€ÿÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿvvÿ uuÿ uuÿ||ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿÿÿÿÿÿ‚‚ÿƒƒÿƒƒÿ„„ÿ……ÿ††ÿˆˆÿŠŠÿŒŒÿŽŽÿ‘‘ÿ““ÿ––ÿ™™ÿÿ¡¡ÿ¥¥ÿªªÿ®®ÿ³³ÿ¸¸ÿ½½ÿÂÁÿÆÅÿ ËÊÿ ÐÏÿ ÕÔÿ ÙÙÿ ÞÞÿ ããÿççÿ êéÿ îîöøøãúúÜûûÞûûßûûßûûÞûûÛûúÚ÷÷ã ìëøççÿäãÿßÞÿØØÿÒÑÿËÊÿÄÄÿ½½ÿ¶¶ÿ¯¯ÿ©©ÿ¢¢ÿ››ÿ––ÿ ‘‘ÿ!ÿ"ŠŠÿ"‡‡ÿ#……ÿ$ƒƒÿ#€€ÿ{{ÿxxÿyyÿyyÿyyÿyyÿ!{{ÿ#||ÿ$||ÿ&~~ÿ(ÿ*€€ÿ+ÿ-ƒƒÿ/ƒƒÿ1……ÿ4††ÿ6ˆˆÿ7‰‰ÿ:ŠŠÿ;ŠŠÿ;‹‹ÿ>ŒŒÿAŽŽÿCÿEÿG’’ÿI““ÿK””ÿN––ÿP——ÿO••ú/‚‚ºmm:UUpp;-……Ð<þ;ÿ9ŒŒÿ7‹‹ÿ4ŠŠÿ1‰‰ÿ.‡‡ÿ,††ÿ)……ÿ'ƒƒÿ%ƒƒÿ#ÿ €€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿzzÿxxÿwwÿvvÿvvÿ uuÿwwÿ~~ÿÿ~~ÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿÿÿ ‚‚ÿ ‚‚ÿ ƒƒÿ„„ÿ„„ÿ……ÿ††ÿˆˆÿ‰‰ÿ‹‹ÿÿÿ‘‘ÿ““ÿ––ÿššÿÿ¡¡ÿ¥¥ÿ©©ÿ®®ÿ³³ÿ¸¸ÿ¼¼ÿÁÀÿÆÅÿ ËÊÿ ÐÏÿ ÔÓÿ ÙØÿ ÞÝÿ ãâÿçæÿ êéÿ íí÷÷÷ãúúÜûûÝûûÞûûÞûûÜûûÛûûÙ÷õâ ëêøçæÿãâÿÞÝÿ×ÖÿÐÐÿÊÉÿÂÂÿ¼¼ÿ´´ÿ®®ÿ§§ÿ  ÿššÿ ••ÿ!ÿ"ŒŒÿ#‰‰ÿ#‡‡ÿ$„„ÿ%ƒƒÿ"~~ÿzzÿyyÿzzÿ zzÿ!zzÿ"{{ÿ#||ÿ%}}ÿ'ÿ)€€ÿ+ÿ,‚‚ÿ.ƒƒÿ0„„ÿ3††ÿ5‡‡ÿ7ˆˆÿ9ŠŠÿ<‹‹ÿ;‹‹ÿ=‹‹ÿ@ÿBÿDÿG‘‘ÿH’’ÿJ““ÿL••ÿN––ÿQ˜˜ÿH’’íxx‰kkUUttH/††Ú>ŽŽþ<ŽŽÿ9ŒŒÿ7‹‹ÿ5ŠŠÿ1‰‰ÿ.ˆˆÿ,††ÿ)……ÿ'ƒƒÿ%ƒƒÿ#ÿ €€ÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ uuÿzzÿÿÿÿ€€ÿÿ€€ÿ€€ÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ „„ÿ „„ÿ„„ÿ……ÿ††ÿ‡‡ÿˆˆÿŠŠÿŒŒÿŽŽÿÿ‘‘ÿ””ÿ––ÿ™™ÿÿ¡¡ÿ¤¤ÿ©©ÿ®®ÿ³³ÿ··ÿ¼¼ÿÁÀÿÆÅÿ ËÊÿ ÏÎÿ ÔÓÿ ÙØÿ ÞÝÿââÿçæÿ êéÿ íì÷øøâûûÛûûÜûûÝûûÝûûÜûûÚûû×õõâ ëë÷æåÿâáÿÜÛÿÖÕÿÏÎÿÈÇÿÀÀÿº¹ÿ³³ÿ««ÿ¥¥ÿžžÿ™™ÿ ““ÿ"ÿ#‹‹ÿ$‰‰ÿ%……ÿ&„„ÿ%€€ÿ ||ÿzzÿ {{ÿ"||ÿ#{{ÿ#||ÿ%}}ÿ'~~ÿ)€€ÿ*€€ÿ,ÿ-‚‚ÿ/„„ÿ2††ÿ4‡‡ÿ6ˆˆÿ9ŠŠÿ;‹‹ÿ<ŒŒÿ<‹‹ÿ>ŒŒÿAŽŽÿDÿF‘‘ÿH’’ÿI““ÿL””ÿN––ÿO——ÿQ˜˜ÿAŽŽÛtt\__ªªuuP1††á>þ<ŽŽÿ:ÿ7ŒŒÿ5ŠŠÿ2ŠŠÿ/ˆˆÿ,††ÿ*……ÿ'ƒƒÿ%‚‚ÿ#ÿ!€€ÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿwwÿvvÿvvÿ||ÿÿÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ „„ÿ „„ÿ ……ÿ……ÿ††ÿ ‡‡ÿ‰‰ÿŠŠÿŒŒÿŽŽÿÿ’’ÿ””ÿ——ÿššÿžžÿ¡¡ÿ¥¥ÿªªÿ®®ÿ³³ÿ··ÿ¼¼ÿÁÀÿÅÅÿÊÉÿ ÎÍÿ ÓÒÿ Ø×ÿ ÝÜÿâáÿçæÿ éèÿ îíöøøáûûÙûûÛûûÜûûÜûûÛûûÙûûÖõõá ëê÷äãÿààÿÛÚÿÔÓÿÍËÿÆÅÿ¿¾ÿ¸¸ÿ°°ÿªªÿ££ÿÿ!——ÿ"’’ÿ#ŽŽÿ$‹‹ÿ&‡‡ÿ'„„ÿ'‚‚ÿ#~~ÿ!{{ÿ!{{ÿ#||ÿ#||ÿ%}}ÿ'~~ÿ(ÿ*€€ÿ,ÿ-‚‚ÿ/ƒƒÿ1„„ÿ4††ÿ6ˆˆÿ8‰‰ÿ:‹‹ÿ<ŒŒÿ<ŒŒÿ=ŒŒÿ@ŽŽÿCÿEÿG‘‘ÿI““ÿK””ÿM••ÿO——ÿQ˜˜ÿP––ú3„„·kk9ttH0‡‡Û>þ=ÿ:ÿ8ÿ6‹‹ÿ3ŠŠÿ0ˆˆÿ-‡‡ÿ+……ÿ(„„ÿ%‚‚ÿ#ÿ!€€ÿ~~ÿ}}ÿ}}ÿ||ÿ{{ÿzzÿyyÿwwÿwwÿvvÿwwÿ~~ÿ €€ÿ€€ÿ€€ÿ‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ƒƒÿ ƒƒÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ!„„ÿ!„„ÿ!„„ÿ!……ÿ ……ÿ!††ÿ!‡‡ÿ!ˆˆÿ ‰‰ÿ ŠŠÿŒŒÿŽŽÿÿ’’ÿ””ÿ——ÿššÿÿ¡¡ÿ¦¦ÿªªÿ®®ÿ²²ÿ··ÿ¼¼ÿÀÀÿÅÄÿÉÈÿ ÍÌÿ ÓÑÿ ×Öÿ ÝÜÿâáÿæåÿéèÿ ííö÷÷àûûØûûÚûûÛûûÛûûÚûû×üüÔöõß éé÷äãÿàßÿÙØÿÒÑÿËÊÿÄÃÿ½½ÿ¶¶ÿ¯¯ÿ¨¨ÿ¡¡ÿ!››ÿ"––ÿ#‘‘ÿ$ÿ&ŠŠÿ'††ÿ(„„ÿ'ÿ"}}ÿ#}}ÿ$}}ÿ%}}ÿ&~~ÿ(~~ÿ*ÿ,ÿ-‚‚ÿ.‚‚ÿ0„„ÿ3……ÿ5‡‡ÿ7ˆˆÿ:ŠŠÿ<ŒŒÿ=ŒŒÿ=ŒŒÿ?ÿAŽŽÿDÿFÿH’’ÿJ““ÿL••ÿN––ÿP——ÿR˜˜ÿGçww|mmÿÿ ss>-ƒƒÑ>ý=ÿ;ŽŽÿ9ÿ6ŒŒÿ3ŠŠÿ0ˆˆÿ.‡‡ÿ+††ÿ(„„ÿ&‚‚ÿ$‚‚ÿ!ÿÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿyyÿ €€ÿ ÿÿÿ‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ‚‚ÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ"„„ÿ"„„ÿ"„„ÿ"……ÿ!††ÿ!††ÿ!‡‡ÿ!ˆˆÿ ‰‰ÿ ‹‹ÿ ÿŽŽÿÿ’’ÿ””ÿ——ÿššÿžžÿ¡¡ÿ¦¦ÿªªÿ®®ÿ²²ÿ··ÿ¼»ÿÀ¿ÿÅÄÿÉÈÿ ÍÌÿ ÒÑÿ ×Öÿ ÜÛÿáàÿæåÿèçÿ íìõöößûû×ûûÙûûÚûûÙûûØüüÕüûÓõõÞ éè÷ãâÿÞÞÿØ×ÿÐÏÿÊÉÿÂÂÿ»»ÿ´´ÿ­­ÿ¦¦ÿ   ÿ!ššÿ#••ÿ%‘‘ÿ&ŒŒÿ(‰‰ÿ*††ÿ)ƒƒÿ%€€ÿ$~~ÿ%~~ÿ'~~ÿ(ÿ)ÿ+€€ÿ-ÿ.‚‚ÿ0ƒƒÿ2……ÿ4††ÿ6ˆˆÿ8‰‰ÿ<‹‹ÿ>ŒŒÿ>ÿ>ÿAŽŽÿCŽŽÿEÿG‘‘ÿI““ÿK““ÿM••ÿO––ÿQ——ÿP––ü6……à mmHffÿÿ ppB.„„Ô?þ>ÿ;ŽŽÿ9ÿ6ŒŒÿ4ŠŠÿ1‰‰ÿ.ˆˆÿ,††ÿ)……ÿ&ƒƒÿ$‚‚ÿ!ÿÿ~~ÿ||ÿ||ÿ{{ÿzzÿyyÿxxÿwwÿvvÿ{{ÿ"ÿ!ÿÿ€€ÿÿ‚‚ÿ‚‚ÿ‚‚ÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"ƒƒÿ"ƒƒÿ"„„ÿ"„„ÿ"„„ÿ"„„ÿ"……ÿ#……ÿ"……ÿ!††ÿ!††ÿ"‡‡ÿ!ˆˆÿ!ŠŠÿ!‹‹ÿ!ŒŒÿ ŽŽÿÿ’’ÿ••ÿ——ÿ››ÿžžÿ¡¡ÿ¥¥ÿ©©ÿ­­ÿ²²ÿ·¶ÿ»ºÿÀ¿ÿÄÃÿÈÇÿÌËÿ ÑÐÿ ÖÕÿÛÛÿàßÿåäÿçæÿ ìëõööÞûûÖûûØûûØûûØüüÖüüÓüûÑõôÝèæ÷ááÿÝÜÿÖÕÿÐÎÿÈÇÿÀÀÿ¹¹ÿ²²ÿ««ÿ¥¥ÿ!žžÿ#™™ÿ%••ÿ'ÿ(‹‹ÿ*‰‰ÿ+††ÿ)ƒƒÿ%ÿ'ÿ(ÿ)€€ÿ*€€ÿ,ÿ.‚‚ÿ0ƒƒÿ2„„ÿ4††ÿ6‡‡ÿ8ˆˆÿ:ŠŠÿ=ŒŒÿ?ÿ?ÿ@ŽŽÿBÿDÿG‘‘ÿI‘‘ÿK““ÿL””ÿN••ÿP––ÿS˜˜ÿI‘‘ë!xx‰iiÿÿ ppF/††×@þ?ÿ<ŽŽÿ:ŽŽÿ7ÿ4‹‹ÿ1‰‰ÿ/ˆˆÿ-††ÿ*……ÿ'„„ÿ%‚‚ÿ"ÿÿ~~ÿ}}ÿ||ÿ||ÿzzÿyyÿxxÿwwÿvvÿ~~ÿ#‚‚ÿ!ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿƒƒÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿƒƒÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!„„ÿ!„„ÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"ƒƒÿ#„„ÿ#„„ÿ#„„ÿ#„„ÿ#„„ÿ#……ÿ$……ÿ#……ÿ#††ÿ#††ÿ#††ÿ#‡‡ÿ"‰‰ÿ#ŠŠÿ"‹‹ÿ"ÿ!ÿ ‘‘ÿ““ÿ••ÿ˜˜ÿššÿžžÿ¡¡ÿ¥¥ÿ©©ÿ­­ÿ²²ÿ¶¶ÿººÿ¿¿ÿÃÂÿÇÆÿÌÊÿ ÑÏÿ ÕÕÿÛÚÿàßÿäãÿçæÿ ìëôöõÜûûÔüüÕüüÖûûÖûûÕüüÒüüÏôòÜåä÷àßÿÛÚÿÕÔÿÍÌÿÆÅÿ¾¾ÿ··ÿ±±ÿ©©ÿ!££ÿ#ÿ%˜˜ÿ'““ÿ)ÿ*ŠŠÿ+ˆˆÿ+……ÿ&ÿ(€€ÿ)€€ÿ*€€ÿ,€€ÿ-‚‚ÿ0ƒƒÿ2„„ÿ4……ÿ6‡‡ÿ7ˆˆÿ9‰‰ÿ<‹‹ÿ>ŒŒÿ@ŽŽÿ@ŽŽÿAŽŽÿDÿF‘‘ÿH‘‘ÿJ’’ÿL””ÿN••ÿO––ÿR——ÿQ––ü9‡‡Ä iiMUU ssK0‡‡Û@þ@‘‘ÿ=ÿ:ŽŽÿ8ÿ5‹‹ÿ2‰‰ÿ0ˆˆÿ-††ÿ+……ÿ(„„ÿ&‚‚ÿ#ÿ €€ÿ~~ÿ}}ÿ||ÿ||ÿzzÿyyÿyyÿwwÿxxÿ €€ÿ#‚‚ÿ"‚‚ÿ!ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿƒƒÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ƒƒÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!„„ÿ!„„ÿ"„„ÿ"„„ÿ"„„ÿ"„„ÿ#„„ÿ#„„ÿ$……ÿ$„„ÿ$„„ÿ$„„ÿ$……ÿ$……ÿ$……ÿ$††ÿ#††ÿ#‡‡ÿ#ˆˆÿ#‰‰ÿ#ŠŠÿ"ŒŒÿ"ÿ!ÿ!‘‘ÿ ““ÿ••ÿ——ÿššÿÿ¡¡ÿ¥¥ÿ¨¨ÿ­­ÿ°°ÿµµÿ¹¹ÿ¾½ÿÂÁÿÆÅÿËÊÿÐÏÿÔÓÿÚÙÿßÞÿããÿååÿ êéôõõÛûûÒüüÓüüÔûûÕüüÓüüÐüûÎóòÛåäößÞÿÚÙÿÓÒÿÌÊÿÄÄÿ½½ÿ¶¶ÿ¯¯ÿ ©©ÿ"¢¢ÿ%œœÿ'——ÿ)‘‘ÿ*ÿ,ŠŠÿ-‡‡ÿ)ƒƒÿ(ÿ)ÿ+ÿ-ÿ/‚‚ÿ2„„ÿ4††ÿ6††ÿ7ˆˆÿ9ˆˆÿ;ŠŠÿ>ŒŒÿ@ÿBŽŽÿAŽŽÿBŽŽÿE‘‘ÿGÿI’’ÿL““ÿM••ÿO––ÿQ——ÿS˜˜ÿJ’’î#{{gg UUrrN3‡‡ÞAþ@‘‘ÿ>ÿ;ŽŽÿ8ÿ5‹‹ÿ3ŠŠÿ0ˆˆÿ-††ÿ+††ÿ)„„ÿ&ƒƒÿ$ÿ!€€ÿ~~ÿ}}ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿyyÿ"‚‚ÿ#ƒƒÿ"ƒƒÿ"„„ÿ!„„ÿ!ƒƒÿ!ƒƒÿ ƒƒÿ‚‚ÿ‚‚ÿ‚‚ÿƒƒÿ ƒƒÿ ‚‚ÿ ƒƒÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!„„ÿ!„„ÿ!„„ÿ"„„ÿ#„„ÿ#„„ÿ#„„ÿ#„„ÿ$……ÿ$……ÿ$……ÿ$……ÿ$„„ÿ%……ÿ%……ÿ$……ÿ$††ÿ%††ÿ$††ÿ$‡‡ÿ$‡‡ÿ$ˆˆÿ$‰‰ÿ#ŠŠÿ#‹‹ÿ"ÿ"ÿ!‘‘ÿ!““ÿ ””ÿ——ÿššÿÿ  ÿ¤¤ÿ¨¨ÿ««ÿ¯¯ÿ³³ÿ¸·ÿ¼¼ÿÁÀÿÅÅÿÊÉÿÎÍÿÓÒÿÙØÿÞÝÿââÿåäÿ êèóöõÙüüÏûûÒüüÓüüÓüüÑýüÎüûÌóòÙääöÞÞÿÙØÿÒÑÿÊÊÿÃÃÿ¼¼ÿµµÿ®®ÿ!§§ÿ$¡¡ÿ'ššÿ(••ÿ+‘‘ÿ-ŽŽÿ.ŠŠÿ,……ÿ*ƒƒÿ*‚‚ÿ,‚‚ÿ.ƒƒÿ1ƒƒÿ4……ÿ6††ÿ7‡‡ÿ9ˆˆÿ;‰‰ÿ=‹‹ÿ?ÿBŽŽÿCÿBŽŽÿDÿF‘‘ÿI‘‘ÿK““ÿM””ÿO––ÿQ––ÿR˜˜ÿR——þ=‰‰ÏooW__UUssR5‰‰áB‘‘þA‘‘ÿ>ÿ<ÿ9ÿ6ŒŒÿ3ŠŠÿ0ˆˆÿ.‡‡ÿ,††ÿ)„„ÿ&ƒƒÿ%ÿ"€€ÿÿ~~ÿ||ÿ{{ÿ{{ÿzzÿyyÿxxÿzzÿ#ƒƒÿ$„„ÿ#„„ÿ#„„ÿ"„„ÿ!ƒƒÿ!ƒƒÿ ƒƒÿ ƒƒÿƒƒÿ ƒƒÿ ƒƒÿ ‚‚ÿ ‚‚ÿ ƒƒÿ‚‚ÿ ƒƒÿ ƒƒÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ"……ÿ#„„ÿ#……ÿ#……ÿ#……ÿ#……ÿ$……ÿ$……ÿ$……ÿ$……ÿ%……ÿ%……ÿ%……ÿ&……ÿ%……ÿ%……ÿ%……ÿ%……ÿ%††ÿ%‡‡ÿ%‡‡ÿ%‰‰ÿ%ŠŠÿ$‹‹ÿ#ŒŒÿ#ŽŽÿ"ÿ"‘‘ÿ"““ÿ!••ÿ ——ÿššÿÿ  ÿ££ÿ§§ÿ««ÿ¯¯ÿ²²ÿ··ÿ»»ÿ¿¿ÿÄÄÿÉÇÿÍÌÿÒÑÿØ×ÿÝÜÿâáÿäãþ ééòöö×üüÎüüÐüüÑüüÑüüÏýüÌüüÉòòØããöÝÜÿØ×ÿÑÐÿÊÉÿÂÂÿººÿ³³ÿ ¬¬ÿ#¦¦ÿ'  ÿ(ššÿ+••ÿ.‘‘ÿ/ÿ.ˆˆÿ+……ÿ,„„ÿ-ƒƒÿ0„„ÿ3„„ÿ6††ÿ8‡‡ÿ9ˆˆÿ:‰‰ÿ=‹‹ÿ?ŒŒÿAŽŽÿDÿCÿDÿFÿH‘‘ÿJ““ÿL””ÿN••ÿP––ÿR——ÿS™™ÿN••ó(}}Ÿdd+ÿÿssT7‰‰ãB‘‘ÿA‘‘ÿ>ÿ;ŽŽÿ:ŽŽÿ7ŒŒÿ4ŠŠÿ1ˆˆÿ.‡‡ÿ,††ÿ*„„ÿ'ƒƒÿ%‚‚ÿ$ÿ €€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿ{{ÿ$„„ÿ$„„ÿ$„„ÿ#……ÿ#„„ÿ"„„ÿ!ƒƒÿ!„„ÿ!ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!„„ÿ"„„ÿ#„„ÿ"„„ÿ"„„ÿ#……ÿ#……ÿ#……ÿ#……ÿ#……ÿ$……ÿ$……ÿ%……ÿ%……ÿ%……ÿ%……ÿ%……ÿ&……ÿ&……ÿ&……ÿ&……ÿ&……ÿ&……ÿ&……ÿ&††ÿ&‡‡ÿ%‡‡ÿ%ˆˆÿ%‰‰ÿ%ŠŠÿ$‹‹ÿ#ŒŒÿ#ÿ#ÿ#ÿ"’’ÿ!””ÿ!——ÿššÿœœÿŸŸÿ££ÿ¦¦ÿªªÿ­­ÿ±±ÿ¶¶ÿººÿ¾¾ÿÃÂÿÇÆÿÌËÿÑÐÿÖÖÿÜÛÿààÿãâþ èçòööÕüüÌüüÎüüÏüüÏüüÎýüÊûûÇ ññÖââõÛÛÿ×ÕÿÐÏÿÈÇÿÀÀÿ¹¹ÿ ²²ÿ#««ÿ&¤¤ÿ(žžÿ+™™ÿ.””ÿ0ÿ/ŒŒÿ-‡‡ÿ.††ÿ0……ÿ2††ÿ5††ÿ8‡‡ÿ:ˆˆÿ;‰‰ÿ<ŠŠÿ?ŒŒÿBÿDÿDÿDÿEÿG‘‘ÿI’’ÿK““ÿM””ÿP––ÿR——ÿS˜˜ÿS˜˜ý?‹‹Òoo`jj ssT7ŠŠãC’’ÿB‘‘ÿ?ÿ<ÿ:ŽŽÿ8ÿ5‹‹ÿ2‰‰ÿ/ˆˆÿ,††ÿ+……ÿ(ƒƒÿ%‚‚ÿ$ÿ"€€ÿÿ}}ÿ||ÿ{{ÿzzÿyyÿxxÿ~~ÿ&„„ÿ%„„ÿ$……ÿ$……ÿ#……ÿ"„„ÿ"„„ÿ!„„ÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ ƒƒÿ!ƒƒÿ ƒƒÿ!„„ÿ!„„ÿ ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ"„„ÿ#„„ÿ#„„ÿ#„„ÿ#……ÿ#……ÿ#……ÿ#……ÿ$††ÿ$††ÿ$††ÿ%††ÿ%††ÿ%††ÿ%……ÿ&……ÿ%……ÿ&……ÿ&……ÿ&……ÿ&……ÿ&††ÿ&††ÿ&††ÿ&††ÿ&††ÿ&‡‡ÿ&‡‡ÿ%‡‡ÿ%ˆˆÿ%‰‰ÿ%ŠŠÿ$‹‹ÿ$ŒŒÿ$ÿ#ŽŽÿ#ÿ"’’ÿ"””ÿ!––ÿ ™™ÿ››ÿžžÿ¡¡ÿ¥¥ÿ¨¨ÿ¬¬ÿ°°ÿ´´ÿ¸¸ÿ¼¼ÿÁÁÿÆÅÿÊÊÿÐÏÿÕÔÿÛÚÿßßÿâáþèçñööÔüüÊüüÌüüÍüüÎýüÌüüÉûûÆ óñÕáàõÛÚÿÖÕÿÎÍÿÇÆÿÀ¿ÿ¸¸ÿ!°°ÿ%ªªÿ'££ÿ*ÿ-˜˜ÿ0””ÿ1ÿ/ŠŠÿ/ˆˆÿ1‡‡ÿ4ˆˆÿ7ˆˆÿ9ˆˆÿ<‰‰ÿ<ŠŠÿ>‹‹ÿAÿCŽŽÿFÿDÿEÿG‘‘ÿI’’ÿJ’’ÿM””ÿO••ÿQ––ÿS——ÿT˜˜ÿN““î&||“hh'ÿÿUUrrG5ˆˆÚD’’þB‘‘ÿ@‘‘ÿ=ÿ;ŽŽÿ9ÿ6ŒŒÿ3ŠŠÿ0ˆˆÿ-‡‡ÿ+……ÿ)„„ÿ&‚‚ÿ$ÿ"ÿÿ}}ÿ||ÿ{{ÿzzÿzzÿyyÿÿ'„„ÿ%……ÿ$……ÿ$……ÿ$……ÿ#„„ÿ"„„ÿ!„„ÿ!„„ÿ!ƒƒÿ!ƒƒÿ!„„ÿ!„„ÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!„„ÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ"„„ÿ"„„ÿ#„„ÿ#„„ÿ#……ÿ#„„ÿ#„„ÿ$……ÿ$††ÿ$††ÿ$††ÿ$††ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ&……ÿ&††ÿ&††ÿ&††ÿ&††ÿ&††ÿ&††ÿ'††ÿ&††ÿ&††ÿ&††ÿ&††ÿ'‡‡ÿ'‡‡ÿ&‡‡ÿ&‡‡ÿ&ˆˆÿ&‰‰ÿ%‰‰ÿ%‹‹ÿ$ŒŒÿ$ÿ$ŽŽÿ#ÿ#’’ÿ"””ÿ"––ÿ!˜˜ÿ ššÿÿ  ÿ££ÿ¦¦ÿªªÿ­­ÿ²²ÿ¶¶ÿ»ºÿ¿¿ÿÃÃÿÉÈÿÎÍÿÔÒÿÚÙÿÞÝÿààþççñõõÓûûÈüüÊýüËýüÌüüÊüüÇûûÄ ññÓáàôÚÙÿÕÓÿÍÌÿÆÅÿ¾¾ÿ ··ÿ#°°ÿ'¨¨ÿ)¢¢ÿ,ÿ0˜˜ÿ2””ÿ1ÿ0‹‹ÿ3ŠŠÿ6ŠŠÿ8‰‰ÿ;ŠŠÿ>‹‹ÿ>‹‹ÿ@ŒŒÿCŽŽÿEÿGÿEÿFÿI’’ÿJ’’ÿL““ÿN••ÿQ––ÿS——ÿT˜˜ÿR——ü;‰‰ÅkkS__tt.4‡‡ÄC’’ýC’’ÿA‘‘ÿ>ÿ;ŽŽÿ9ÿ6ŒŒÿ4ŠŠÿ1‰‰ÿ/‡‡ÿ,††ÿ)„„ÿ'ƒƒÿ%‚‚ÿ"ÿ ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿzzÿ"‚‚ÿ(……ÿ&……ÿ%……ÿ%……ÿ%……ÿ$……ÿ#……ÿ"„„ÿ"„„ÿ"„„ÿ"„„ÿ!„„ÿ!„„ÿ!„„ÿ!„„ÿ!„„ÿ!ƒƒÿ!„„ÿ!„„ÿ!„„ÿ"„„ÿ"„„ÿ"„„ÿ#„„ÿ#„„ÿ#„„ÿ#„„ÿ$……ÿ$……ÿ%††ÿ%††ÿ%††ÿ$††ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ&††ÿ&††ÿ&‡‡ÿ&††ÿ&††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ&ˆˆÿ&ˆˆÿ&ˆˆÿ&‰‰ÿ%ŠŠÿ%‹‹ÿ%ŒŒÿ$ŽŽÿ$ÿ#‘‘ÿ#““ÿ"••ÿ#——ÿ#ššÿ"ÿ!  ÿ!££ÿ §§ÿ ««ÿ¯¯ÿ³³ÿ¸¸ÿ¼¼ÿ¿¿ÿÄÄÿÉÈÿÏÍÿÔÓÿÙØÿÞÝÿáàþèçðööÐûûÆüüÈüüÉýüÉüüÈüüÅýüÁ ñðÑßÞóÙ×ÿÓÒÿÌËÿÄÄÿ ½½ÿ"µµÿ%­­ÿ(§§ÿ,¢¢ÿ0ÿ4™™ÿ3““ÿ2ŽŽÿ4ŒŒÿ6ŒŒÿ9‹‹ÿ<‹‹ÿ?ŒŒÿ@ÿCŽŽÿEÿGÿGÿFÿH‘‘ÿJ’’ÿL““ÿN••ÿP••ÿR––ÿT˜˜ÿU˜˜ÿK‘‘é ww†bbxx&3ˆˆ½D’’ýD““ÿA‘‘ÿ?ÿ<ÿ:ÿ7ŒŒÿ5ŠŠÿ2‰‰ÿ/ˆˆÿ-††ÿ*……ÿ'ƒƒÿ%‚‚ÿ#ÿ ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿ{{ÿ%„„ÿ(††ÿ'††ÿ&††ÿ&……ÿ%……ÿ$……ÿ$……ÿ#„„ÿ"„„ÿ"„„ÿ"„„ÿ!ƒƒÿ!„„ÿ"„„ÿ"„„ÿ"„„ÿ!„„ÿ"„„ÿ"„„ÿ"„„ÿ"„„ÿ#„„ÿ#……ÿ#„„ÿ#„„ÿ#„„ÿ$……ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ'††ÿ'††ÿ'††ÿ'††ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'††ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ&ˆˆÿ&‰‰ÿ'‰‰ÿ&ŠŠÿ&ŒŒÿ'ÿ)ÿ)’’ÿ+””ÿ+––ÿ,™™ÿ,››ÿ+ÿ+  ÿ*¢¢ÿ)¥¥ÿ(©©ÿ&¬¬ÿ$°°ÿ#´´ÿ!¸¸ÿ¼¼ÿÀÀÿÅÄÿÊÉÿÏÎÿÔÓÿÙØÿÞÝÿáàþèçïõõÏüüÄüüÆüüÈüüÈüüÆýýÂüü¾ ññÍÞÝòØ×ÿÒÑÿ ËÊÿ ÃÃÿ!»»ÿ$³³ÿ'­­ÿ+§§ÿ/¡¡ÿ4ÿ3——ÿ3‘‘ÿ5ÿ8ŽŽÿ;ÿ=ÿ@ÿBŽŽÿDÿGÿH‘‘ÿFÿHÿJ’’ÿK““ÿN••ÿO••ÿR––ÿT˜˜ÿT™™ÿT˜˜û:††¿kkGffww *ƒƒ­C‘‘úE““ÿB’’ÿ@‘‘ÿ=ÿ:ŽŽÿ8ŒŒÿ6‹‹ÿ3‰‰ÿ0ˆˆÿ.††ÿ+……ÿ(ƒƒÿ%‚‚ÿ#ÿ!€€ÿ~~ÿ}}ÿ||ÿ{{ÿzzÿ||ÿ'……ÿ)††ÿ(‡‡ÿ&††ÿ&††ÿ%††ÿ$……ÿ%……ÿ$……ÿ#„„ÿ"„„ÿ"„„ÿ!„„ÿ"„„ÿ#……ÿ#„„ÿ"„„ÿ"„„ÿ"„„ÿ#……ÿ#„„ÿ#„„ÿ#„„ÿ$……ÿ$……ÿ$……ÿ$……ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(‡‡ÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ(ˆˆÿ)‰‰ÿ+‹‹ÿ.ÿ0ŽŽÿ1ÿ1‘‘ÿ2““ÿ2••ÿ2••ÿ1––ÿ1˜˜ÿ0™™ÿ/››ÿ/ÿ.ŸŸÿ,¢¢ÿ+¤¤ÿ)§§ÿ(««ÿ&®®ÿ$²²ÿ"¶¶ÿ ººÿ¾¾ÿÃÃÿÈÇÿÍÌÿÓÑÿØ×ÿÝÜÿàßþçæîöõÍüûÃüüÅüüÆüüÆüüÄýüÀüü¼ ññÌÞÞñ×Öÿ!ÑÐÿ!ÊÉÿ"ÂÂÿ$ººÿ'³³ÿ*­­ÿ/§§ÿ4¢¢ÿ5œœÿ3••ÿ5‘‘ÿ9ÿ<ÿ?ÿAÿCÿFÿH‘‘ÿH‘‘ÿGÿI’’ÿK““ÿM””ÿP••ÿQ––ÿT——ÿU˜˜ÿU˜˜ÿJ‘‘åvvgg pp!}} A‘‘÷F””ÿC““ÿ@‘‘ÿ>ÿ;ŽŽÿ9ÿ6‹‹ÿ4ŠŠÿ1‰‰ÿ/‡‡ÿ,††ÿ(„„ÿ&‚‚ÿ$€€ÿ"€€ÿ ÿ~~ÿ}}ÿ||ÿ{{ÿ}}ÿ(††ÿ*ˆˆÿ)ˆˆÿ'‡‡ÿ&††ÿ&……ÿ%††ÿ&††ÿ%††ÿ#„„ÿ"„„ÿ#„„ÿ#„„ÿ#……ÿ#……ÿ#„„ÿ"„„ÿ#……ÿ#……ÿ#……ÿ#……ÿ#……ÿ$……ÿ%††ÿ%……ÿ$……ÿ$††ÿ%††ÿ%††ÿ&††ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ)‡‡ÿ)ˆˆÿ*‰‰ÿ-ŠŠÿ0ŒŒÿ2ŽŽÿ4ÿ5ÿ5‘‘ÿ5‘‘ÿ5’’ÿ4““ÿ4””ÿ3””ÿ3••ÿ3––ÿ2——ÿ1˜˜ÿ1ššÿ/œœÿ/žžÿ-  ÿ,££ÿ+¦¦ÿ)©©ÿ'¬¬ÿ%°°ÿ$´´ÿ!¸¸ÿ½½ÿÁÁÿÆÆÿËÊÿÑÐÿ×ÕÿÜÛÿßÞþæåîöõÌýýÀüüÂüüÃüüÄýýÁýý½üüº ññÊÝÝñ ÕÔÿ"ÐÏÿ#ÉÉÿ$ÁÁÿ&¹¹ÿ)²²ÿ.¬¬ÿ3§§ÿ3ŸŸÿ2˜˜ÿ6••ÿ9““ÿ>’’ÿ@‘‘ÿC‘‘ÿF’’ÿG’’ÿI‘‘ÿGÿI‘‘ÿK““ÿM””ÿO••ÿQ––ÿS——ÿT˜˜ÿV˜˜ÿR––÷1ƒƒ¯gg@ nn"~~›A÷G””ÿD““ÿA‘‘ÿ?ÿ<ÿ9ÿ7ŒŒÿ4ŠŠÿ2‰‰ÿ/‡‡ÿ-††ÿ*……ÿ'ƒƒÿ$ÿ#€€ÿ!ÿ~~ÿ}}ÿ||ÿ||ÿ}}ÿ)‡‡ÿ+‰‰ÿ)ˆˆÿ'‡‡ÿ'††ÿ'††ÿ'††ÿ&††ÿ%††ÿ$……ÿ$……ÿ#……ÿ#……ÿ#……ÿ#……ÿ#……ÿ#……ÿ$……ÿ$††ÿ#……ÿ#……ÿ#……ÿ%††ÿ%††ÿ%††ÿ%……ÿ%††ÿ%††ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ*ˆˆÿ,‰‰ÿ/‹‹ÿ2ŒŒÿ4ŽŽÿ6ÿ7ÿ7ÿ6ÿ5ÿ5ÿ5ÿ5‘‘ÿ5’’ÿ5’’ÿ5““ÿ4““ÿ3””ÿ4••ÿ3––ÿ3˜˜ÿ1™™ÿ1ššÿ0ÿ.ŸŸÿ-¡¡ÿ,¤¤ÿ*§§ÿ(ªªÿ'®®ÿ%²²ÿ#··ÿ »»ÿÀÀÿÄÄÿÊÉÿÐÏÿÕÕÿÛÚÿÞÝþæåîööÊýü¿üüÁüüÂýüÁýý¿ýý»ýý¶ ðïÈÛÚð!ÔÓÿ$ÐÏÿ%ÈÈÿ'ÀÀÿ)¹¸ÿ-±±ÿ1¬¬ÿ2¤¤ÿ3ÿ7ššÿ;——ÿ?••ÿA““ÿE””ÿG““ÿI““ÿH’’ÿH‘‘ÿJ’’ÿL““ÿO••ÿQ––ÿR——ÿT——ÿU˜˜ÿT˜˜ýBÓoogiiqq#~~AõH••ÿE””ÿB’’ÿ@ÿ<ÿ:ÿ8ŒŒÿ5‹‹ÿ3‰‰ÿ0ˆˆÿ.‡‡ÿ+……ÿ(„„ÿ%‚‚ÿ#€€ÿ!ÿ~~ÿ}}ÿ||ÿ{{ÿ~~ÿ+ˆˆÿ+‰‰ÿ*ˆˆÿ)ˆˆÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ'††ÿ&††ÿ&††ÿ%……ÿ$……ÿ$……ÿ#„„ÿ$……ÿ$……ÿ$……ÿ$††ÿ$††ÿ$……ÿ$……ÿ$……ÿ%……ÿ%††ÿ%††ÿ&††ÿ&††ÿ&‡‡ÿ&‡‡ÿ'ˆˆÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(ˆˆÿ)ˆˆÿ)‡‡ÿ)ˆˆÿ)ˆˆÿ)‡‡ÿ*ˆˆÿ+‰‰ÿ.ŠŠÿ2ŒŒÿ4ŽŽÿ6ŽŽÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ6ÿ6ÿ6ÿ6ÿ7‘‘ÿ6’’ÿ6’’ÿ6’’ÿ5““ÿ5””ÿ5••ÿ5––ÿ4——ÿ3˜˜ÿ2ššÿ1œœÿ0žžÿ.  ÿ-££ÿ+¦¦ÿ*©©ÿ(­­ÿ&±±ÿ$µµÿ!ººÿ¾¾ÿÃÃÿÉÈÿÏÎÿÕÓÿÚÙÿÝÜþååíööÉýü½ýý¾þý¿ýý¿ýý½ýý¹üü´ ïîÆÛÙï$ÔÓÿ'ÏÎÿ(ÈÇÿ*¿¿ÿ-··ÿ/°°ÿ1©©ÿ2¢¢ÿ7žžÿ<››ÿ?™™ÿB––ÿE––ÿH••ÿI””ÿI““ÿJ““ÿL““ÿN••ÿQ––ÿR——ÿT——ÿU˜˜ÿV™™ÿL““è$zzŒbb'ÿÿuu "||@ðH––ÿF””ÿC““ÿA‘‘ÿ=ÿ;ŽŽÿ9ÿ6‹‹ÿ3‰‰ÿ1ˆˆÿ.‡‡ÿ,……ÿ)„„ÿ'‚‚ÿ#ÿ!€€ÿ ÿ}}ÿ||ÿ{{ÿÿ,ˆˆÿ,‰‰ÿ*ˆˆÿ*ˆˆÿ)ˆˆÿ(‡‡ÿ'‡‡ÿ'‡‡ÿ'††ÿ&††ÿ&††ÿ%††ÿ%……ÿ$……ÿ%……ÿ$††ÿ%††ÿ%††ÿ%††ÿ%……ÿ%††ÿ%††ÿ%††ÿ%††ÿ&††ÿ'††ÿ'‡‡ÿ&‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ&‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ*ˆˆÿ-ŠŠÿ0ŒŒÿ3ÿ5ŽŽÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ7ÿ7‘‘ÿ7‘‘ÿ7’’ÿ7’’ÿ6““ÿ6““ÿ6““ÿ6””ÿ6••ÿ5––ÿ5˜˜ÿ4™™ÿ3››ÿ1œœÿ0ŸŸÿ.¡¡ÿ,¤¤ÿ+§§ÿ)««ÿ'¯¯ÿ$´´ÿ"¸¸ÿ ½½ÿÂÂÿÈÇÿÎÍÿÔÒÿÙØÿÝÜþäãíöôÇüü»ýý¼ýý½ýý½ýý»ýý¶üü± ðíÃÚÙï%ÓÒþ)ÎÍÿ+ÆÆÿ-¾¾ÿ/¶¶ÿ0®®ÿ2§§ÿ7££ÿ<ŸŸÿ?œœÿB™™ÿF˜˜ÿI——ÿI••ÿJ””ÿK””ÿO••ÿP––ÿR——ÿT˜˜ÿU˜˜ÿW™™ÿT——ø5ƒƒ´ kkEqq wwk<çH––ÿG””ÿD““ÿB’’ÿ?ÿ<ÿ9ÿ7‹‹ÿ4ŠŠÿ2‰‰ÿ/‡‡ÿ,††ÿ*„„ÿ(ƒƒÿ%‚‚ÿ"€€ÿ ÿ}}ÿ||ÿ}}ÿÿ-‰‰ÿ,‰‰ÿ,‰‰ÿ+ˆˆÿ*ˆˆÿ)ˆˆÿ(‡‡ÿ)‡‡ÿ(‡‡ÿ'††ÿ&††ÿ&‡‡ÿ&††ÿ&††ÿ%††ÿ%††ÿ%††ÿ%††ÿ%††ÿ&††ÿ&††ÿ%††ÿ&‡‡ÿ&††ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ)‰‰ÿ)ˆˆÿ)ˆˆÿ*‰‰ÿ.‹‹ÿ2ŒŒÿ4ŽŽÿ6ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ8‘‘ÿ8‘‘ÿ8’’ÿ7’’ÿ7’’ÿ7’’ÿ7““ÿ7““ÿ7””ÿ6••ÿ6——ÿ5˜˜ÿ4™™ÿ2››ÿ1žžÿ/  ÿ-££ÿ,¦¦ÿ*ªªÿ(®®ÿ%²²ÿ#¶¶ÿ!»»ÿÁÁÿÆÆÿÌÌÿÓÒÿÙØÿÛÚþãâìôôÆüü¹ýýºýý»ýý»ýý¸ýý³üü¯ïïÁÚÚî'ÒÒþ+ÍÌÿ.ÆÅÿ/½½ÿ/´´ÿ1­­ÿ6§§ÿ;££ÿ?ŸŸÿCÿG››ÿI™™ÿI––ÿK••ÿM––ÿP——ÿR——ÿT˜˜ÿU™™ÿV™™ÿW˜˜þHÛppqffmmzz`=ŽŽåI––ÿH••ÿE““ÿB’’ÿ@‘‘ÿ=ÿ:ÿ8‹‹ÿ5ŠŠÿ3‰‰ÿ0ˆˆÿ-††ÿ+……ÿ)„„ÿ'ƒƒÿ#ÿ!ÿ~~ÿ}}ÿ||ÿ!ÿ.‰‰ÿ-‰‰ÿ,‰‰ÿ+‰‰ÿ+ˆˆÿ*ˆˆÿ)ˆˆÿ)ˆˆÿ(‡‡ÿ'††ÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ&††ÿ&††ÿ&††ÿ%††ÿ&††ÿ&††ÿ'‡‡ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ)‰‰ÿ+‰‰ÿ.‹‹ÿ3ŽŽÿ6ÿ6ÿ6ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9ÿ9ÿ9ÿ:ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ8‘‘ÿ9‘‘ÿ9’’ÿ8’’ÿ8““ÿ8““ÿ7““ÿ7••ÿ7––ÿ6––ÿ4˜˜ÿ3ššÿ2œœÿ0ŸŸÿ/¡¡ÿ-¥¥ÿ+©©ÿ)¬¬ÿ'°°ÿ$µµÿ"ººÿ ¿¿ÿÅÅÿÌËÿ ÒÑÿ ×ÖÿÛÙþãáëôóÄýý¶ýý¸ýý¹ýý¸ýýµýý°üü¬ííÀ ÙØî*ÑÐþ/ÌÌÿ0ÄÄÿ/ººÿ1³³ÿ5­­ÿ9§§ÿ?¤¤ÿC  ÿGžžÿI››ÿI˜˜ÿK——ÿN˜˜ÿQ˜˜ÿT˜˜ÿU™™ÿV™™ÿWššÿO““í+}}šhh1UUttD3‰‰ÎH••þI––ÿF””ÿC’’ÿ@‘‘ÿ>ÿ;ŽŽÿ8ŒŒÿ6‹‹ÿ3ŠŠÿ1ˆˆÿ/‡‡ÿ,††ÿ*„„ÿ'ƒƒÿ$ÿ"€€ÿ ~~ÿ~~ÿ~~ÿ#‚‚ÿ/‰‰ÿ.‰‰ÿ,‰‰ÿ+‰‰ÿ,‰‰ÿ+‰‰ÿ*ˆˆÿ)ˆˆÿ)‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ&††ÿ&††ÿ'††ÿ'‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ&††ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)‰‰ÿ)‰‰ÿ+ŠŠÿ.‹‹ÿ2ÿ6ÿ6ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8ÿ8ÿ9ÿ9‘‘ÿ:‘‘ÿ:ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ9’’ÿ9’’ÿ9’’ÿ9““ÿ8””ÿ8––ÿ7——ÿ5˜˜ÿ4™™ÿ3››ÿ2žžÿ0¡¡ÿ.¤¤ÿ,§§ÿ*««ÿ'¯¯ÿ%´´ÿ"¹¹ÿ ¾¾ÿÄÄÿËÊÿ ÑÐÿ ×ÕÿÚÙþâáëõôÂýý´ýý¶ýý·ýý¶ýý³ýý®ûû© ìì½!ÙØì-ÐÐþ1ËÊÿ0ÂÂÿ1ººÿ4²²ÿ9­­ÿ>¨¨ÿC¤¤ÿG¡¡ÿGœœÿIššÿM™™ÿPššÿR™™ÿTššÿVššÿXššÿS——ö4„„³ iiKqq oo (¥E““ùI––ÿG••ÿD““ÿA‘‘ÿ?ÿ<ÿ:ÿ7ŒŒÿ4ŠŠÿ3‰‰ÿ/‡‡ÿ,††ÿ*„„ÿ(ƒƒÿ&‚‚ÿ#€€ÿ ÿÿ~~ÿ$ƒƒÿ0‹‹ÿ.ŠŠÿ-‰‰ÿ,‰‰ÿ,‰‰ÿ+‰‰ÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ)ˆˆÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ*‰‰ÿ,ŠŠÿ1ÿ5ÿ7ÿ7ÿ6ÿ7ÿ8ÿ8ÿ8ÿ9ÿ9ÿ9‘‘ÿ9ÿ9ÿ:ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ<‘‘ÿ<’’ÿ<‘‘ÿ<’’ÿ<‘‘ÿ;‘‘ÿ;’’ÿ:‘‘ÿ:‘‘ÿ;’’ÿ:’’ÿ:““ÿ9““ÿ9••ÿ9––ÿ8——ÿ7——ÿ6™™ÿ5››ÿ3žžÿ2¡¡ÿ/££ÿ-¦¦ÿ+ªªÿ(®®ÿ%³³ÿ"¸¸ÿ ¾¾ÿ ÃÃÿ ÊÉÿ!ÐÏÿ!ÖÕÿÙØþâáêôóÁüü³ýýµýýµýý´ýý±ýý«ýý¥íí¹#×Öë.ÏÎþ1ÈÈÿ2ÁÁÿ5¹¹ÿ9³³ÿ=­­ÿB¨¨ÿE££ÿFŸŸÿIÿNœœÿQ››ÿR››ÿU››ÿX››ÿW˜˜ü@‹‹Íoogffoo#~~‡E’’õJ––ÿH••ÿE””ÿC’’ÿ@ÿ=ÿ;ŽŽÿ9ÿ6‹‹ÿ3‰‰ÿ1ˆˆÿ-††ÿ+……ÿ)„„ÿ'ƒƒÿ$ÿ"ÿ~~ÿ~~ÿ%„„ÿ0ŒŒÿ.‹‹ÿ.ŠŠÿ-ŠŠÿ,‰‰ÿ+‰‰ÿ+ˆˆÿ+ˆˆÿ+ˆˆÿ*ˆˆÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ)‰‰ÿ*‰‰ÿ+ŠŠÿ0ŒŒÿ4ŽŽÿ5ÿ7ÿ7ÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ:ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ<‘‘ÿ<’’ÿ<’’ÿ<’’ÿ<’’ÿ<’’ÿ<’’ÿ=’’ÿ=’’ÿ<’’ÿ<‘‘ÿ<’’ÿ<’’ÿ<““ÿ;““ÿ;““ÿ;””ÿ:””ÿ9••ÿ9––ÿ8——ÿ7™™ÿ6››ÿ5ÿ2  ÿ0¢¢ÿ.¥¥ÿ,©©ÿ)­­ÿ&²²ÿ#··ÿ!¼¼ÿ ÂÂÿ ÉÈÿ"ÐÎÿ"ÕÔÿÙØþàßëóñÁýý±ýý²ýý³ýý²ýý®ýý¨ûû¢ëê¸$ÖÕë.ÍÌþ3ÇÇÿ6ÀÀÿ9¹¹ÿ=³³ÿ?¬¬ÿA¦¦ÿF££ÿJ¡¡ÿNžžÿQÿTœœÿVœœÿWššþIàuu„cc$qq !~~qD’’ïK——ÿH••ÿF””ÿC““ÿA‘‘ÿ?ÿ<ŽŽÿ:ÿ7ŒŒÿ4ŠŠÿ1ˆˆÿ.‡‡ÿ,††ÿ+„„ÿ(ƒƒÿ%‚‚ÿ"€€ÿ €€ÿ€€ÿ%……ÿ1‹‹ÿ0‹‹ÿ/‹‹ÿ.ŠŠÿ-‰‰ÿ,‰‰ÿ,‰‰ÿ+‰‰ÿ+ˆˆÿ)ˆˆÿ(‡‡ÿ)‡‡ÿ)‡‡ÿ)ˆˆÿ(ˆˆÿ(‡‡ÿ(‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ-‹‹ÿ2ŽŽÿ5ÿ6ÿ7‘‘ÿ8‘‘ÿ7ÿ8‘‘ÿ8‘‘ÿ8ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ9ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ<‘‘ÿ<‘‘ÿ<’’ÿ=’’ÿ=““ÿ=““ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=““ÿ=““ÿ<““ÿ<““ÿ;““ÿ:““ÿ;””ÿ9––ÿ9——ÿ7˜˜ÿ6ššÿ5œœÿ3ŸŸÿ1¢¢ÿ/¥¥ÿ-©©ÿ*­­ÿ'±±ÿ$¶¶ÿ"¼¼ÿ ÂÂÿ!ÉÈÿ"ÏÎÿ#ÕÔÿ Ø×þßÞë ññÀüü¯ýý°ýý±ýý°þý«ýý¤ûûŸëé¶&ÕÔê2ÌÌþ8ÇÆÿ:ÀÀÿ=¹¹ÿ=°°ÿ@««ÿE§§ÿJ¤¤ÿN¡¡ÿQŸŸÿUžžÿXÿP––ð+}} ii:ÿÿUUvvX=ßK——ÿI••ÿG””ÿD““ÿB’’ÿ@‘‘ÿ>ÿ;ŽŽÿ9ÿ6‹‹ÿ3‰‰ÿ/ˆˆÿ-††ÿ+……ÿ)ƒƒÿ%‚‚ÿ#ÿ!ÿÿ$„„ÿ1‹‹ÿ1ŒŒÿ0‹‹ÿ.‹‹ÿ-ŠŠÿ-ŠŠÿ-‰‰ÿ,‰‰ÿ+‰‰ÿ+ˆˆÿ)ˆˆÿ)‡‡ÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(‡‡ÿ(ˆˆÿ)ˆˆÿ(‡‡ÿ(‡‡ÿ)ˆˆÿ)ˆˆÿ(ˆˆÿ(ˆˆÿ)‰‰ÿ*‰‰ÿ)‰‰ÿ)‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ,ŠŠÿ0ÿ4ÿ5ÿ6ÿ7ÿ8‘‘ÿ7‘‘ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ8ÿ9‘‘ÿ9’’ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ;’’ÿ;’’ÿ<’’ÿ<’’ÿ<’’ÿ<’’ÿ<’’ÿ=’’ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=““ÿ<““ÿ;““ÿ;““ÿ;””ÿ:••ÿ9——ÿ8˜˜ÿ7™™ÿ5œœÿ3žžÿ1  ÿ/¤¤ÿ-¨¨ÿ*¬¬ÿ'°°ÿ$¶¶ÿ"»»ÿ!ÁÁÿ"ÈÇÿ#ÎÍÿ#ÔÓÿ ×ÖþÞÝë ñð¾ýü¬ýý®ýý¯ýý­ýý§ýý¡ýûœéé³(ÔÔé5ËËþ;ÇÇÿ<¾¾ÿ<¶¶ÿA°°ÿD««ÿI§§ÿM¥¥ÿQ¢¢ÿU  ÿRšš÷:ŠŠ¼rrUÿÿÿÿÿÿss@;ŒŒÑL——ÿJ––ÿG••ÿE““ÿC’’ÿA‘‘ÿ>ÿ<ÿ:ŽŽÿ7ŒŒÿ4ŠŠÿ1‰‰ÿ.‡‡ÿ,††ÿ*„„ÿ'‚‚ÿ$ÿ"ÿ€€ÿ#‚‚ÿ2ŒŒÿ2ÿ1ŒŒÿ/‹‹ÿ-ŠŠÿ-ŠŠÿ-‰‰ÿ-‰‰ÿ,‰‰ÿ,‰‰ÿ+‰‰ÿ*ˆˆÿ*ˆˆÿ)ˆˆÿ)‡‡ÿ)‡‡ÿ)‡‡ÿ)‡‡ÿ)‡‡ÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*ŠŠÿ-‹‹ÿ2ŽŽÿ5ÿ5ÿ6ÿ7‘‘ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ:’’ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ<’’ÿ<’’ÿ<’’ÿ=’’ÿ=’’ÿ=’’ÿ=““ÿ=““ÿ=““ÿ=““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>’’ÿ=’’ÿ=’’ÿ<’’ÿ;““ÿ;””ÿ;””ÿ:••ÿ9––ÿ8——ÿ7™™ÿ6››ÿ4ÿ2ŸŸÿ0££ÿ-§§ÿ+««ÿ(°°ÿ%µµÿ#ººÿ"ÀÀÿ"ÆÆÿ#ÍÌÿ$ÓÒÿ!ÖÕþÞÝé ñð¼ýýªýý¬ýý¬ýý©ýý¥ýýŸûû˜êé¯,ÒÒç8ÊÊþ;ÄÄÿ=½½ÿ@¶¶ÿC±±ÿH««ÿL¨¨ÿP¥¥ÿP  û<‘‘Ëyyk„„ÌÌÿÿÿÿÿÿÿÿww/:ŒŒÄM––ÿL——ÿI••ÿG””ÿD’’ÿA‘‘ÿ?ÿ=ÿ;ŽŽÿ8ŒŒÿ6‹‹ÿ4‰‰ÿ0ˆˆÿ-††ÿ+……ÿ)ƒƒÿ%‚‚ÿ#ÿ €€ÿ#ƒƒÿ2ÿ3ŽŽÿ2ŒŒÿ0‹‹ÿ/‹‹ÿ.ŠŠÿ-ŠŠÿ-ŠŠÿ,ŠŠÿ,‰‰ÿ,‰‰ÿ+ˆˆÿ*ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ*ˆˆÿ*‰‰ÿ*‰‰ÿ)‰‰ÿ)‰‰ÿ*ŠŠÿ+ŠŠÿ*ŠŠÿ-‹‹ÿ3ŽŽÿ5ÿ5ÿ6ÿ7ÿ7‘‘ÿ7‘‘ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ9’’ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ<’’ÿ<’’ÿ<’’ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ?““ÿ?““ÿ?””ÿ?””ÿ?””ÿ?””ÿ?““ÿ?““ÿ?““ÿ>““ÿ>’’ÿ>““ÿ=’’ÿ<““ÿ;““ÿ;””ÿ:””ÿ9••ÿ8——ÿ7™™ÿ6ššÿ4œœÿ2ŸŸÿ1¢¢ÿ.¦¦ÿ+ªªÿ(¯¯ÿ%³³ÿ$¹¹ÿ#¿¿ÿ#ÅÅÿ$ÌËÿ%ÑÑÿ"ÕÔþÝÛé ññ¹ýý¨ýý©þý¨ýý§ýý¢ýý›ýû•èè­,ÑÐæ9ÉÈþ>ÄÄÿA¾¾ÿC··ÿF±±ÿJ­­ÿM¨¨ý@œœÜ††””+åå ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿrr-„„¢I””ùL——ÿJ––ÿH••ÿE““ÿB’’ÿ@‘‘ÿ>ÿ<ŽŽÿ9ÿ7‹‹ÿ5ŠŠÿ2‰‰ÿ.‡‡ÿ,……ÿ*„„ÿ'ƒƒÿ$‚‚ÿ!ÿ#„„ÿ2ÿ4ŽŽÿ3ÿ2ŒŒÿ0‹‹ÿ/‹‹ÿ.‹‹ÿ.ŠŠÿ.ŠŠÿ-‰‰ÿ-‰‰ÿ,‰‰ÿ+ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ)ˆˆÿ)ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*‰‰ÿ*ˆˆÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ)‰‰ÿ*‰‰ÿ+ŠŠÿ+‹‹ÿ/ŒŒÿ3ŽŽÿ5ÿ6ÿ6ÿ7ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ:’’ÿ:’’ÿ;’’ÿ<““ÿ<““ÿ<““ÿ<““ÿ<““ÿ=““ÿ=““ÿ=““ÿ=““ÿ=““ÿ>““ÿ>““ÿ>““ÿ>““ÿ?““ÿ>““ÿ?““ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ@””ÿ@””ÿ@””ÿ@””ÿ?““ÿ?““ÿ?““ÿ?““ÿ>““ÿ<““ÿ<““ÿ<””ÿ;””ÿ:••ÿ9——ÿ8˜˜ÿ6ššÿ5œœÿ3ŸŸÿ1¢¢ÿ/¥¥ÿ,©©ÿ)®®ÿ&²²ÿ$··ÿ#½½ÿ$ÄÃÿ%ËÊÿ&ÑÐÿ#ÔÓþÛÚè ñï¸ýû¥ýý¥ýý¦ýý¤þýŸýý™ûû’èè¨.ÐÏå;ÉÉýAÄÄÿC½½ÿF··ÿG°°þ?§§å••–?ååÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿmm||{D‘‘îM——ÿJ––ÿI••ÿF””ÿD““ÿA‘‘ÿ?ÿ<ÿ:ÿ8‹‹ÿ6‹‹ÿ4‰‰ÿ0ˆˆÿ.††ÿ+……ÿ(„„ÿ&‚‚ÿ"‚‚ÿ$ƒƒÿ2ÿ5ÿ4ŽŽÿ3ÿ2ŒŒÿ1ŒŒÿ/‹‹ÿ/ŠŠÿ/ŠŠÿ.ŠŠÿ-ŠŠÿ-‰‰ÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ+ˆˆÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ+ŠŠÿ,ŠŠÿ0ÿ4ÿ5ÿ6ÿ6ÿ7ÿ8ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ:’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ<““ÿ<““ÿ=““ÿ=““ÿ=””ÿ=““ÿ=““ÿ>””ÿ>””ÿ>““ÿ>““ÿ?““ÿ>““ÿ?““ÿ?““ÿ?““ÿ?””ÿ@””ÿ@””ÿ@””ÿ?””ÿ?””ÿ@””ÿ@””ÿ@””ÿA””ÿA””ÿA””ÿA””ÿ@””ÿ?““ÿ?““ÿ?““ÿ?““ÿ>““ÿ>““ÿ<““ÿ<””ÿ<””ÿ:••ÿ9––ÿ8˜˜ÿ7ššÿ5œœÿ3ŸŸÿ1¢¢ÿ/¥¥ÿ,©©ÿ)­­ÿ&±±ÿ$¶¶ÿ#¼¼ÿ$ÄÃÿ%ÊÉÿ&ÐÏÿ#ÒÑþÛÚç ðî¶ýý¢ýý£ýý£ýý¡ýýœýý”ûûŽéç¥/ÐÏã>ÉÉýCÄÄÿF½½ÿ=²²í¥¥ª¬¬Wéé$ÿÿÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿUU3™™ÆÆÆ þþþ ÿÿÿmmww^>ãN˜˜ÿL––ÿI••ÿH••ÿF””ÿB’’ÿ@ÿ>ÿ;ŽŽÿ8ŒŒÿ6‹‹ÿ5ŠŠÿ2‰‰ÿ/‡‡ÿ,††ÿ*……ÿ'……ÿ$ƒƒÿ$ƒƒÿ3ÿ6ÿ5ŽŽÿ4ŽŽÿ3ÿ2ÿ0‹‹ÿ/‹‹ÿ/‹‹ÿ/ŠŠÿ.ŠŠÿ-ŠŠÿ-‰‰ÿ-‰‰ÿ,‰‰ÿ,‰‰ÿ+‰‰ÿ+ˆˆÿ+ˆˆÿ+ˆˆÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ+ŠŠÿ+ŠŠÿ,ŠŠÿ1ÿ5ÿ6ÿ6ÿ6ÿ6ÿ7‘‘ÿ8‘‘ÿ9‘‘ÿ9’’ÿ9’’ÿ9‘‘ÿ9’’ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ<““ÿ<““ÿ=““ÿ=““ÿ=””ÿ=””ÿ>””ÿ>””ÿ>””ÿ>””ÿ>””ÿ>““ÿ>““ÿ?““ÿ?””ÿ@””ÿ@””ÿ@””ÿ@””ÿ@””ÿ@””ÿ@””ÿ@””ÿ@””ÿA””ÿA””ÿA””ÿA””ÿA””ÿA””ÿ@””ÿ@““ÿ@““ÿ@““ÿ@““ÿ?““ÿ>““ÿ>””ÿ=””ÿ<””ÿ;••ÿ:——ÿ9˜˜ÿ7™™ÿ5››ÿ4žžÿ2¡¡ÿ/¤¤ÿ,¨¨ÿ)¬¬ÿ&°°ÿ%¶¶ÿ$¼¼ÿ$ÂÂÿ&ÉÈÿ'ÏÎÿ$ÒÑþÚØç ïîµýû ýý ýý ýýýý˜ýý’ûû‹èè¢1ÐÐá@ÇÇý?ÀÀñ!µµ¸ººoèè9ÿÿ(þþþþÿÿþþ þþÿÿÿÿÿÿÿÿÿÿUUH‘‘Ôéé þþþ ÿÿÿUUuuC=ŒŒÑN——ÿM——ÿJ––ÿI••ÿG””ÿD““ÿA‘‘ÿ?ÿ<ÿ9ÿ7‹‹ÿ5ŠŠÿ3‰‰ÿ0ˆˆÿ-††ÿ+††ÿ)††ÿ&ƒƒÿ$„„ÿ2ŒŒÿ6ÿ5ŽŽÿ4ŽŽÿ3ÿ3ÿ1ŒŒÿ0‹‹ÿ0‹‹ÿ/‹‹ÿ/ŠŠÿ/ŠŠÿ.ŠŠÿ-‰‰ÿ,‰‰ÿ-‰‰ÿ+‰‰ÿ+ˆˆÿ+ˆˆÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ+‰‰ÿ,‰‰ÿ-‰‰ÿ-ŠŠÿ,ŠŠÿ,ŠŠÿ1ÿ5ÿ6ÿ7ÿ7ÿ7ÿ7‘‘ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:’’ÿ:’’ÿ:‘‘ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ<““ÿ<““ÿ=““ÿ=““ÿ>””ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ@””ÿA••ÿA••ÿB••ÿA••ÿA••ÿA””ÿA””ÿA””ÿA””ÿA””ÿA””ÿA””ÿA””ÿA••ÿA••ÿA••ÿA••ÿA””ÿA””ÿA””ÿA””ÿ@““ÿ@““ÿ?““ÿ>””ÿ=””ÿ=••ÿ<––ÿ;––ÿ9˜˜ÿ7™™ÿ6ššÿ4ÿ2  ÿ/££ÿ-§§ÿ*««ÿ'°°ÿ%µµÿ$ººÿ%ÁÁÿ&ÈÇÿ'ÎÍÿ%ÑÐþØØç ïí³ýýýýýýýýšýý–ýýýýˆèèž0ÏÏØ#ÇÇÂËËììQÿÿ<ÿÿ0þþ&þþþþÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿ??U åòòþþþÿÿÿ oo'0ƒƒ®L––úN˜˜ÿL––ÿI••ÿH••ÿF““ÿB’’ÿ@ÿ=ÿ;ŽŽÿ8ŒŒÿ6ŠŠÿ4ŠŠÿ2‰‰ÿ/‡‡ÿ-††ÿ*……ÿ(……ÿ%„„ÿ2ŒŒÿ7ÿ6ÿ5ÿ4ŽŽÿ4ŽŽÿ3ÿ1ŒŒÿ1‹‹ÿ0‹‹ÿ/‹‹ÿ/ŠŠÿ/ŠŠÿ.ŠŠÿ-‰‰ÿ-ŠŠÿ,‰‰ÿ,‰‰ÿ+‰‰ÿ,‰‰ÿ,‰‰ÿ,‰‰ÿ,‰‰ÿ-‰‰ÿ-ŠŠÿ-ŠŠÿ-‹‹ÿ1ÿ5ÿ6ÿ6ÿ7ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ=““ÿ=““ÿ=””ÿ>””ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?••ÿ@••ÿ@••ÿ@••ÿA••ÿA••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿA••ÿA••ÿA••ÿA••ÿA””ÿA””ÿA••ÿA••ÿA••ÿB••ÿB••ÿB••ÿB••ÿB••ÿA””ÿA””ÿ@””ÿ@““ÿ@““ÿ?““ÿ?““ÿ=””ÿ=••ÿ<––ÿ;——ÿ:˜˜ÿ8˜˜ÿ6››ÿ4ÿ2ŸŸÿ0¢¢ÿ-¦¦ÿ*ªªÿ'¯¯ÿ%³³ÿ%¹¹ÿ%ÀÀÿ'ÇÆÿ(ÍÌÿ&ÐÏþØ×ç íí±ýû›ýýœýý›ýý˜ýý“ýýŒûû… ëë‘ÞÞŽòòiþþSþþFþþ:ÿÿ0þþ%þþþþÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿHHTÙââþþþÿÿÿff zzwE’’èO˜˜ÿM——ÿJ––ÿH••ÿG””ÿD’’ÿA‘‘ÿ>ÿ<ŽŽÿ9ÿ7‹‹ÿ5ŠŠÿ3‰‰ÿ0‡‡ÿ-††ÿ,††ÿ)……ÿ%„„ÿ1‹‹ÿ9ÿ8ÿ7ÿ5ŽŽÿ4ŽŽÿ4ŽŽÿ2ÿ1ŒŒÿ1ŒŒÿ1‹‹ÿ/‹‹ÿ/‹‹ÿ/‹‹ÿ.ŠŠÿ-ŠŠÿ-ŠŠÿ-ŠŠÿ,‰‰ÿ,‰‰ÿ,‰‰ÿ-‰‰ÿ-‰‰ÿ-‰‰ÿ-ŠŠÿ-‹‹ÿ1ŒŒÿ5ÿ6ÿ6ÿ7ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ9’’ÿ9‘‘ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ<’’ÿ<““ÿ<““ÿ=““ÿ=““ÿ=””ÿ>””ÿ>””ÿ>””ÿ?””ÿ?••ÿ?••ÿ@••ÿ@••ÿA––ÿA––ÿA••ÿB––ÿB––ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿB••ÿA””ÿA””ÿ@““ÿ@““ÿ@““ÿ?““ÿ>““ÿ>””ÿ=••ÿ=••ÿ;––ÿ:——ÿ8™™ÿ6ššÿ5œœÿ3žžÿ0¢¢ÿ-¥¥ÿ*©©ÿ'®®ÿ&³³ÿ%¸¸ÿ%¿¾ÿ'ÆÅÿ)ÌÌÿ&ÐÏý×Õæ îí¯ûû™ýý™ýý˜ýý–ýýýýˆýýúúyþþkþþ]ÿÿPÿÿDþþ9þþ.þþ$þþþþÿÿþþ þþÿÿÿÿÿÿÿÿÿÿff H……ßçç þþþÿÿÿ qq85ˆˆÀO˜˜ýO˜˜ÿL––ÿI••ÿH””ÿF““ÿB‘‘ÿ@ÿ=ÿ;ÿ8ŒŒÿ6ŠŠÿ4‰‰ÿ1ˆˆÿ.††ÿ-††ÿ+††ÿ'……ÿ0ŠŠÿ;ÿ9ÿ8ÿ6ÿ6ÿ5ŽŽÿ4ŽŽÿ2ÿ2ŒŒÿ1ŒŒÿ0ŒŒÿ0‹‹ÿ0‹‹ÿ/‹‹ÿ.ŠŠÿ-ŠŠÿ-ŠŠÿ-ŠŠÿ-ŠŠÿ-ŠŠÿ-‰‰ÿ-‰‰ÿ-ŠŠÿ-ŠŠÿ0ŒŒÿ5ÿ6ÿ6ÿ6ÿ7ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ9’’ÿ:’’ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ<““ÿ<““ÿ<““ÿ<““ÿ=““ÿ>””ÿ>””ÿ>””ÿ?””ÿ@••ÿ@••ÿ@••ÿA––ÿA––ÿA––ÿB––ÿA––ÿB––ÿB––ÿB••ÿB••ÿB••ÿC––ÿC––ÿB••ÿB••ÿC––ÿC––ÿC––ÿC––ÿC––ÿC––ÿC––ÿC••ÿC••ÿC••ÿC••ÿC••ÿB••ÿB••ÿA••ÿA””ÿ@““ÿ@““ÿ@““ÿ?’’ÿ?““ÿ?““ÿ>””ÿ>””ÿ=••ÿ<––ÿ9——ÿ8˜˜ÿ7ššÿ5œœÿ2žžÿ0¡¡ÿ-¥¥ÿ+©©ÿ(­­ÿ&±±ÿ$··ÿ&½½ÿ(ÅÅÿ)ËËÿ&ÏÏý×Õæ íí­ýý–ýý–ýý–ýý“ýýýý†üü~þþtþþhþþ[þþNþþCþþ7þþ,þþ#þþþþÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿ\\ E‹‹åîîþþþÿÿÿ kk0„„šN——øP™™ÿM——ÿJ––ÿI••ÿF””ÿD’’ÿAÿ?ÿ<ŽŽÿ9ÿ7‹‹ÿ5ŠŠÿ3‰‰ÿ0ˆˆÿ.‡‡ÿ,‡‡ÿ)‡‡ÿ.‰‰ÿ;ÿ:ÿ9ÿ8ÿ7ÿ6ÿ5ŽŽÿ4ŽŽÿ3ÿ2ÿ1ŒŒÿ1ŒŒÿ0‹‹ÿ0‹‹ÿ/‹‹ÿ.ŠŠÿ-ŠŠÿ.ŠŠÿ.ŠŠÿ-ŠŠÿ-ŠŠÿ-ŠŠÿ-ŠŠÿ/‹‹ÿ5ÿ6ÿ6ÿ6ÿ7ÿ8‘‘ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:’’ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ;““ÿ<““ÿ<““ÿ<““ÿ<““ÿ<““ÿ<““ÿ=””ÿ>””ÿ?””ÿ?••ÿ@••ÿ@••ÿ@––ÿA––ÿB––ÿB––ÿB––ÿC––ÿB––ÿB––ÿB––ÿC––ÿC––ÿC––ÿC––ÿC––ÿB••ÿC––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿC––ÿC••ÿC––ÿC––ÿC••ÿD––ÿC––ÿC––ÿC––ÿB••ÿA””ÿA””ÿ@““ÿ?““ÿ?’’ÿ?““ÿ?““ÿ?““ÿ>””ÿ=••ÿ;••ÿ:––ÿ8˜˜ÿ7™™ÿ5››ÿ3ÿ0¡¡ÿ.¤¤ÿ+§§ÿ(««ÿ&°°ÿ%¶¶ÿ'½½ÿ(ÄÄÿ*ÊÊÿ(ÎÎýÔÔå ëë«ýý“ýý”ýý“ýýýý‹ýýƒþþ{þþqÿÿfþþZþþOþþBþþ6þþ+þþ#þþÿÿÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿ\\ Láëëÿÿÿÿÿÿjj &~~qG““êQ™™ÿN˜˜ÿK––ÿI••ÿH””ÿF““ÿB‘‘ÿ@ÿ=ÿ;ÿ8ŒŒÿ6‹‹ÿ4ŠŠÿ2‰‰ÿ/‡‡ÿ-‡‡ÿ+‡‡ÿ+‡‡ÿ9ÿ;ÿ:ÿ9ÿ8ÿ7ÿ6ÿ5ŽŽÿ4ŽŽÿ3ÿ2ÿ1ŒŒÿ1ŒŒÿ0‹‹ÿ0‹‹ÿ/ŠŠÿ.ŠŠÿ/ŠŠÿ.ŠŠÿ.ŠŠÿ.ŠŠÿ.ŠŠÿ/‹‹ÿ4ÿ7ÿ6ÿ6ÿ7ÿ7ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:‘‘ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ<““ÿ=““ÿ=““ÿ=””ÿ>””ÿ?••ÿ?••ÿ@••ÿ@––ÿA••ÿ@––ÿA––ÿB––ÿB——ÿB——ÿB——ÿC––ÿC––ÿC——ÿC——ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD——ÿD––ÿD––ÿE——ÿE——ÿD––ÿE––ÿD––ÿC––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿC••ÿB••ÿA””ÿ@““ÿ?““ÿ?““ÿ?““ÿ?““ÿ>””ÿ=””ÿ<””ÿ;••ÿ9––ÿ8——ÿ7™™ÿ5››ÿ3ÿ0ŸŸÿ.££ÿ+¦¦ÿ)««ÿ'°°ÿ%µµÿ'¼¼ÿ)ÃÃÿ+ÉÉÿ)ÍÌý ÔÓä ìì¨ûû‘ýý’ýýýýýý‰þþ‚üüzþþpþþdþþZþþNþþAþþ5þþ,þþ#þþþþÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿTT Oåååÿÿÿ ÿÿÿvvG>ŒŒÑQ™™þP˜˜ÿM——ÿJ••ÿI••ÿG””ÿE’’ÿA‘‘ÿ?ÿ<ŽŽÿ:ÿ8ŒŒÿ5ŠŠÿ3‰‰ÿ0‡‡ÿ.ˆˆÿ,‡‡ÿ*‡‡ÿ7ŽŽÿ=‘‘ÿ;ÿ:ÿ9ÿ8ÿ7ÿ7ÿ5ŽŽÿ4ŽŽÿ3ÿ2ÿ2ŒŒÿ1ŒŒÿ0‹‹ÿ/‹‹ÿ/‹‹ÿ0‹‹ÿ/‹‹ÿ/ŠŠÿ/ŠŠÿ/‹‹ÿ3ÿ6ÿ7ÿ7ÿ7ÿ8ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:‘‘ÿ:‘‘ÿ:’’ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ=””ÿ>””ÿ>””ÿ>••ÿ?••ÿ@••ÿ?••ÿ@••ÿ@––ÿ@••ÿ@––ÿB––ÿB——ÿB——ÿB——ÿC——ÿC––ÿC––ÿD——ÿD——ÿD——ÿD——ÿD——ÿD——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE––ÿD––ÿD––ÿE––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿC––ÿB••ÿB””ÿA””ÿ@““ÿ?’’ÿ?’’ÿ?““ÿ>““ÿ=””ÿ=””ÿ:””ÿ9••ÿ8——ÿ6˜˜ÿ4ššÿ3œœÿ1ŸŸÿ.¢¢ÿ+¦¦ÿ(ªªÿ&¯¯ÿ&µµÿ'»»ÿ)ÂÂÿ+ÉÉÿ)ÌÌýÓÓã ëë§ûûýýýýŽýýŒýý‡ýý€þþwþþmþþcþþXþþLÿÿ@þþ6þþ,þþ#þþþþÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ__N‰‰ þþþÿÿÿÿÿÿjj$.„„¦M——ùR™™ÿO˜˜ÿL——ÿJ••ÿH””ÿF““ÿD’’ÿAÿ=ŽŽÿ<ŽŽÿ9ŒŒÿ6‹‹ÿ4‰‰ÿ1ˆˆÿ/‰‰ÿ-ˆˆÿ+‡‡ÿ5ÿ>’’ÿ<‘‘ÿ;ÿ:ÿ9ÿ8ÿ8ÿ7ÿ5ŽŽÿ4ÿ4ŽŽÿ2ÿ2ŒŒÿ1ŒŒÿ0ŒŒÿ1‹‹ÿ1‹‹ÿ0‹‹ÿ/ŠŠÿ/‹‹ÿ2ŒŒÿ7ÿ7ÿ8ÿ8ÿ8ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ:‘‘ÿ:’’ÿ:’’ÿ:’’ÿ;’’ÿ<’’ÿ<’’ÿ<’’ÿ<““ÿ=““ÿ?••ÿ?••ÿ?••ÿ?••ÿ@••ÿ@••ÿ@••ÿ@••ÿ@––ÿA––ÿA––ÿB––ÿB——ÿB——ÿC——ÿC——ÿC——ÿC——ÿD——ÿD˜˜ÿD˜˜ÿD˜˜ÿE˜˜ÿE˜˜ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿF——ÿE——ÿE——ÿD––ÿE––ÿE––ÿE––ÿE––ÿE––ÿE––ÿD––ÿD––ÿD––ÿD––ÿC––ÿC––ÿB••ÿA””ÿ@““ÿ@““ÿ@““ÿ?““ÿ?““ÿ>””ÿ<””ÿ:””ÿ9••ÿ8––ÿ6——ÿ4™™ÿ3œœÿ1ŸŸÿ.¢¢ÿ+¥¥ÿ(©©ÿ'®®ÿ&´´ÿ(ººÿ*ÁÁÿ+ÇÇÿ)ËËý ÒÑâ êé¥ýýŒýýýýýýŠýý…üü~þþvþþmþþbþþVþþJÿÿ@þþ5þþ+þþ#þþþþÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿUU_ŸŸÿÿÿÿÿÿÿÿÿuu {{xH““íSššÿQ™™ÿN——ÿK––ÿJ••ÿH””ÿE““ÿC‘‘ÿ>ÿ<ŽŽÿ;ÿ8ŒŒÿ5ŠŠÿ2‰‰ÿ1‰‰ÿ/‰‰ÿ,‡‡ÿ3ŒŒÿ?’’ÿ>’’ÿ<‘‘ÿ;ÿ:ÿ9ÿ9ÿ7ÿ6ŽŽÿ5ŽŽÿ5ŽŽÿ3ŽŽÿ3ÿ2ÿ1ŒŒÿ1ŒŒÿ1ŒŒÿ1‹‹ÿ0‹‹ÿ0‹‹ÿ5ŽŽÿ8ÿ8ÿ8ÿ8ÿ8ÿ9‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ<’’ÿ=““ÿ>””ÿ>””ÿ?••ÿ?••ÿ?••ÿ@••ÿ@••ÿ@••ÿA••ÿA––ÿA––ÿA––ÿB——ÿB——ÿB——ÿB——ÿC——ÿC——ÿD——ÿD——ÿE——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE——ÿE——ÿE——ÿF——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE––ÿE––ÿE––ÿE––ÿE––ÿE––ÿE––ÿD––ÿD––ÿC––ÿC••ÿB••ÿA””ÿA““ÿA““ÿ@““ÿ?““ÿ?““ÿ>““ÿ<””ÿ:””ÿ9••ÿ8––ÿ6——ÿ5™™ÿ4››ÿ1žžÿ.¡¡ÿ+¤¤ÿ)¨¨ÿ'­­ÿ&³³ÿ(¸¸ÿ*ÀÀÿ-ÆÆÿ*ÊÊý!ÑÐáëê¡ýýŠýý‹ýý‹ýýˆýýƒþþ}þþuþþlþþaþþVþþKÿÿ@þþ6þþ,þþ#þþþþÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿf™™ÌÌÌÿÿÿuuJ=ŒŒÒS™™þR™™ÿP˜˜ÿM——ÿJ••ÿI””ÿF““ÿD‘‘ÿBÿ=ŽŽÿ;ÿ:ÿ7‹‹ÿ4‹‹ÿ2ŠŠÿ0‰‰ÿ-ˆˆÿ1ŠŠÿ@’’ÿ?’’ÿ>’’ÿ<ÿ;ÿ:ÿ9ÿ8ÿ7ÿ6ŽŽÿ5ŽŽÿ4ŽŽÿ4ŽŽÿ3ÿ2ÿ2ŒŒÿ1ŒŒÿ1ŒŒÿ1ŒŒÿ4ÿ8ÿ8ÿ8ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ9‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ;’’ÿ;’’ÿ;’’ÿ<““ÿ=““ÿ=““ÿ>””ÿ?””ÿ?””ÿ?””ÿ?••ÿ@••ÿ@••ÿA––ÿA––ÿB––ÿB––ÿB––ÿB——ÿC——ÿC——ÿC——ÿC——ÿD——ÿD˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF˜˜ÿF˜˜ÿF˜˜ÿF——ÿF——ÿE——ÿF——ÿE——ÿF——ÿF——ÿF——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE––ÿE––ÿD––ÿD––ÿD––ÿC••ÿC••ÿB••ÿA””ÿA””ÿA““ÿ@““ÿ?““ÿ?““ÿ=””ÿ<““ÿ;””ÿ:••ÿ8••ÿ7——ÿ6˜˜ÿ4ššÿ1ÿ/  ÿ+££ÿ)§§ÿ'¬«ÿ'°°ÿ(··ÿ,¿¿ÿ-ÅÅÿ+ÈÈý!ÏÏàêê ýýˆýý‰ýýˆýý†ýýƒüü}þþuþþjÿÿ`þþVþþLÿÿ@þþ6þþ,þþ$þþþþþþþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUªªÿÿÿjj$.„„¦O––ùSššÿR™™ÿO˜˜ÿL––ÿJ••ÿG““ÿE’’ÿC’’ÿAÿ=ŽŽÿ;ÿ9ŒŒÿ6‹‹ÿ3ŠŠÿ1‰‰ÿ.‰‰ÿ/ŠŠÿ?’’ÿB““ÿ@““ÿ=‘‘ÿ<‘‘ÿ;ÿ:ÿ9ÿ9ÿ8ÿ6ŽŽÿ5ŽŽÿ5ŽŽÿ4ŽŽÿ3ÿ3ÿ2ÿ1ŒŒÿ3ŒŒÿ8ÿ9ÿ9ÿ9ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:’’ÿ;’’ÿ;’’ÿ;’’ÿ<’’ÿ<’’ÿ<““ÿ=““ÿ=””ÿ>””ÿ>””ÿ>””ÿ?””ÿ@••ÿ@••ÿ@••ÿA––ÿA––ÿB––ÿB––ÿB––ÿB––ÿC——ÿD——ÿD——ÿD——ÿD˜˜ÿD˜˜ÿD˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF˜˜ÿF™™ÿF™™ÿF™™ÿF˜˜ÿF™™ÿG™™ÿG˜˜ÿG˜˜ÿG˜˜ÿG˜˜ÿF˜˜ÿF——ÿF——ÿG˜˜ÿG˜˜ÿG˜˜ÿF˜˜ÿG——ÿG——ÿF——ÿF——ÿF——ÿE——ÿE––ÿE––ÿD––ÿE––ÿD––ÿC••ÿC••ÿB””ÿA””ÿA““ÿA““ÿ?’’ÿ?““ÿ?““ÿ=““ÿ<““ÿ;””ÿ:””ÿ8••ÿ7––ÿ5˜˜ÿ3ššÿ1œœÿ.ŸŸÿ+¢¢ÿ)¦¦ÿ(ªªÿ&¯¯ÿ)·¶ÿ,¾¾ÿ-ÄÄÿ+ÇÇý!ÐÐßëëžýý†ýý†ýý†ýý…ýý‚þþ|þþsþþjÿÿ`þþWþþKÿÿ@þþ6þþ-þþ%ÿÿÿÿþþþþÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿmm||uH’’éTššÿS™™ÿP˜˜ÿN——ÿL––ÿJ••ÿF““ÿD’’ÿC‘‘ÿ@ÿ=ŽŽÿ;ŒŒÿ8‹‹ÿ5‹‹ÿ2ŠŠÿ0‰‰ÿ.‰‰ÿ<‘‘ÿD••ÿB””ÿ?’’ÿ>’’ÿ<ÿ;ÿ;ÿ:ÿ9ÿ8ÿ6ŽŽÿ6ŽŽÿ6ŽŽÿ5ŽŽÿ4ŽŽÿ3ÿ2ŒŒÿ5ŽŽÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ;’’ÿ;’’ÿ;’’ÿ<’’ÿ<““ÿ=““ÿ=””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?””ÿ@••ÿ@••ÿ@••ÿA––ÿA––ÿA––ÿB––ÿB––ÿC——ÿC——ÿD——ÿD——ÿD——ÿD——ÿD˜˜ÿD˜˜ÿE˜˜ÿE˜˜ÿF˜˜ÿF™™ÿG™™ÿG™™ÿF™™ÿF™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG˜˜ÿG˜˜ÿG˜˜ÿG˜˜ÿH™™ÿG˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿG——ÿG˜˜ÿG——ÿF——ÿE——ÿE––ÿE––ÿE––ÿE––ÿD––ÿD––ÿC––ÿB••ÿA••ÿA””ÿA””ÿ@““ÿ?’’ÿ?““ÿ>““ÿ>““ÿ<““ÿ<””ÿ:””ÿ9••ÿ7––ÿ6——ÿ4™™ÿ1œœÿ.žžÿ,¡¡ÿ*¥¥ÿ(©©ÿ(¯¯ÿ*¶¶ÿ,½¼ÿ.ÃÃÿ+ÆÆý"ÎÍÞëëœýýƒýýƒýý„ýý„ýýþü{þþsþþjþþaþþVþþKþþBþþ7þþ.þþ%ÿÿÿÿþþþþþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿªªrrC>ÌT™™þTššÿR™™ÿP˜˜ÿM——ÿK••ÿH””ÿF““ÿD’’ÿB‘‘ÿ@ÿ=ÿ9ŒŒÿ7ŒŒÿ4‹‹ÿ1‹‹ÿ/‰‰ÿ9ÿE••ÿC””ÿB““ÿ?’’ÿ=‘‘ÿ=‘‘ÿ<‘‘ÿ<‘‘ÿ:ÿ9ÿ8ÿ6ŽŽÿ6ŽŽÿ6ÿ5ŽŽÿ3ÿ3ÿ9ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ;’’ÿ;’’ÿ:‘‘ÿ;’’ÿ<’’ÿ<““ÿ<’’ÿ=““ÿ=““ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?••ÿ@••ÿA••ÿ@••ÿA––ÿA––ÿA––ÿB––ÿC––ÿC––ÿC——ÿD——ÿD——ÿD——ÿD˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿF™™ÿG™™ÿG™™ÿG™™ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿH˜˜ÿG˜˜ÿG˜˜ÿG˜˜ÿF——ÿF——ÿE——ÿE——ÿE——ÿE––ÿE––ÿD––ÿD––ÿC••ÿB••ÿB••ÿA””ÿ@““ÿ?’’ÿ?’’ÿ?’’ÿ?““ÿ=““ÿ<““ÿ;““ÿ9””ÿ9••ÿ7••ÿ5——ÿ3™™ÿ1››ÿ.ÿ,  ÿ)¤¤ÿ'¨¨ÿ(­­ÿ*´´ÿ-»»ÿ/ÁÁÿ,ÄÄý"ÌÌÞëëšýýýýƒýý„ýý‚þüþþyþþsþþjÿÿ`þþWþþMþþBþþ8þþ/þþ'þþþþÿÿÿÿþþ ÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿjj1…… P——÷U››ÿTššÿQ˜˜ÿO——ÿL––ÿJ””ÿG””ÿF““ÿD‘‘ÿAÿ?ŽŽÿ;ŽŽÿ:ÿ7ÿ4ŒŒÿ0ŠŠÿ5ÿC••ÿD••ÿC””ÿA““ÿ?’’ÿ>’’ÿ>’’ÿ=‘‘ÿ<ÿ:ÿ9ÿ8ÿ7ŽŽÿ7ÿ7ÿ6ŽŽÿ7ÿ;‘‘ÿ;‘‘ÿ:‘‘ÿ:‘‘ÿ<’’ÿ<’’ÿ;’’ÿ<’’ÿ<’’ÿ=““ÿ=““ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?””ÿ@••ÿ@••ÿ@••ÿA••ÿA••ÿA––ÿA––ÿB––ÿC––ÿC——ÿC——ÿC——ÿD——ÿD——ÿE——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿF™™ÿG™™ÿG™™ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿH™™ÿH™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿG˜˜ÿG——ÿF——ÿF——ÿE––ÿE––ÿE––ÿE––ÿD––ÿD––ÿC––ÿB••ÿA••ÿA””ÿ@““ÿ?’’ÿ?’’ÿ?’’ÿ>““ÿ=““ÿ;““ÿ:““ÿ:““ÿ8””ÿ7””ÿ5——ÿ3˜˜ÿ1ššÿ.œœÿ+ŸŸÿ)¢¢ÿ(§§ÿ)¬¬ÿ*³²ÿ-ººÿ0ÁÁÿ-ÃÃý#ÌÌÝéé™þüýýýý‚ýý€þþ}þþyþþsþþjþþaþþXþþNÿÿDþþ:ÿÿ0ÿÿ(ÿÿ þþþþÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿqq %~~gJ’’æU››ÿTššÿR™™ÿP˜˜ÿN——ÿL••ÿI””ÿG““ÿE’’ÿB‘‘ÿ@ÿ>ÿ<ÿ9ŒŒÿ7ŒŒÿ3ŠŠÿ3‹‹ÿB””ÿE––ÿC••ÿB””ÿA““ÿ@’’ÿ?’’ÿ>’’ÿ=‘‘ÿ<‘‘ÿ;ÿ:ÿ8ÿ8ÿ8ÿ7ÿ:‘‘ÿ=’’ÿ<’’ÿ<’’ÿ;‘‘ÿ<’’ÿ<’’ÿ<’’ÿ=’’ÿ=““ÿ>””ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?””ÿ@••ÿ@••ÿ@••ÿA••ÿA••ÿA••ÿB––ÿB––ÿB––ÿC——ÿC——ÿC——ÿC——ÿD˜˜ÿD——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿF™™ÿF™™ÿG™™ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿH™™ÿHššÿH™™ÿH™™ÿHššÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿH˜˜ÿG˜˜ÿF——ÿE——ÿE——ÿE——ÿE––ÿD––ÿD––ÿD––ÿC––ÿC••ÿB••ÿA””ÿ@““ÿ?’’ÿ?’’ÿ>’’ÿ>““ÿ=’’ÿ<’’ÿ:’’ÿ9““ÿ8““ÿ7••ÿ5••ÿ3——ÿ1˜˜ÿ.››ÿ+ÿ*¢¢ÿ)¦¦ÿ(««ÿ+²²ÿ.¹¹ÿ1¿¿ÿ.ÂÂý#ÌÌÝéé—üü~ýý€þþþþþþ|ÿÿxþþrþþjþþbþþXþþOÿÿDþþ:þþ2þþ)ÿÿ"þþþþÿÿþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿtt07‡‡³R™™ùU››ÿTššÿR™™ÿO——ÿM––ÿK••ÿI””ÿF““ÿD‘‘ÿAÿ@ÿ=ŽŽÿ;ŽŽÿ9ÿ5ŒŒÿ2ŠŠÿ?’’ÿF——ÿD••ÿC””ÿB””ÿ@““ÿ?’’ÿ>’’ÿ=’’ÿ=‘‘ÿ<‘‘ÿ;ÿ9ÿ8ÿ9ÿ:ÿ>““ÿ>””ÿ>““ÿ=’’ÿ<’’ÿ<’’ÿ<’’ÿ=““ÿ>““ÿ>““ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?””ÿ?””ÿ@••ÿ@••ÿA••ÿA••ÿA••ÿA––ÿB––ÿC——ÿC––ÿC——ÿD——ÿC——ÿD——ÿD˜˜ÿD——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿG™™ÿG™™ÿHššÿHššÿHššÿHššÿHššÿIššÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿG——ÿF——ÿF——ÿF——ÿE——ÿE––ÿE––ÿE––ÿD––ÿC––ÿC••ÿB••ÿ@””ÿ?““ÿ?““ÿ?’’ÿ>’’ÿ>’’ÿ=’’ÿ;’’ÿ:’’ÿ9““ÿ8““ÿ6““ÿ4””ÿ3––ÿ1˜˜ÿ.ššÿ,ÿ+¡¡ÿ)¥¥ÿ)ªªÿ+±±ÿ.¸¸ÿ0¾¾ÿ.ÃÃý%ËËÞéé—üü}þþ~þþþþþþ{þþvþþqþþjþþaþþXþþNþþEÿÿ<þþ2þþ)ÿÿ"þþþþÿÿþþÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[[xxnF‘‘àUššþU››ÿSššÿQ˜˜ÿO——ÿM––ÿJ••ÿH””ÿF’’ÿC‘‘ÿ@ÿ>ÿ<ÿ:ŽŽÿ7ÿ4‹‹ÿ:ÿF––ÿF––ÿE••ÿC••ÿB””ÿ@““ÿ?““ÿ?’’ÿ>’’ÿ=‘‘ÿ<‘‘ÿ:ÿ9ÿ:ÿ<‘‘ÿ@””ÿ@””ÿ?““ÿ>““ÿ=““ÿ=’’ÿ=““ÿ>””ÿ>””ÿ?““ÿ>””ÿ>””ÿ>””ÿ?””ÿ?””ÿ?””ÿ@••ÿ?””ÿ@••ÿA••ÿA••ÿA••ÿB––ÿC––ÿC——ÿC——ÿD——ÿE——ÿD——ÿE˜˜ÿE˜˜ÿD——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿG™™ÿHššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿI™™ÿI™™ÿI™™ÿH™™ÿI™™ÿI™™ÿH™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿG˜˜ÿG——ÿG——ÿF——ÿE——ÿE——ÿE——ÿE––ÿE––ÿD––ÿC••ÿB••ÿA””ÿ?““ÿ?““ÿ?’’ÿ>’’ÿ>’’ÿ=’’ÿ<’’ÿ;’’ÿ:’’ÿ9’’ÿ8’’ÿ6““ÿ4””ÿ3••ÿ1——ÿ.ššÿ-ÿ+  ÿ)¤¤ÿ)©©ÿ+°¯ÿ.··ÿ1¾¾ÿ0ÁÁý'ÉÉÝçç–üü|þþ}þþ}þþ|þþyþþvþþqþþjþþaþþXþþOþþFÿÿ<þþ2þþ*þþ#þþþþþþþþÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ mm*/ƒƒ¡P——ôV››ÿT››ÿR™™ÿP˜˜ÿN——ÿL––ÿJ””ÿG““ÿE’’ÿBÿ?ÿ=ÿ<ŽŽÿ9ŽŽÿ6ŒŒÿ5ÿC””ÿG——ÿF––ÿE––ÿC••ÿB””ÿA““ÿ@’’ÿ?’’ÿ=‘‘ÿ=‘‘ÿ<‘‘ÿ;ÿ;ÿ?““ÿA””ÿA””ÿ@””ÿ?””ÿ?””ÿ?““ÿ>““ÿ?””ÿ?””ÿ?””ÿ>””ÿ?””ÿ?””ÿ?””ÿ?””ÿ@••ÿ@••ÿ@••ÿA••ÿB––ÿB––ÿB––ÿB––ÿC––ÿC——ÿD——ÿE——ÿE——ÿE——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿG™™ÿH™™ÿH™™ÿHššÿG™™ÿG™™ÿHššÿIššÿIššÿJššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿI™™ÿIššÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿH˜˜ÿG˜˜ÿG——ÿF——ÿE——ÿE——ÿE——ÿE––ÿE––ÿD––ÿC••ÿC••ÿB••ÿ@””ÿ?““ÿ?““ÿ>’’ÿ>’’ÿ=‘‘ÿ=’’ÿ<’’ÿ;‘‘ÿ9‘‘ÿ9‘‘ÿ8““ÿ5’’ÿ4““ÿ2••ÿ0––ÿ/˜˜ÿ-››ÿ+ŸŸÿ*££ÿ)¨¨ÿ+¯®ÿ/¶¶ÿ2½½ÿ0ÀÀý'ÉÉÝèè”þþyþþzþþ|þþ{þþyþþuþþqþþjþþbþþYþþOþþFÿÿ<ÿÿ3þþ,þþ%þþÿÿþþþþþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmmttQAÎTššýU››ÿTššÿR™™ÿO˜˜ÿN——ÿL••ÿI””ÿF““ÿD’’ÿA‘‘ÿ?ÿ=ÿ;ŽŽÿ8ÿ5ÿ<ÿH——ÿH——ÿF––ÿE••ÿD••ÿB””ÿA““ÿ@’’ÿ?’’ÿ=‘‘ÿ=‘‘ÿ<‘‘ÿ<‘‘ÿ@““ÿB””ÿB””ÿA””ÿA””ÿA””ÿA””ÿ?””ÿ?””ÿ@””ÿ@””ÿ?””ÿ@””ÿ?””ÿ?””ÿ@••ÿA••ÿA••ÿA••ÿB––ÿB––ÿB––ÿC––ÿD——ÿD——ÿD——ÿE——ÿE——ÿE——ÿE˜˜ÿE˜˜ÿE˜˜ÿE˜˜ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿH™™ÿHššÿHššÿHššÿHššÿIššÿJ››ÿJ››ÿJ››ÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿI™™ÿJššÿJššÿJššÿJššÿI™™ÿI™™ÿJ™™ÿI™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿG˜˜ÿF——ÿF——ÿE——ÿE––ÿE––ÿE––ÿD––ÿC––ÿC••ÿB••ÿA••ÿ@””ÿ?““ÿ?““ÿ>’’ÿ=‘‘ÿ=‘‘ÿ=‘‘ÿ<‘‘ÿ:‘‘ÿ:‘‘ÿ9‘‘ÿ6‘‘ÿ5‘‘ÿ4““ÿ2””ÿ0––ÿ.˜˜ÿ-››ÿ+žžÿ*¢¢ÿ*§§ÿ,®®ÿ0µµÿ2¼¼ÿ0ÀÀý&ÉÈÜææ’þþwþþzþþzþþzÿÿxþþvþþpþþiþþbþþYÿÿPþþGþþ=þþ5þþ,þþ%þþþþþþþþþþ þþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿii0††˜Q——öVœœÿTššÿSššÿQ™™ÿP˜˜ÿM––ÿK••ÿH””ÿE’’ÿC‘‘ÿ@‘‘ÿ?‘‘ÿ=ÿ:ŽŽÿ7ÿ9ŽŽÿF––ÿJ˜˜ÿH——ÿF––ÿE––ÿD••ÿB””ÿA““ÿ@““ÿ?’’ÿ>’’ÿ=‘‘ÿ>’’ÿA””ÿB••ÿC••ÿB””ÿB••ÿC••ÿC••ÿA••ÿA””ÿA””ÿA””ÿA••ÿ@••ÿ@••ÿ@••ÿA••ÿA••ÿA••ÿB––ÿB––ÿC––ÿC––ÿC––ÿD——ÿE——ÿD——ÿE——ÿE——ÿE——ÿE˜˜ÿF˜˜ÿE˜˜ÿE˜˜ÿG™™ÿG™™ÿG™™ÿG™™ÿH™™ÿH™™ÿIššÿIššÿIššÿIššÿJššÿK››ÿJ››ÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿJššÿJššÿJššÿJššÿI™™ÿI™™ÿJ™™ÿJ™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿG˜˜ÿF——ÿE——ÿE——ÿE——ÿE––ÿD––ÿD––ÿC––ÿB••ÿA••ÿ@””ÿ?””ÿ?““ÿ?““ÿ>’’ÿ=‘‘ÿ=‘‘ÿ=‘‘ÿ;ÿ;ÿ:ÿ8ÿ6ÿ5‘‘ÿ3’’ÿ2””ÿ0–•ÿ/˜˜ÿ-››ÿ,ÿ*¢¢ÿ*§§ÿ,®®ÿ0µµÿ3¼»ÿ1¿¿ý'ÈÈÛææþþuþþwþþzþþzþþyþþuþþpþþjþþaþþYþþQþþGþþ>þþ4þþ-þþ&þþÿÿþþÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmm#{{WH’’ÙV››þU››ÿTššÿR™™ÿQ™™ÿO——ÿM––ÿJ••ÿG““ÿE’’ÿC’’ÿ@’’ÿ>ÿ<ÿ9ŽŽÿ8ÿC””ÿK˜˜ÿH——ÿG——ÿF––ÿE––ÿD••ÿB””ÿA““ÿ@““ÿ?““ÿ>’’ÿA””ÿC••ÿC••ÿD••ÿC••ÿD––ÿD••ÿD––ÿC••ÿB••ÿA••ÿA••ÿA••ÿA••ÿA••ÿA••ÿB––ÿC––ÿC––ÿB––ÿC––ÿC––ÿC––ÿC––ÿD——ÿD——ÿD——ÿE˜˜ÿE——ÿE˜˜ÿF˜˜ÿF˜˜ÿF˜˜ÿF˜˜ÿG™™ÿG™™ÿG™™ÿH™™ÿHššÿHššÿIššÿIššÿIššÿIššÿIššÿJššÿJ››ÿJ››ÿIššÿIššÿIššÿIššÿIššÿIššÿIššÿJššÿJššÿJššÿJššÿJššÿJššÿJ™™ÿJ™™ÿJ™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿH˜˜ÿG˜˜ÿF——ÿE——ÿE——ÿE——ÿE——ÿE––ÿE––ÿD––ÿC––ÿB••ÿA””ÿ?””ÿ?””ÿ?““ÿ?’’ÿ>’’ÿ>’’ÿ=‘‘ÿ<ÿ;ÿ;ÿ9ÿ8ÿ6ÿ5‘‘ÿ4’’ÿ2““ÿ1••ÿ0——ÿ.™™ÿ+ÿ*¡¡ÿ+¦¦ÿ,¬¬ÿ1´´ÿ4ººÿ2¾¾ý*ÆÅÚçåŽüüsþþwþþyþþzÿÿxþþtþþpþþjþþbþþYÿÿPþþGþþ>þþ5þþ,þþ&þþÿÿþþÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿss!7ˆˆS™™öWœœÿU››ÿSššÿR™™ÿP˜˜ÿN——ÿL––ÿJ””ÿG““ÿE““ÿB’’ÿ@ÿ=ÿ<ÿ9ÿ?‘‘ÿK˜˜ÿK˜˜ÿI——ÿG——ÿG––ÿE••ÿC••ÿB””ÿA““ÿA““ÿ@““ÿB””ÿD––ÿD––ÿD••ÿE––ÿD––ÿD––ÿD––ÿD––ÿC––ÿB••ÿB••ÿB••ÿB••ÿB••ÿB––ÿC––ÿC––ÿC––ÿC––ÿD——ÿD——ÿC––ÿD——ÿE——ÿE——ÿE——ÿF˜˜ÿF˜˜ÿF˜˜ÿG˜˜ÿG™™ÿG˜˜ÿG™™ÿH™™ÿH™™ÿH™™ÿHššÿHššÿHššÿIššÿIššÿIššÿIššÿJššÿJ››ÿJ››ÿJ››ÿJ››ÿJ››ÿJ››ÿJ››ÿJ››ÿJ››ÿJššÿK››ÿK››ÿK››ÿKššÿKššÿKššÿKššÿJššÿJššÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿI™™ÿH™™ÿH™™ÿF——ÿE——ÿE——ÿF——ÿE——ÿE——ÿE––ÿD––ÿD––ÿC––ÿB••ÿA””ÿ@””ÿ@””ÿ?““ÿ?““ÿ>’’ÿ=‘‘ÿ=‘‘ÿ<ÿ;ÿ:ÿ:ÿ8ÿ7ÿ5‘‘ÿ4’’ÿ3““ÿ1••ÿ0——ÿ,˜˜ÿ+œ›ÿ+  ÿ+¥¥ÿ.««ÿ2³³ÿ4ººÿ3½½ý*ÇÇØçå‹üüqþþuþþwÿÿxþþwþþsþþoþþiþþaþþYþþQþþHþþ>þþ5þþ-þþ%ÿÿþþÿÿþþþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmm!{{UG‘‘×W››þW››ÿU››ÿS™™ÿR™™ÿP˜˜ÿN––ÿL––ÿJ””ÿG““ÿC’’ÿA‘‘ÿ?‘‘ÿ=ÿ:ŽŽÿ;ÿI——ÿM™™ÿK˜˜ÿI——ÿH——ÿF––ÿE••ÿD••ÿB””ÿB””ÿA““ÿD••ÿF——ÿF––ÿF––ÿF––ÿE––ÿE––ÿE––ÿE––ÿD––ÿC––ÿC••ÿC––ÿC––ÿC––ÿD––ÿD––ÿC––ÿD––ÿD——ÿD——ÿE——ÿD——ÿD——ÿE——ÿE——ÿE——ÿF˜˜ÿF˜˜ÿF˜˜ÿG˜˜ÿG™™ÿG™™ÿH™™ÿI™™ÿI™™ÿH™™ÿHššÿIššÿIššÿIššÿIššÿIššÿIššÿJššÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿJ››ÿJ››ÿJ››ÿK››ÿK››ÿK››ÿKššÿK››ÿKššÿKššÿJššÿIššÿJššÿJ™™ÿJ™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿG˜˜ÿG˜˜ÿF——ÿF——ÿE——ÿE——ÿE––ÿD––ÿD––ÿD––ÿC––ÿB••ÿA””ÿ@””ÿ@““ÿ?““ÿ>’’ÿ=‘‘ÿ<‘‘ÿ<ÿ<ÿ;ÿ;ÿ:ÿ8ÿ7ÿ6‘‘ÿ5’’ÿ3““ÿ1””ÿ/––ÿ-˜˜ÿ,››ÿ+  ÿ,¥¥ÿ/¬¬ÿ3³³ÿ6º¹ÿ4¾¾ü*ÆÆØååŠþþoþþrþþvþþwþþuþþqþþmþþhþþaþþZþþQþþGþþ>þþ5þþ-þþ%ÿÿþþþþþþþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ mm/ƒƒ‘R——ðXœœÿWœœÿUššÿS™™ÿQ™™ÿP˜˜ÿM––ÿK••ÿJ••ÿG””ÿC““ÿA’’ÿ?‘‘ÿ<ÿ:ŽŽÿD””ÿNššÿM™™ÿK˜˜ÿJ˜˜ÿH——ÿG––ÿF––ÿD••ÿC””ÿB””ÿF——ÿH˜˜ÿH——ÿH——ÿG——ÿF——ÿE––ÿE––ÿE––ÿF——ÿE——ÿD––ÿE––ÿD––ÿD––ÿD——ÿE——ÿD——ÿD——ÿD——ÿD——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿF˜˜ÿF˜˜ÿG˜˜ÿH™™ÿH™™ÿG™™ÿH™™ÿIššÿI™™ÿIššÿI™™ÿI™™ÿIššÿJššÿJššÿJššÿJ››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿJ››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿKššÿKššÿKššÿIššÿIššÿJ™™ÿJ™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH™™ÿH˜˜ÿG˜˜ÿG˜˜ÿF——ÿE––ÿE––ÿE––ÿD––ÿD––ÿC––ÿA••ÿA••ÿA••ÿA””ÿ?““ÿ?’’ÿ>’’ÿ=‘‘ÿ<ÿ<ÿ<ÿ;ÿ:ÿ9ÿ7ÿ7ÿ6‘‘ÿ4‘‘ÿ3’’ÿ2””ÿ0––ÿ.˜˜ÿ,››ÿ-  ÿ/¦¥ÿ1¬¬ÿ4³³ÿ8¹¹ÿ5½½ü*ÆÆ×ææ‰þþnþþqþþtþþuþþsþþpþþmþþhþþaþþYþþOþþFþþ=þþ4þþ,þþ%þþÿÿþþþþÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffuuLDÍWœœýXœœÿW››ÿUššÿS™™ÿQ™™ÿO——ÿM––ÿK––ÿI””ÿF””ÿB’’ÿ@’’ÿ>ÿ<ÿ?‘‘ÿM™™ÿOššÿM™™ÿK˜˜ÿI——ÿI——ÿH——ÿF––ÿD••ÿC””ÿH˜˜ÿJ˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿG——ÿG——ÿF——ÿF——ÿG——ÿG——ÿF——ÿF——ÿE——ÿE——ÿE––ÿF˜˜ÿE——ÿE——ÿE——ÿE——ÿE——ÿF˜˜ÿF˜˜ÿF˜˜ÿG˜˜ÿG˜˜ÿG™™ÿG™™ÿG™™ÿH™™ÿH™™ÿH™™ÿIššÿJššÿJššÿIššÿIššÿJššÿJššÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿLœœÿLœœÿK››ÿLœœÿL››ÿLœœÿK››ÿK››ÿK››ÿL››ÿLœœÿL››ÿL››ÿL››ÿL››ÿKššÿKššÿKššÿKššÿJššÿJ™™ÿI™™ÿI™™ÿI™™ÿI™™ÿH˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿF——ÿE——ÿE——ÿD––ÿD––ÿD––ÿB••ÿA••ÿA••ÿA””ÿ@””ÿ@““ÿ?’’ÿ>‘‘ÿ=ÿ=ÿ<ÿ<ÿ;ÿ9ŽŽÿ8ŽŽÿ7ÿ7ÿ6ÿ5‘‘ÿ4’’ÿ3””ÿ1••ÿ0˜˜ÿ/œ›ÿ/  ÿ0¦¥ÿ2¬¬ÿ6³³ÿ9¹¹ÿ6½½ü,ÅÅÖääˆþþmþþpþþrþþsþþrþþpþþlÿÿfþþ_þþWþþNþþEÿÿ<þþ4þþ,þþ%ÿÿÿÿþþþþ ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ mm1„„ŒR™™ïZÿYœœÿW››ÿU››ÿS™™ÿP˜˜ÿO——ÿL––ÿK••ÿH••ÿE””ÿB““ÿ@‘‘ÿ=ÿ<ÿH––ÿP››ÿNššÿMššÿL™™ÿJ˜˜ÿI˜˜ÿH——ÿF––ÿE••ÿJ˜˜ÿK™™ÿJ™™ÿJ™™ÿI˜˜ÿH˜˜ÿH——ÿG——ÿG——ÿG——ÿG——ÿG——ÿG——ÿG˜˜ÿG˜˜ÿG˜˜ÿF˜˜ÿE——ÿF˜˜ÿF——ÿF——ÿF˜˜ÿG˜˜ÿG˜˜ÿG˜˜ÿG˜˜ÿG˜˜ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿI™™ÿJššÿJššÿJššÿJššÿJššÿKššÿK››ÿL››ÿL››ÿLœœÿK››ÿK››ÿK››ÿL››ÿL››ÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿM››ÿM››ÿM››ÿL››ÿL››ÿL››ÿK››ÿKššÿJššÿI™™ÿI™™ÿI™™ÿH™™ÿH™™ÿH™™ÿG˜˜ÿG˜˜ÿF——ÿE——ÿD––ÿE––ÿD––ÿC––ÿB••ÿA••ÿA””ÿA””ÿA““ÿ@““ÿ>’’ÿ=‘‘ÿ=ÿ<ÿ<ÿ;ÿ:ÿ9ŽŽÿ9ÿ8ŽŽÿ7ÿ7ÿ6’’ÿ5““ÿ4””ÿ3––ÿ2˜˜ÿ0œ›ÿ/ Ÿÿ1¤¤ÿ4««ÿ8²²ÿ;¸¸ÿ8¼¼ü-ÃÂÖää†üükþþlþþpþþqþþpþþnþþjþþeþþ_þþWþþMþþEÿÿ<þþ2þþ,þþ$ÿÿÿÿþþþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿffuuACÂYœœüZÿXœœÿV››ÿTššÿR™™ÿP˜˜ÿO——ÿM––ÿJ••ÿH••ÿE””ÿC’’ÿ@‘‘ÿ>ÿC““ÿOššÿP››ÿNššÿM™™ÿK˜˜ÿK˜˜ÿI——ÿH——ÿH——ÿL™™ÿL™™ÿLššÿKššÿJ™™ÿI˜˜ÿI˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿG——ÿG——ÿH˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿG˜˜ÿF——ÿG˜˜ÿF˜˜ÿG——ÿG˜˜ÿG˜˜ÿG˜˜ÿH˜˜ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿI™™ÿJššÿJššÿJššÿJššÿKššÿKššÿK››ÿLœœÿLœœÿLœœÿLœœÿL››ÿL››ÿL››ÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿL››ÿL››ÿL››ÿL››ÿKššÿKššÿJššÿJššÿI™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿG˜˜ÿF——ÿE——ÿE––ÿE––ÿD––ÿC––ÿC––ÿB––ÿB••ÿA””ÿA““ÿ@““ÿ?’’ÿ>’’ÿ=‘‘ÿ<‘‘ÿ<‘‘ÿ<ÿ;ÿ;ÿ:ÿ9ÿ9ÿ9ÿ8‘‘ÿ7’’ÿ6’’ÿ5””ÿ4––ÿ3˜˜ÿ1››ÿ1ŸŸÿ3¤¤ÿ5««ÿ9²²ÿ<¸·ÿ9¼¼ü.ÂÂÖââ…üühþþkþþnþþnþþnþþlþþiþþdþþ\ÿÿUþþMþþCþþ;ÿÿ3þþ+þþ$þþþþÿÿþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿoo(rO••ã[ÿZÿWœœÿV››ÿTššÿQ™™ÿP˜˜ÿN––ÿL––ÿI••ÿG••ÿE””ÿB““ÿ@‘‘ÿ?‘‘ÿK——ÿRœœÿP››ÿOššÿM™™ÿL˜˜ÿK˜˜ÿI——ÿI——ÿNššÿNššÿMššÿLššÿL™™ÿK™™ÿK™™ÿI˜˜ÿH˜˜ÿI˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿH™™ÿG˜˜ÿH˜˜ÿH˜˜ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿI™™ÿIššÿJššÿJššÿJššÿJššÿJššÿKššÿKššÿK››ÿLœœÿLœœÿLœœÿLœœÿLœœÿLœœÿLœœÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿM››ÿL››ÿL››ÿK››ÿKššÿKššÿKššÿJššÿI™™ÿH™™ÿH™™ÿI™™ÿI™™ÿH˜˜ÿG——ÿF——ÿE——ÿE––ÿD––ÿC––ÿC––ÿC––ÿB••ÿA””ÿA””ÿ@““ÿ@’’ÿ?’’ÿ>‘‘ÿ=‘‘ÿ=‘‘ÿ<ÿ<ÿ<ÿ;ÿ:ŽŽÿ:ÿ:ÿ9ÿ8‘‘ÿ8’’ÿ7““ÿ6””ÿ5––ÿ4™™ÿ3››ÿ2ŸŸÿ3¤¤ÿ6ªªÿ:±±ÿ<··ÿ:»»ü0ÂÂÖàà…üügþþiþþlþþmþþlþþkþþgþþaþþ[þþTþþJþþBþþ:þþ2þþ*ÿÿ"þþþþÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿnn.=‹‹ªX››ø[žžÿXÿWœœÿUššÿS™™ÿQ˜˜ÿO——ÿM——ÿK––ÿI••ÿG••ÿD””ÿA’’ÿ?‘‘ÿD””ÿQ››ÿQ››ÿP››ÿOššÿM™™ÿK™™ÿJ˜˜ÿK˜˜ÿO››ÿO››ÿN››ÿMššÿMššÿMššÿLššÿK™™ÿI˜˜ÿJ™™ÿJ™™ÿI˜˜ÿI˜˜ÿI˜˜ÿI™™ÿH™™ÿH˜˜ÿG˜˜ÿH™™ÿH™™ÿH™™ÿI™™ÿI™™ÿH™™ÿI™™ÿI™™ÿH™™ÿI™™ÿI™™ÿH™™ÿJššÿJššÿJššÿJššÿJššÿKššÿKššÿKššÿK››ÿL››ÿLœœÿLœœÿMœœÿLœœÿLœœÿLœœÿLœœÿLœœÿL››ÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿL››ÿL››ÿL››ÿL››ÿKššÿJ™™ÿI™™ÿH™™ÿI™™ÿI™™ÿI™™ÿH˜˜ÿG——ÿG——ÿF——ÿE——ÿD––ÿC––ÿC––ÿC––ÿC••ÿB””ÿB””ÿA““ÿA““ÿ@’’ÿ?’’ÿ>’’ÿ>‘‘ÿ=ÿ=ÿ=ÿ=ÿ<ÿ;ÿ;ÿ:ÿ:ÿ:‘‘ÿ9’’ÿ8““ÿ7••ÿ6——ÿ5™˜ÿ4œ›ÿ4ŸŸÿ5¤¤ÿ8ªªÿ;±±ÿ>·¶ÿ=»ºü2ÁÁÖÞÞ„üüdþþeþþhþþjþþjþþhþþdþþ_þþYþþQþþIþþAþþ9ÿÿ0ÿÿ(þþ!þþþþÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿff "{{YJ’’ÒZœœýZÿXÿW››ÿUššÿS™™ÿQ˜˜ÿO——ÿM——ÿK——ÿI––ÿF••ÿC““ÿA’’ÿ@‘‘ÿL˜˜ÿTœœÿR››ÿP››ÿOššÿMššÿL™™ÿL™™ÿP››ÿP››ÿO››ÿN››ÿNššÿMššÿN››ÿMššÿK™™ÿK™™ÿKššÿK™™ÿK™™ÿK™™ÿI™™ÿI™™ÿI™™ÿH˜˜ÿI™™ÿJššÿI™™ÿI™™ÿJ™™ÿI™™ÿI™™ÿI™™ÿH™™ÿI™™ÿJššÿIššÿKššÿKššÿJššÿJššÿKššÿKššÿL››ÿL››ÿL››ÿL››ÿL››ÿMœœÿMœœÿMœœÿL››ÿL››ÿL››ÿL››ÿLœœÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿM››ÿM››ÿL››ÿL››ÿL››ÿK››ÿKššÿJššÿJ™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿG˜˜ÿG——ÿG——ÿF——ÿE——ÿD––ÿD––ÿC––ÿC••ÿB””ÿB””ÿB””ÿA““ÿA““ÿ@’’ÿ?’’ÿ?’’ÿ>‘‘ÿ>‘‘ÿ=ÿ=ÿ=ÿ<ÿ<ÿ<ÿ;ÿ;‘‘ÿ;’’ÿ;““ÿ:““ÿ9••ÿ8––ÿ7™™ÿ6œ›ÿ6  ÿ7¤¤ÿ:ªªÿ=°°ÿA¶µÿ?º¹ü3ÀÀÕÝÝüüaþþcÿÿfþþiþþgþþeþþaþþ]þþVþþOþþHþþ?þþ7þþ/þþ'ÿÿ þþÿÿÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ kk,€€…S™™ì[žžÿYÿXÿW››ÿTššÿR™™ÿP˜˜ÿN——ÿM——ÿK——ÿH––ÿF••ÿD““ÿA‘‘ÿF””ÿSœœÿTœœÿR››ÿQ››ÿO››ÿNššÿL™™ÿP››ÿRœœÿPœœÿP››ÿO››ÿNššÿN››ÿNššÿMššÿMššÿM››ÿLššÿMššÿLššÿK™™ÿJ™™ÿJ™™ÿI™™ÿI™™ÿJššÿJ™™ÿJššÿKššÿJ™™ÿJ™™ÿJššÿJššÿJššÿJššÿJššÿK››ÿL››ÿKššÿKššÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿMœœÿMœœÿMœœÿMœœÿL››ÿLœœÿMœœÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿNÿMœœÿNœœÿNœœÿNÿNœœÿNœœÿNœœÿMœœÿM››ÿM››ÿL››ÿJššÿKššÿJ™™ÿJ™™ÿJ™™ÿJ™™ÿI™™ÿI™™ÿH˜˜ÿG˜˜ÿG——ÿG——ÿF——ÿF——ÿE––ÿE——ÿD––ÿC••ÿC••ÿC••ÿB””ÿB””ÿB””ÿA““ÿ?’’ÿ?’’ÿ?’’ÿ?’’ÿ>‘‘ÿ>‘‘ÿ>‘‘ÿ>ÿ=ÿ=ÿ=ÿ<‘‘ÿ<’’ÿ<’’ÿ;“’ÿ;””ÿ:••ÿ9——ÿ9™™ÿ8œ›ÿ7ŸŸÿ9££ÿ<©©ÿ?¯¯ÿCµµÿ@¹¹ü5ÀÀÓÜÜüü^þþaþþeþþeþþeþþbþþ_þþZþþTþþNþþFþþ=þþ5þþ-þþ%þþÿÿþþþþÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??vv:D»Yú\žžÿZÿXœœÿV››ÿTššÿR™™ÿP˜˜ÿN˜˜ÿL——ÿJ——ÿI––ÿG••ÿE””ÿB’’ÿM˜˜ÿVÿTÿRœœÿQ››ÿP››ÿNššÿP››ÿTÿSÿRœœÿP››ÿO››ÿO››ÿNššÿN››ÿN››ÿN››ÿMššÿMššÿMššÿLššÿLššÿKššÿKššÿK™™ÿKššÿKššÿKššÿKššÿKššÿKššÿKššÿKššÿK››ÿL››ÿK››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿNœœÿNÿNÿNÿNÿNÿNÿNÿNÿNÿNÿNÿNÿNœœÿNœœÿNœœÿM››ÿMœœÿL››ÿL››ÿKššÿJššÿJššÿJššÿJ™™ÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿH˜˜ÿG——ÿE——ÿF——ÿF——ÿE––ÿD––ÿD••ÿC••ÿC••ÿC””ÿC””ÿB””ÿA““ÿA““ÿ@““ÿ@’’ÿ?’’ÿ?‘‘ÿ?‘‘ÿ?‘‘ÿ?‘‘ÿ>‘‘ÿ>ÿ>‘‘ÿ=‘‘ÿ=’’ÿ=’’ÿ=““ÿ=••ÿ<––ÿ:——ÿ9š™ÿ9››ÿ9žžÿ:¢¢ÿ=¨¨ÿA¯¯ÿE¶¶ÿDººü6¿¿ÑÝÝzüü[þþ_þþbþþcþþbÿÿ`þþ\þþXþþRþþKþþCþþ;ÿÿ3þþ,þþ$þþþþþþþþÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿjj #zz^J““Õ\žžþ[žžÿYÿXœœÿV››ÿTššÿRššÿP™™ÿN˜˜ÿL——ÿK——ÿI––ÿG••ÿD““ÿF””ÿSœœÿVžžÿTÿSœœÿQœœÿP››ÿQœœÿUžžÿUžžÿTÿSÿRœœÿQœœÿPœœÿPœœÿO››ÿO››ÿO››ÿN››ÿN››ÿMššÿMššÿM››ÿM››ÿM››ÿM››ÿL››ÿLššÿLššÿLššÿL››ÿM››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿL››ÿMœœÿNœœÿNœœÿMœœÿMœœÿMœœÿMœœÿMœœÿNœœÿNÿNÿNÿNœœÿNÿOÿOÿOÿNÿNÿNÿNÿNÿNÿOÿNÿNÿNÿNœœÿNœœÿMœœÿMœœÿM››ÿM››ÿKššÿLššÿKššÿKššÿJššÿJššÿJššÿJ™™ÿH™™ÿH™™ÿH˜˜ÿG˜˜ÿF——ÿF——ÿF——ÿE––ÿE––ÿE––ÿD––ÿC••ÿC••ÿC••ÿC””ÿC””ÿC””ÿB””ÿA““ÿA““ÿ@’’ÿ@‘‘ÿ?‘‘ÿ?‘‘ÿ@‘‘ÿ?‘‘ÿ?‘‘ÿ>‘‘ÿ>’’ÿ>’’ÿ?““ÿ?””ÿ>••ÿ<––ÿ<˜˜ÿ;™™ÿ;››ÿ;ŸŸÿ=¢¢ÿ?©©ÿD°¯ÿG¶¶ÿE¹¹ü8¾¾ÏßßxüüZþþ\þþ^ÿÿ`þþaþþ^þþZþþVþþOþþHÿÿ@þþ9þþ1þþ)ÿÿ"þþþþÿÿþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿii0ƒƒŠS˜˜ì]ŸŸÿ[žžÿZÿXœœÿV››ÿT™™ÿQ™™ÿP™™ÿN˜˜ÿL˜˜ÿK——ÿI––ÿG••ÿE””ÿL˜˜ÿWžžÿVžžÿUÿSœœÿQ››ÿRœœÿWžžÿVŸŸÿUžžÿTžžÿTžžÿSÿSÿRÿPœœÿPœœÿPœœÿPœœÿOœœÿN››ÿN››ÿN››ÿOœœÿN››ÿOœœÿNœœÿM››ÿM››ÿL››ÿL››ÿM››ÿM››ÿL››ÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿNœœÿNœœÿNœœÿOÿOÿNœœÿNÿNÿNÿNÿNÿOÿOÿOÿNÿNÿOÿOÿOÿOÿOÿOÿOÿOÿNÿOÿOÿNÿNÿNÿNœœÿNœœÿNœœÿMœœÿM››ÿL››ÿM››ÿM››ÿL››ÿKššÿKššÿJššÿI™™ÿIššÿJ™™ÿH˜˜ÿG˜˜ÿG˜˜ÿG——ÿG˜˜ÿF——ÿF——ÿG——ÿF——ÿE––ÿE––ÿD••ÿD••ÿD••ÿD••ÿC””ÿB””ÿC””ÿA““ÿ@’’ÿA’’ÿ@’’ÿ@‘‘ÿ@‘‘ÿ@‘‘ÿ@’’ÿ@’’ÿ@’’ÿ@““ÿ@““ÿ?””ÿ>••ÿ?——ÿ>˜˜ÿ=™™ÿ=››ÿ>ŸŸÿ?££ÿA©¨ÿF¯¯ÿI¶¶ÿH¹¹û:¾¾ÎÜÜvüüWþþYþþ[þþ]þþ]þþZþþWþþSþþLþþEþþ>þþ6þþ/þþ'þþ!þþþþÿÿÿÿ þþ ÿÿÿÿÿÿÿÿÿÿÿÿnn59ˆˆªYœœö\ŸŸÿZÿYÿXœœÿV››ÿSššÿQ™™ÿP™™ÿN˜˜ÿL˜˜ÿK˜˜ÿI––ÿF••ÿF””ÿQ››ÿXŸŸÿWžžÿUÿSœœÿTÿXŸŸÿX  ÿVŸŸÿVžžÿUžžÿTžžÿTžžÿTžžÿRÿQÿQÿQÿQÿPœœÿOœœÿPœœÿPœœÿPœœÿOœœÿOœœÿN››ÿNœœÿN››ÿM››ÿM››ÿM››ÿM››ÿNœœÿNœœÿNœœÿNœœÿNœœÿNÿOÿOÿOÿOÿOÿPÿOÿOÿOÿOÿOÿOÿOÿPžžÿPžžÿPÿOÿPÿPÿPÿPÿPÿPÿPÿPÿOÿPÿOÿOÿOÿOÿOÿOÿOœœÿNœœÿNœœÿMœœÿM››ÿN››ÿM››ÿL››ÿLššÿKššÿJššÿKššÿJššÿI™™ÿH™™ÿH™™ÿH˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿG˜˜ÿG——ÿG——ÿG——ÿE––ÿE––ÿE––ÿD••ÿC••ÿC••ÿC””ÿC””ÿA““ÿB““ÿA““ÿA’’ÿB““ÿC““ÿC““ÿB““ÿA““ÿA““ÿA““ÿ@““ÿB••ÿB——ÿA––ÿA——ÿ@™™ÿ@››ÿ@ŸŸÿB££ÿD©©ÿI°°ÿL¶¶ÿJ¹¸û<¿¾ÌÛÛtûûTÿÿUþþXþþZþþZþþXþþTÿÿPþþJþþCþþ;þþ4þþ,þþ&ÿÿþþÿÿÿÿþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ\\ vvVJ““Ð\ŸŸý\ŸŸÿ[žžÿYÿXœœÿV››ÿT››ÿRššÿP™™ÿO™™ÿM˜˜ÿL˜˜ÿH––ÿG••ÿI––ÿUÿYŸŸÿWžžÿVÿUÿXŸŸÿZ  ÿXŸŸÿXŸŸÿVŸŸÿUžžÿVžžÿUžžÿTžžÿSžžÿRÿRÿRÿPœœÿPœœÿQœœÿQÿPÿOœœÿPœœÿOœœÿOœœÿOœœÿNœœÿNœœÿNœœÿNœœÿNœœÿNœœÿNœœÿOœœÿOÿOÿOÿPÿPÿPÿPÿPÿQžžÿPžžÿPÿPÿPÿPÿPÿPžžÿPžžÿPÿPÿPÿPÿPÿPÿPÿPžžÿPžžÿPÿPÿPÿPÿPÿPÿPÿOÿPÿOÿOœœÿOœœÿNœœÿNœœÿNœœÿM››ÿL››ÿL››ÿKššÿLššÿKššÿKššÿJ™™ÿI™™ÿJ™™ÿI™™ÿH˜˜ÿI˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿG——ÿG——ÿF——ÿF——ÿF––ÿE––ÿE––ÿE••ÿD••ÿC••ÿC””ÿC””ÿC””ÿD””ÿD””ÿD””ÿD””ÿD””ÿC““ÿC““ÿC””ÿB””ÿD••ÿE––ÿD—–ÿD——ÿD™˜ÿCššÿBœÿD  ÿE¥¥ÿHªªÿL°°ÿO¶¶ÿL¸¸û=¾¾ÌÜÚpþþOþþQÿÿUþþVþþVþþSÿÿPþþKþþGÿÿ@þþ9þþ1þþ*þþ#þþþþþþþþþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ cc-‚‚{P˜˜ä]ŸŸþ\ŸŸÿZÿYÿXœœÿVœœÿTššÿRššÿPššÿO™™ÿM˜˜ÿK——ÿI——ÿF••ÿL˜˜ÿXŸŸÿY  ÿWžžÿVÿXžžÿ[  ÿZ  ÿZ¡¡ÿY  ÿXŸŸÿWŸŸÿWŸŸÿVžžÿUžžÿX  ÿ^¤¤ÿb¥¥ÿa¥¥ÿ[¢¢ÿTžžÿRÿQÿQœœÿQœœÿPœœÿOœœÿPÿPÿPÿOÿOÿNœœÿOÿPÿPÿPÿPÿOÿPÿPÿPÿPÿPÿQžžÿQžžÿQžžÿQžžÿQžžÿQžžÿPÿPÿPžžÿQžžÿQžžÿQžžÿQžžÿQžžÿQžžÿPžžÿPžžÿPžžÿPžžÿPÿPÿPÿPÿPÿPÿPÿPÿPÿPÿPÿOœœÿNœœÿNœœÿM››ÿM››ÿL››ÿL››ÿM››ÿL››ÿL››ÿKššÿKššÿKššÿI™™ÿI™™ÿI˜˜ÿH˜˜ÿI˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿG——ÿG——ÿF——ÿG——ÿF––ÿF––ÿF––ÿE••ÿE••ÿE••ÿF••ÿF••ÿE””ÿE””ÿE””ÿE””ÿF••ÿF••ÿE””ÿE••ÿG––ÿG––ÿF——ÿF——ÿG˜˜ÿFššÿF››ÿGžžÿG¡¡ÿG¥¥ÿJªªÿN°°ÿRµµÿM¶¶û=¼ºÊÛÙlûûLþþOÿÿPþþRþþRÿÿPþþMþþHÿÿDþþ=þþ5þþ/ÿÿ(ÿÿ þþþþÿÿþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿ rr&1„„Tššì^  ÿ\žžÿZžžÿYÿXœœÿV››ÿT››ÿR››ÿQššÿO™™ÿM™™ÿL˜˜ÿJ——ÿH••ÿOššÿYŸŸÿYŸŸÿXžžÿXžžÿ[  ÿ\¡¡ÿ\¢¢ÿ\¢¢ÿZ¡¡ÿY  ÿXŸŸÿX  ÿb¥¥ÿr®®ÿy²²ÿ{´´ÿ{´´ÿw²²ÿiªªÿX¡¡ÿRÿRÿRÿQœœÿQÿQÿRÿRÿQÿQÿQÿQžžÿQžžÿPÿQÿQÿQÿPÿPÿPÿPÿQžžÿQžžÿQžžÿQžžÿQžžÿRžžÿRžžÿQžžÿQžžÿQžžÿRžžÿRžžÿRžžÿRžžÿRžžÿQžžÿQžžÿQžžÿPžžÿPžžÿQžžÿQžžÿQžžÿQžžÿQžžÿPžžÿPÿPÿPÿPÿPÿOÿOœœÿNœœÿNœœÿNœœÿNœœÿNœœÿM››ÿM››ÿM››ÿL››ÿKššÿKššÿJššÿJ™™ÿI™™ÿI˜˜ÿI˜˜ÿI™™ÿI™™ÿI˜˜ÿH˜˜ÿH˜˜ÿG˜˜ÿG——ÿG——ÿG——ÿG——ÿG——ÿG––ÿG––ÿG––ÿG••ÿF••ÿF••ÿG••ÿG••ÿG––ÿH––ÿG••ÿH––ÿI——ÿI––ÿH––ÿI——ÿJ˜˜ÿJ™™ÿJ››ÿIÿIŸŸÿI¢¡ÿJ¥¥ÿMªªÿQ°°ÿR´´ÿMµµû?ººÈ!ÚØiûûIþþKþþMþþOþþNþþLþþHþþEþþ?þþ9ÿÿ3þþ,þþ%þþþþÿÿÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿqq6?ŒŒ°Y÷_¡¡ÿ\ŸŸÿZžžÿYÿXœœÿWœœÿT››ÿS››ÿQššÿO™™ÿN˜˜ÿL˜˜ÿJ——ÿI––ÿSœœÿZ  ÿZŸŸÿYŸŸÿ[  ÿ^¢¢ÿ^¢¢ÿ]¢¢ÿ\¢¢ÿ[¡¡ÿY  ÿe§§ÿy²²ÿ~´´ÿ}´´ÿ}´´ÿ}µµÿ}µµÿ|´´ÿq®®ÿ[¢¢ÿSžžÿSžžÿSžžÿSžžÿSžžÿSžžÿSžžÿRžžÿRžžÿSžžÿSžžÿRžžÿRžžÿRÿRžžÿRžžÿQžžÿQÿQžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿSŸŸÿSŸŸÿRŸŸÿSŸŸÿSŸŸÿRŸŸÿRŸŸÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRŸŸÿRŸŸÿRžžÿQžžÿQžžÿQžžÿQžžÿPÿPÿPÿPÿPÿOÿOœœÿOœœÿOœœÿNœœÿNœœÿM››ÿM››ÿM››ÿL››ÿKššÿLššÿLššÿKššÿJ™™ÿJ™™ÿJ™™ÿJ™™ÿJ™™ÿJ™™ÿI˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿG——ÿI˜˜ÿH——ÿH——ÿH––ÿG––ÿG––ÿH––ÿH––ÿH––ÿH––ÿI——ÿI——ÿJ––ÿK——ÿJ––ÿJ––ÿL˜˜ÿM™™ÿM™™ÿLššÿLœœÿLÿK ŸÿL¢¢ÿM¥¥ÿOªªÿP¯®ÿT´´ÿR¶¶ûB¹¹Ç%ÙÙfûûEþþGþþIþþJþþIþþGÿÿDÿÿ@ÿÿ<þþ6þþ/þþ)þþ#þþþþþþþþþþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿqq vvKCŽŽ¾\ù^  ÿ]ŸŸÿ[ÿYÿXÿVœœÿT››ÿS››ÿQššÿP™™ÿN™™ÿL˜˜ÿJ——ÿK˜˜ÿWžžÿ[  ÿZ  ÿ[  ÿ^¢¢ÿ^¢¢ÿ^¢¢ÿ^¢¢ÿ\¢¢ÿ^££ÿt°°ÿ}µµÿ}´´ÿ}´´ÿ~µµÿ~µµÿ~µµÿ~µµÿ}µµÿr¯¯ÿ[¢¢ÿTžžÿUŸŸÿUŸŸÿSžžÿTžžÿSžžÿSžžÿSžžÿSžžÿSžžÿSŸŸÿSžžÿSžžÿSžžÿSžžÿSžžÿSžžÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿTŸŸÿTŸŸÿSŸŸÿTŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿRžžÿRžžÿRžžÿRžžÿRžžÿRŸŸÿRŸŸÿRžžÿRžžÿRžžÿRžžÿRžžÿQžžÿQžžÿQžžÿQžžÿQÿQžžÿQÿPÿOœœÿNœœÿNœœÿM››ÿM››ÿNœœÿNœœÿM››ÿM››ÿM››ÿLššÿLššÿKššÿKššÿKššÿK™™ÿKššÿJ™™ÿI™™ÿJ™™ÿJ˜˜ÿI˜˜ÿI˜˜ÿI˜˜ÿI˜˜ÿJ˜˜ÿI——ÿH——ÿI——ÿJ——ÿJ——ÿJ——ÿK˜˜ÿL˜˜ÿK˜˜ÿL——ÿL——ÿM——ÿM——ÿN˜˜ÿO™™ÿN™™ÿNššÿN››ÿNœœÿNžžÿN  ÿN¢¢ÿM¥¤ÿN©¨ÿS®®ÿW´³ÿS´´ûD¸¸Æ&ØÕcûûAþþBÿÿDÿÿDÿÿDþþBÿÿ@ÿÿ<þþ8þþ1þþ+þþ&ÿÿ þþþþÿÿþþ ÿÿ þþÿÿÿÿÿÿÿÿÿÿÿÿffxxYEÅ[žžú_  ÿ]ŸŸÿ[žžÿZÿXÿVœœÿU››ÿS››ÿR››ÿPššÿN™™ÿL˜˜ÿJ——ÿO™™ÿZ  ÿ\¡¡ÿ[  ÿ_¢¢ÿ`££ÿ_££ÿ_££ÿ]¢¢ÿc¦¦ÿy³³ÿ}µµÿ}µµÿ}´´ÿµµÿµµÿ~µµÿ}µµÿ}µµÿ~¶¶ÿo­­ÿY¡¡ÿW  ÿW  ÿVŸŸÿUŸŸÿTŸŸÿTŸŸÿSžžÿSŸŸÿSžžÿSŸŸÿSŸŸÿSŸŸÿSžžÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿT  ÿT  ÿTŸŸÿTŸŸÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿTŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿQžžÿQÿPÿPÿPœœÿOœœÿOœœÿNœœÿNœœÿNœœÿN››ÿMššÿM››ÿM››ÿLššÿMššÿLššÿLššÿLššÿLššÿLššÿK™™ÿJ˜˜ÿJ˜˜ÿJ™™ÿJ™™ÿK™™ÿK™™ÿJ˜˜ÿJ˜˜ÿK˜˜ÿL˜˜ÿM™™ÿM™™ÿM™™ÿM˜˜ÿM˜˜ÿN˜˜ÿO˜˜ÿO˜˜ÿP™™ÿP™™ÿPššÿQ››ÿP››ÿQœœÿQžžÿRŸŸÿP  ÿM¡ ÿN¤¤ÿR©©ÿT­­ÿV±±ÿT´´úG¶¶Å'ÔÔ`úú<þþ=þþ?ÿÿ@þþ?þþ=þþ;þþ8ÿÿ3þþ-ÿÿ(ÿÿ"þþþþþþÿÿÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿkk!zzdH’’Ì[ŸŸú^  ÿ]ŸŸÿ[žžÿYžžÿXÿWÿUœœÿS››ÿR››ÿPššÿN™™ÿM˜˜ÿK˜˜ÿSœœÿ\¡¡ÿ\¡¡ÿ_££ÿb¤¤ÿa¤¤ÿa¤¤ÿ_££ÿf§§ÿ{³³ÿ~µµÿ~µµÿ~µµÿµµÿµµÿ~¶¶ÿ~¶¶ÿ~µµÿ¶¶ÿ|´´ÿe¨¨ÿX  ÿX  ÿX  ÿW  ÿW  ÿUŸŸÿUŸŸÿU  ÿUŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿUŸŸÿU  ÿU  ÿU  ÿU  ÿTŸŸÿTŸŸÿTŸŸÿU  ÿU  ÿU  ÿU  ÿV  ÿV  ÿV  ÿU  ÿU  ÿU  ÿU  ÿU  ÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿS  ÿT  ÿTŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRÿQžžÿQžžÿPÿOœœÿOœœÿOœœÿOœœÿNœœÿN››ÿNœœÿN››ÿN››ÿN››ÿN››ÿM››ÿN››ÿN››ÿM››ÿLššÿK™™ÿL™™ÿLššÿLššÿLššÿLššÿL™™ÿL™™ÿM™™ÿN™™ÿN™™ÿNššÿN™™ÿN˜˜ÿP™™ÿQššÿQ™™ÿQ™™ÿR™™ÿR™™ÿSššÿS››ÿTœœÿTÿUžžÿTŸžÿPžžÿNžžÿO¡¡ÿO££ÿP¦¦ÿT¬¬ÿY²±ÿX´´úK··Ã,ÒÒ\úú7þþ7þþ9þþ:þþ:þþ9þþ6þþ2þþ.þþ)þþ$þþþþþþÿÿþþ ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ jj!yykK““Ï]žžû^  ÿ]ŸŸÿ[žžÿZÿXÿWÿUœœÿTœœÿR››ÿPššÿO™™ÿM˜˜ÿM˜˜ÿUžžÿ]¢¢ÿ^££ÿb¤¤ÿc¥¥ÿc¥¥ÿa¤¤ÿf§§ÿ{³³ÿ€¶¶ÿ€¶¶ÿ€¶¶ÿ€¶¶ÿ¶¶ÿ¶¶ÿ¶¶ÿ¶¶ÿ~µµÿ~¶¶ÿp®®ÿZ¡¡ÿY¡¡ÿY¡¡ÿX  ÿX  ÿW  ÿX  ÿW  ÿW  ÿV  ÿU  ÿV  ÿV  ÿU  ÿV  ÿV  ÿU  ÿU  ÿU  ÿU  ÿU  ÿUŸŸÿU  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿT  ÿT  ÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿSŸŸÿSŸŸÿSžžÿSžžÿSŸŸÿSŸŸÿSžžÿRžžÿRžžÿRžžÿQÿQÿPÿPÿPÿOœœÿPœœÿOœœÿOœœÿNœœÿOœœÿOœœÿNœœÿNœœÿNœœÿN››ÿMššÿMššÿMššÿMššÿMššÿM››ÿN››ÿNššÿNššÿNššÿNššÿOššÿPššÿPššÿQššÿQššÿRššÿR™™ÿSššÿSššÿT››ÿU››ÿVœœÿVÿWÿWŸŸÿUžžÿQœœÿQžžÿPžžÿNŸŸÿO¢¢ÿS§§ÿX¬¬ÿ]²±ÿ\´´úQµµÀ-ÑÑTùù1þþ2þþ4þþ5þþ4ÿÿ3ÿÿ0þþ-þþ)þþ%þþ!þþþþþþÿÿþþ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ gg$yyqK’’Ô^žžü_  ÿ]ŸŸÿ\žžÿZžžÿXÿWÿUÿTœœÿR››ÿQššÿO™™ÿM˜˜ÿN™™ÿXŸŸÿ^££ÿa¤¤ÿd¦¦ÿd¥¥ÿb¤¤ÿd¥¥ÿw±±ÿ¶¶ÿ¶¶ÿ··ÿ··ÿ€¶¶ÿ€¶¶ÿ¶¶ÿ¶¶ÿ~µµÿ~µµÿr®®ÿ\¢¢ÿ[¡¡ÿ[¢¢ÿZ¢¢ÿY¡¡ÿY¡¡ÿY¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW  ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿV  ÿV  ÿV¡¡ÿV¡¡ÿW  ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿV  ÿV  ÿV  ÿV  ÿU  ÿU  ÿV  ÿV  ÿV¡¡ÿV  ÿV  ÿU  ÿU  ÿU  ÿU  ÿU  ÿUŸŸÿU  ÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿSŸŸÿSŸŸÿSžžÿRžžÿSžžÿRžžÿQžžÿQÿQÿQÿQÿPÿPœœÿOœœÿOœœÿOœœÿOœœÿNœœÿOœœÿOœœÿO››ÿN››ÿN››ÿN››ÿOœœÿPœœÿO››ÿO››ÿO››ÿPššÿQ››ÿQ››ÿR››ÿR››ÿRššÿSššÿTššÿU››ÿU››ÿVœœÿWœœÿWœœÿXÿYŸŸÿXžžÿTœœÿSœœÿRœœÿPœœÿNÿPŸŸÿR¢¢ÿW§§ÿ\¬¬ÿa±±ÿ^²²ô:®®ÕÕ7þþ+þþ-þþ/þþ.þþ.þþ-þþ+ÿÿ(þþ$þþ!þþÿÿÿÿÿÿÿÿ ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿnn%zzuO––Ù^ŸŸý_  ÿ^  ÿ\ŸŸÿZžžÿYžžÿWÿVÿTœœÿS››ÿRššÿPššÿM™™ÿPššÿZ  ÿ`¤¤ÿd¦¦ÿe¦¦ÿd¥¥ÿc¥¥ÿn¬¬ÿµµÿ··ÿ¶¶ÿ··ÿ··ÿ··ÿ€¶¶ÿ¶¶ÿ¶¶ÿ~µµÿn¬¬ÿ]££ÿ]££ÿ\££ÿ\¢¢ÿ[¢¢ÿ[¢¢ÿZ¢¢ÿZ¡¡ÿY¡¡ÿY¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿY¡¡ÿY¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿX¡¡ÿW¡¡ÿV  ÿV¡¡ÿV  ÿV  ÿV¡¡ÿV¡¡ÿV  ÿV  ÿV  ÿV  ÿU  ÿU  ÿU  ÿU  ÿU  ÿU  ÿUŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿRžžÿRžžÿRžžÿRžžÿRÿQžžÿRÿQÿPÿQÿPÿPÿPÿQÿPÿPœœÿQœœÿQœœÿQœœÿRÿQœœÿQœœÿQœœÿRœœÿSœœÿSœœÿSœœÿS››ÿS››ÿT››ÿVœœÿWÿWœœÿWÿXÿYÿZžžÿ[žžÿWÿT››ÿTœœÿR››ÿP››ÿQœœÿRÿS  ÿU££ÿY§§ÿ[ªªúL¥¥ÈžžoÄÄ4÷÷$þþ%þþ'ÿÿ(ÿÿ(ÿÿ(þþ'þþ%ÿÿ"þþþþÿÿÿÿÿÿþþ ÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿjj+€€{Q––Ü^  ý`¡¡ÿ^  ÿ]ŸŸÿ[žžÿYžžÿXžžÿVÿTœœÿSœœÿR››ÿQššÿOššÿQ››ÿ[¡¡ÿc¥¥ÿf¦¦ÿe¦¦ÿe¦¦ÿg§§ÿu¯¯ÿ¶¶ÿ‚··ÿ‚··ÿ‚··ÿ··ÿ€¶¶ÿ¶¶ÿ€¶¶ÿ{³³ÿf¨¨ÿ^££ÿ^££ÿ]££ÿ]££ÿ\££ÿ\££ÿ\££ÿ\¢¢ÿ\¢¢ÿ[¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿY¡¡ÿY¡¡ÿY¡¡ÿY¡¡ÿY¡¡ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿX¡¡ÿY¢¢ÿY¢¢ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿY¡¡ÿY¢¢ÿY¢¢ÿY¡¡ÿX¢¢ÿY¢¢ÿY¡¡ÿX¢¢ÿX¡¡ÿW¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿU  ÿTŸŸÿTŸŸÿSŸŸÿTŸŸÿTŸŸÿSŸŸÿSžžÿSžžÿSžžÿRžžÿRžžÿRžžÿRžžÿRÿRžžÿSžžÿRžžÿRÿRÿRÿSÿSžžÿSÿSÿTžžÿUÿUÿUœœÿUÿUœœÿVÿVÿXÿYžžÿXÿYžžÿZžžÿ[ŸŸÿ\ŸŸÿ[žžÿV››ÿU››ÿT››ÿRššÿS››ÿSœœÿTÿUŸžÿU  ÿU¡¡ûFššÎ‹‹u­­/õõþþþþþþþþ!ÿÿ"þþ#ÿÿ"þþ!þþþþþþþþÿÿÿÿþþ ÿÿ ÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿll!*~Q——Ü`  ýa¡¡ÿ_  ÿ^ŸŸÿ\ŸŸÿZŸŸÿXžžÿVÿUÿTœœÿS››ÿR››ÿOššÿR››ÿ\¡¡ÿd¦¦ÿg§§ÿg§§ÿe¦¦ÿh¨¨ÿu¯¯ÿµµÿ‚··ÿ‚··ÿ‚··ÿ··ÿ€¶¶ÿµµÿn¬¬ÿa¤¤ÿ`¤¤ÿ`¤¤ÿ^££ÿ_¤¤ÿ]££ÿ]££ÿ]££ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ\££ÿ]££ÿ\££ÿ\££ÿ[££ÿZ££ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ££ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿV  ÿU  ÿTŸŸÿU  ÿUŸŸÿTŸŸÿTŸŸÿUŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSžžÿTŸŸÿUŸŸÿUŸŸÿTžžÿTžžÿTžžÿTžžÿUŸŸÿUŸŸÿUŸŸÿUŸŸÿVŸŸÿVžžÿVÿWÿWžžÿXžžÿYžžÿYžžÿYžžÿYžžÿZžžÿ[ŸŸÿ\ŸŸÿ\  ÿ]  ÿXÿV››ÿTššÿS™™ÿSššÿT››ÿUœœÿVÿWžžÿUžžúB““Ðz-ááÿÿþþþþÿÿþþþþþþþþþþþþþþþþÿÿþþÿÿþþ ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿii"*~Q––Ü`  ýa¡¡ÿ_¡¡ÿ^  ÿ\ŸŸÿ[ŸŸÿYžžÿWÿVÿTœœÿSœœÿR››ÿQššÿTœœÿ^¢¢ÿf§§ÿh¨¨ÿg§§ÿf§§ÿg§§ÿo¬¬ÿw°°ÿ{³³ÿ~µµÿ|´´ÿx²²ÿn¬¬ÿb¦¦ÿa¥¥ÿa¥¥ÿa¥¥ÿ`¤¤ÿ`¤¤ÿ`¤¤ÿ`¤¤ÿ_¤¤ÿ_¤¤ÿ_¤¤ÿ_¤¤ÿ^¤¤ÿ_¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ[££ÿ[¢¢ÿ[¢¢ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[¢¢ÿ[¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿZ¢¢ÿZ¢¢ÿY¡¡ÿY¢¢ÿY¢¢ÿY¢¢ÿX¡¡ÿY¡¡ÿX¡¡ÿX¡¡ÿY¢¢ÿX¢¢ÿW¡¡ÿW¡¡ÿW  ÿW¡¡ÿW  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿV  ÿVŸŸÿV  ÿV  ÿUŸŸÿUŸŸÿVŸŸÿVŸŸÿVŸŸÿW  ÿVŸŸÿVŸŸÿVŸŸÿVŸŸÿWžžÿWžžÿYžžÿYŸŸÿYŸŸÿ[ŸŸÿ[ŸŸÿ[ŸŸÿ[ŸŸÿ\ŸŸÿ\  ÿ^  ÿ^  ÿ[ŸŸÿWœœÿTššÿR™™ÿT™™ÿU››ÿV››ÿWœœÿXžžÿVœœúC‘‘Ízzyyy*ÐÐ ÿÿ þþ þþ ÿÿÿÿþþÿÿþþþþþþþþþþÿÿþþÿÿþþÿÿ ÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUee#&||}N””Ù`ŸŸüa¡¡ÿ`  ÿ^  ÿ]  ÿ[ŸŸÿYžžÿXžžÿWÿVÿTœœÿS››ÿQ››ÿUÿ_££ÿg¨¨ÿj©©ÿi¨¨ÿh¨¨ÿh¨¨ÿh¨¨ÿj¨¨ÿk©©ÿi©©ÿg¨¨ÿd¦¦ÿc¦¦ÿc¦¦ÿb¥¥ÿc¦¦ÿb¥¥ÿa¥¥ÿa¥¥ÿb¥¥ÿa¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ_¥¥ÿ`¥¥ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ_¤¤ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]££ÿ\££ÿ\££ÿ[££ÿ[££ÿ\££ÿ[££ÿ[££ÿ[££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ[££ÿ\££ÿ\££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[¢¢ÿ[££ÿ[££ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿ[££ÿ[¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿW  ÿW  ÿW¡¡ÿW  ÿW  ÿX¡¡ÿX¡¡ÿX  ÿY¡¡ÿX  ÿX  ÿX  ÿXŸŸÿYŸŸÿYŸŸÿZŸŸÿ[  ÿ[  ÿ]  ÿ]¡¡ÿ\  ÿ]  ÿ]  ÿ^¡¡ÿ_¡¡ÿ]  ÿYÿU››ÿSššÿTššÿU››ÿVœœÿWœœÿYÿU››÷@ŽŽÆxxrrr&ÔÔÿÿÿÿþþþþ ÿÿ ÿÿ þþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿ ÿÿ þþ þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUll!$||wK’’Ô`  ûb¡¡ÿ`  ÿ^  ÿ^¡¡ÿ\  ÿZŸŸÿXžžÿWžžÿVÿUÿTœœÿR››ÿVÿa¤¤ÿi¨¨ÿk©©ÿkªªÿj©©ÿi¨¨ÿh¨¨ÿh¨¨ÿg§§ÿg§§ÿf§§ÿe§§ÿe§§ÿe§§ÿe§§ÿc¦¦ÿc¦¦ÿc¦¦ÿc¦¦ÿb¦¦ÿb¦¦ÿa¥¥ÿa¥¥ÿa¥¥ÿa¦¦ÿa¦¦ÿa¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ_¤¤ÿ^¤¤ÿ_¥¥ÿ_¤¤ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ]££ÿ\££ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ\¤¤ÿ]¤¤ÿ^¤¤ÿ]££ÿ]££ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ\¤¤ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[¢¢ÿ[¢¢ÿZ££ÿZ££ÿZ¢¢ÿ[¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿY¡¡ÿX¡¡ÿX¡¡ÿY¡¡ÿY¢¢ÿZ¢¢ÿY¢¢ÿY¡¡ÿZ¢¢ÿZ¡¡ÿZ¡¡ÿ[¡¡ÿZ  ÿ[  ÿ[  ÿ\  ÿ\  ÿ^¡¡ÿ^¡¡ÿ^¡¡ÿ]  ÿ^¡¡ÿ_¡¡ÿ`¢¢ÿ^¡¡ÿYžžÿVœœÿTššÿT››ÿU››ÿVœœÿXœœÿXÿUššõ<‹‹¾uuhjjÿÿÿÿÿÿÿÿÿÿþþþþÿÿÿÿ ÿÿ þþ ÿÿ ÿÿ ÿÿ þþ þþ ÿÿ þþ ÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿff"zznJ’’Í^  úb¡¡ÿa¡¡ÿ`¡¡ÿ_¡¡ÿ]  ÿ[  ÿYŸŸÿXžžÿWžžÿVÿTœœÿS››ÿVÿa££ÿk©©ÿlªªÿkªªÿkªªÿk©©ÿj©©ÿj¨¨ÿi¨¨ÿh¨¨ÿh¨¨ÿh¨¨ÿg§§ÿf¨¨ÿe§§ÿe§§ÿd¦¦ÿd§§ÿd§§ÿd§§ÿc¦¦ÿb¥¥ÿb¦¦ÿb¦¦ÿb¦¦ÿb¦¦ÿb¦¦ÿa¦¦ÿa¦¦ÿa¥¥ÿa¥¥ÿa¥¥ÿ`¥¥ÿ`¥¥ÿa¥¥ÿ`¥¥ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ_¤¤ÿ^¤¤ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¤¤ÿ^¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¥¥ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]££ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]££ÿ\££ÿ\££ÿ\££ÿ\££ÿ[££ÿ\££ÿ[££ÿ[££ÿZ££ÿZ¢¢ÿZ¢¢ÿ[¢¢ÿ[¢¢ÿ[¢¢ÿ[££ÿ[¢¢ÿ[££ÿ[¢¢ÿZ¢¢ÿ[¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿ[¢¢ÿ\££ÿ\££ÿ\¢¢ÿ]¢¢ÿ]¡¡ÿ]¡¡ÿ]¡¡ÿ]¡¡ÿ^¡¡ÿ_¢¢ÿ^¢¢ÿ^¢¢ÿ_¢¢ÿ`¢¢ÿ`¢¢ÿ`¡¡ÿ[ŸŸÿVœœÿT››ÿU››ÿVœœÿWœœÿXÿYÿSššñ:‰‰¶rr`kkUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþÿÿÿÿÿÿÿÿÿÿþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ee!yycFÃ]öb¡¡ÿa¢¢ÿ`¢¢ÿ_¡¡ÿ^¡¡ÿ\  ÿZŸŸÿYŸŸÿXžžÿVÿUÿTœœÿVÿ`££ÿkªªÿm««ÿlªªÿlªªÿkªªÿkªªÿj©©ÿj¨¨ÿi¨¨ÿi©©ÿh¨¨ÿh¨¨ÿg¨¨ÿg¨¨ÿf§§ÿf§§ÿe§§ÿe§§ÿe§§ÿd¦¦ÿd§§ÿc§§ÿc§§ÿc¦¦ÿc§§ÿc§§ÿc§§ÿb¦¦ÿb¦¦ÿb¦¦ÿb¦¦ÿa¦¦ÿb¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¥¥ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¥¥ÿ^¤¤ÿ^¥¥ÿ^¥¥ÿ_¥¥ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ\¤¤ÿ\££ÿ[££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ\££ÿ[££ÿ[££ÿ\££ÿ]££ÿ]££ÿ]££ÿ]¢¢ÿ^¢¢ÿ^¢¢ÿ_¢¢ÿ_¢¢ÿ^¢¢ÿ`££ÿ`££ÿ_££ÿ`££ÿa££ÿb££ÿa¢¢ÿ[ŸŸÿV››ÿU››ÿU››ÿVœœÿXÿYÿXœœþQ——ë4‡‡©ssThhÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿkkvvRBŽŽµ[ób¢¢ÿa¢¢ÿ`¡¡ÿ_¡¡ÿ^¡¡ÿ\  ÿ[  ÿYŸŸÿXŸŸÿWžžÿVÿTœœÿUÿ`¤¤ÿlªªÿn««ÿn««ÿl««ÿlªªÿlªªÿkªªÿj©©ÿj©©ÿi©©ÿi©©ÿi©©ÿi©©ÿi©©ÿh¨¨ÿg¨¨ÿf§§ÿf¨¨ÿf¨¨ÿe¨¨ÿd§§ÿe§§ÿe§§ÿd§§ÿd§§ÿd§§ÿc§§ÿc§§ÿd§§ÿc§§ÿc¦¦ÿc¦¦ÿc¦¦ÿc¦¦ÿb¦¦ÿc§§ÿc§§ÿb¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿb¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¥¥ÿ^¤¤ÿ^¥¥ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ\££ÿ]¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ_¤¤ÿ^¤¤ÿ^££ÿ^££ÿ_££ÿ_££ÿ`££ÿ`££ÿ`££ÿ`££ÿa££ÿa££ÿa££ÿb¤¤ÿc¤¤ÿb¤¤ÿ\  ÿVœœÿU››ÿVœœÿWœœÿXÿYžžÿXœœüK••Ü+‚‚“ppDffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿjj yyA@ŒŒ¥X››ê`¡¡þb¢¢ÿa¡¡ÿ`¡¡ÿ_¡¡ÿ]  ÿ[  ÿZŸŸÿXŸŸÿXžžÿVÿUœœÿVÿ_££ÿkªªÿo¬¬ÿn¬¬ÿm««ÿm««ÿm««ÿlªªÿlªªÿkªªÿk©©ÿk©©ÿj©©ÿjªªÿj©©ÿi©©ÿh¨¨ÿh¨¨ÿh¨¨ÿg¨¨ÿf¨¨ÿg¨¨ÿf¨¨ÿf¨¨ÿf¨¨ÿf¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe§§ÿe¨¨ÿe§§ÿe§§ÿd§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿb§§ÿb§§ÿb§§ÿb§§ÿc§§ÿc§§ÿb§§ÿb§§ÿb§§ÿb§§ÿb¦¦ÿb§§ÿb§§ÿb§§ÿb§§ÿa¦¦ÿ`¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿa¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ_¤¤ÿ_¤¤ÿ`££ÿ`¤¤ÿa¤¤ÿ`££ÿ`££ÿa££ÿa¤¤ÿb¤¤ÿb¤¤ÿc¤¤ÿd¥¥ÿc¤¤ÿ\  ÿVœœÿUœœÿVœœÿXÿYÿYžžÿWœœúE‘‘Ð"||}mm1__ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmmtt.4„„ƒN••Ó_  úb¢¢ÿa¢¢ÿ`¢¢ÿ_¡¡ÿ]¡¡ÿ\¡¡ÿZŸŸÿYŸŸÿXŸŸÿWžžÿVÿVžžÿ]¢¢ÿj©©ÿo¬¬ÿo¬¬ÿn««ÿn¬¬ÿn¬¬ÿn««ÿm««ÿmªªÿlªªÿkªªÿkªªÿkªªÿjªªÿjªªÿj©©ÿi©©ÿi©©ÿi©©ÿh©©ÿh©©ÿh©©ÿg¨¨ÿg¨¨ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿf©©ÿf©©ÿf¨¨ÿe¨¨ÿe¨¨ÿd¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿd¨¨ÿc§§ÿc§§ÿd¨¨ÿc§§ÿd¨¨ÿd§§ÿc¨¨ÿc§§ÿc§§ÿb§§ÿb§§ÿb§§ÿc§§ÿc§§ÿc§§ÿb§§ÿb§§ÿb§§ÿb§§ÿb§§ÿb¦¦ÿb¦¦ÿb¦¦ÿa¦¦ÿb¦¦ÿb¦¦ÿb¦¦ÿb¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¥¥ÿa¥¥ÿa¥¥ÿa¤¤ÿb¥¥ÿb¥¥ÿa¤¤ÿa¤¤ÿb¤¤ÿc¥¥ÿc¥¥ÿd¥¥ÿe¦¦ÿc¤¤ÿ[  ÿVœœÿVœœÿWÿXÿYžžÿYžžÿTššôAÃxxlmm#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUU mm%||^G‘‘º\žžõb¡¡ÿb¢¢ÿa¢¢ÿ`¢¢ÿ^¢¢ÿ]¡¡ÿ[  ÿZ  ÿYŸŸÿXžžÿWžžÿVÿ[¡¡ÿg¨¨ÿp¬¬ÿo­­ÿo­­ÿo¬¬ÿp¬¬ÿo¬¬ÿn««ÿn««ÿm««ÿlªªÿlªªÿl««ÿk««ÿk««ÿkªªÿkªªÿjªªÿjªªÿiªªÿiªªÿiªªÿiªªÿhªªÿiªªÿhªªÿhªªÿhªªÿhªªÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿf©©ÿf©©ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿd¨¨ÿd¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿd¨¨ÿd§§ÿd§§ÿd¨¨ÿd¨¨ÿd¨¨ÿc¨¨ÿc§§ÿc§§ÿc§§ÿc§§ÿd§§ÿc§§ÿc§§ÿc§§ÿc§§ÿb§§ÿc§§ÿb§§ÿc§§ÿc§§ÿc§§ÿb§§ÿc§§ÿc§§ÿb§§ÿb§§ÿb§§ÿb§§ÿc§§ÿc§§ÿc¦¦ÿb¥¥ÿc¥¥ÿc¥¥ÿc¥¥ÿc¥¥ÿc¥¥ÿd¥¥ÿd¥¥ÿe¦¦ÿe¦¦ÿe¦¦ÿb¤¤ÿZŸŸÿUœœÿVœœÿXÿYžžÿZžžÿYüM——å4‡‡¦ssVnnÿÿÿÿÿÿÿÿÿÿffyyC?ŒŒ Tššá`¡¡ûb££ÿa¢¢ÿ`¢¢ÿ_¡¡ÿ^¡¡ÿ]¡¡ÿ[  ÿZ  ÿYžžÿXžžÿVÿYžžÿd¥¥ÿo¬¬ÿr­­ÿp¬¬ÿp­­ÿp­­ÿp¬¬ÿo¬¬ÿo¬¬ÿn««ÿn««ÿn««ÿm««ÿl««ÿl««ÿl««ÿkªªÿkªªÿkªªÿjªªÿjªªÿjªªÿjªªÿjªªÿjªªÿjªªÿjªªÿiªªÿiªªÿiªªÿhªªÿh©©ÿh©©ÿh©©ÿg©©ÿh©©ÿh©©ÿh©©ÿg©©ÿgªªÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿf©©ÿg©©ÿf©©ÿf©©ÿf©©ÿf¨¨ÿe¨¨ÿf¨¨ÿf©©ÿf¨¨ÿe¨¨ÿf¨¨ÿf©©ÿe¨¨ÿf¨¨ÿf¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿd¨¨ÿd¨¨ÿd¨¨ÿd¨¨ÿd¨¨ÿd¨¨ÿd¨¨ÿd¨¨ÿe¨¨ÿd¨¨ÿc§§ÿd§§ÿd§§ÿc§§ÿd§§ÿe¨¨ÿe§§ÿd¦¦ÿd¥¥ÿd¦¦ÿd¦¦ÿe¦¦ÿe¦¦ÿe¦¦ÿe¦¦ÿf¦¦ÿe¦¦ÿ_¢¢ÿXžžÿVÿWÿXžžÿZžžÿZžžÿV››öD‘‘Ì&}}† oo>mmmmrr&,€€mI““Ã[ŸŸòb¡¡ÿb¢¢ÿa¢¢ÿ`££ÿ^¢¢ÿ]¡¡ÿ\¡¡ÿ[  ÿZ  ÿYŸŸÿWžžÿWžžÿ`££ÿn««ÿr­­ÿq­­ÿq­­ÿq­­ÿq­­ÿp­­ÿp­­ÿp¬¬ÿo¬¬ÿn««ÿo¬¬ÿo¬¬ÿn¬¬ÿn¬¬ÿm¬¬ÿl««ÿl««ÿl««ÿk««ÿk««ÿk««ÿk««ÿk««ÿk««ÿk««ÿk««ÿjªªÿjªªÿiªªÿiªªÿjªªÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿhªªÿhªªÿhªªÿhªªÿhªªÿhªªÿhªªÿhªªÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿf©©ÿg©©ÿf©©ÿg©©ÿf©©ÿf©©ÿf©©ÿf¨¨ÿf©©ÿf©©ÿf©©ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿf©©ÿf¨¨ÿf©©ÿf©©ÿe¨¨ÿe¨¨ÿe§§ÿe¨¨ÿe§§ÿf§§ÿf§§ÿe¦¦ÿe¦¦ÿe¦¦ÿf§§ÿg§§ÿg§§ÿg§§ÿf¦¦ÿe¥¥ÿ]¡¡ÿWÿVÿXžžÿXžžÿYžžÿZþQ˜˜ê9ŠŠ±vvcee#qqvvG;ŠŠ›S™™ß`  ûb££ÿa££ÿ`££ÿ_££ÿ^¢¢ÿ]¡¡ÿ]¡¡ÿ[  ÿZŸŸÿXŸŸÿWžžÿ^¡¡ÿk©©ÿr®®ÿs®®ÿr®®ÿq­­ÿr®®ÿq­­ÿq­­ÿq­­ÿp¬¬ÿp¬¬ÿp­­ÿp­­ÿp­­ÿo­­ÿo¬¬ÿn¬¬ÿn¬¬ÿm¬¬ÿm««ÿl««ÿl««ÿl««ÿl««ÿl««ÿl¬¬ÿl««ÿl««ÿl««ÿl¬¬ÿl¬¬ÿk««ÿk««ÿk««ÿj««ÿk««ÿj««ÿi««ÿj««ÿi««ÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿhªªÿh©©ÿhªªÿhªªÿhªªÿhªªÿg©©ÿg©©ÿf©©ÿh©©ÿh©©ÿgªªÿg©©ÿgªªÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿg©©ÿf©©ÿf©©ÿg©©ÿg©©ÿg©©ÿf§§ÿg¨¨ÿg¨¨ÿg§§ÿg§§ÿf§§ÿf§§ÿg§§ÿg§§ÿg¨¨ÿh¨¨ÿh¨¨ÿg§§ÿb¤¤ÿ[  ÿWÿWžžÿXžžÿYžžÿYžžÿWœœùJ””×*‚‚ppDkkmmoo',lH‘‘¿Zœœïa¢¢ýb££ÿa££ÿ`££ÿ_¢¢ÿ^¢¢ÿ]¡¡ÿ\¡¡ÿ\  ÿZ  ÿXŸŸÿ\  ÿg§§ÿr­­ÿt¯¯ÿs®®ÿr®®ÿr®®ÿr®®ÿr®®ÿr®®ÿr­­ÿq­­ÿq­­ÿq­­ÿp­­ÿp­­ÿp­­ÿp­­ÿp­­ÿo¬¬ÿn¬¬ÿn¬¬ÿm««ÿm¬¬ÿm««ÿm¬¬ÿm¬¬ÿm¬¬ÿm¬¬ÿm¬¬ÿm¬¬ÿm¬¬ÿm¬¬ÿl¬¬ÿk¬¬ÿk¬¬ÿl¬¬ÿk¬¬ÿk¬¬ÿk¬¬ÿk««ÿk««ÿk««ÿj««ÿj««ÿj««ÿj««ÿj««ÿi««ÿiªªÿiªªÿiªªÿi««ÿi««ÿiªªÿiªªÿiªªÿhªªÿiªªÿi««ÿi««ÿhªªÿi««ÿiªªÿiªªÿh©©ÿh©©ÿh©©ÿh©©ÿg©©ÿg©©ÿh©©ÿh©©ÿh©©ÿh¨¨ÿh©©ÿh¨¨ÿh¨¨ÿg§§ÿg¨¨ÿh¨¨ÿh¨¨ÿi¨¨ÿi¨¨ÿh¨¨ÿf§§ÿ`££ÿYŸŸÿWÿXžžÿYžžÿYŸŸÿXûN––ä7ŠŠ²{{kii)mmiiss>4††‡O––Ñ^ŸŸöc¢¢þc££ÿa££ÿ`££ÿ`££ÿ^¢¢ÿ]¡¡ÿ\¡¡ÿ\¡¡ÿ[  ÿ[  ÿb¤¤ÿm««ÿs®®ÿu¯¯ÿt¯¯ÿt¯¯ÿt¯¯ÿt¯¯ÿs®®ÿs®®ÿr®®ÿr®®ÿr®®ÿq®®ÿq®®ÿq­­ÿq­­ÿp­­ÿp­­ÿp­­ÿp­­ÿo­­ÿo­­ÿo­­ÿo­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿm­­ÿm­­ÿm­­ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿk¬¬ÿk««ÿk¬¬ÿk««ÿk¬¬ÿk¬¬ÿk««ÿk««ÿk««ÿj««ÿk««ÿj««ÿj««ÿj««ÿj««ÿj««ÿj««ÿj««ÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿiªªÿjªªÿi©©ÿi©©ÿi©©ÿi©©ÿi¨¨ÿh¨¨ÿi©©ÿj©©ÿj©©ÿi¨¨ÿc¥¥ÿ[¡¡ÿWžžÿXžžÿXžžÿYŸŸÿYžžþS››ð?Â"}}~ qq?kk kk$zzS@ŸS˜˜Û^ŸŸùb££ÿb¤¤ÿa¤¤ÿ`££ÿ^¢¢ÿ^¢¢ÿ]¡¡ÿ]¡¡ÿ\¡¡ÿ[  ÿ]¢¢ÿf§§ÿp¬¬ÿt¯¯ÿu°°ÿt¯¯ÿu¯¯ÿt¯¯ÿt¯¯ÿs¯¯ÿs®®ÿs®®ÿs¯¯ÿs¯¯ÿr®®ÿr®®ÿr®®ÿr®®ÿr®®ÿr®®ÿr®®ÿq®®ÿq®®ÿq®®ÿp­­ÿp­­ÿp®®ÿp®®ÿo­­ÿo®®ÿo®®ÿp®®ÿo®®ÿn®®ÿo®®ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿm­­ÿm­­ÿm­­ÿm­­ÿm¬¬ÿm­­ÿm¬¬ÿm¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿk¬¬ÿk¬¬ÿk¬¬ÿk¬¬ÿk¬¬ÿk««ÿl¬¬ÿk¬¬ÿk««ÿk««ÿk««ÿkªªÿk««ÿk««ÿkªªÿj©©ÿj©©ÿj©©ÿj©©ÿk©©ÿj©©ÿkªªÿkªªÿf§§ÿ^¢¢ÿYŸŸÿXžžÿXžžÿYŸŸÿZžžÿXøK––Ù0††šttSii__nn%(||^D‘‘¬Xœœç`¡¡ûb££ÿa££ÿ`££ÿ_££ÿ_¢¢ÿ^¢¢ÿ]¡¡ÿ]¡¡ÿ[  ÿ[  ÿ`¢¢ÿh¨¨ÿq­­ÿt¯¯ÿv¯¯ÿu°°ÿu°°ÿt°°ÿt¯¯ÿt¯¯ÿu¯¯ÿu¯¯ÿt¯¯ÿt¯¯ÿt¯¯ÿs¯¯ÿs¯¯ÿs®®ÿs¯¯ÿr®®ÿs®®ÿr®®ÿq®®ÿr®®ÿr®®ÿr®®ÿq®®ÿq®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿo®®ÿo®®ÿo®®ÿo­­ÿo­­ÿo®®ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿm­­ÿm­­ÿm­­ÿm¬¬ÿm¬¬ÿm­­ÿm¬¬ÿm­­ÿm¬¬ÿm¬¬ÿm¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿm¬¬ÿl¬¬ÿm¬¬ÿm¬¬ÿm¬¬ÿl««ÿlªªÿl««ÿkªªÿkªªÿkªªÿkªªÿkªªÿlªªÿlªªÿlªªÿh¨¨ÿa¤¤ÿZ  ÿXžžÿXŸŸÿZŸŸÿZŸŸÿVœœùO˜˜æ=³{{i nn.ss \\ tt.2……oH‘‘¶Xœœç^¡¡úa££ÿa££ÿa££ÿ_££ÿ_¢¢ÿ^¢¢ÿ^¢¢ÿ\¡¡ÿ\  ÿ\¡¡ÿ`££ÿh¨¨ÿp¬¬ÿu°°ÿw±±ÿw±±ÿv°°ÿv°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿt¯¯ÿt¯¯ÿt¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿr®®ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿq¯¯ÿq®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿo®®ÿo­­ÿn­­ÿo­­ÿo­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿn­­ÿm­­ÿm­­ÿm¬¬ÿn¬¬ÿm¬¬ÿm¬¬ÿm««ÿm««ÿm««ÿlªªÿl««ÿl««ÿl««ÿn««ÿlªªÿh¨¨ÿb¤¤ÿ[  ÿXŸŸÿXŸŸÿZ  ÿZ  ÿYžžûP™™ä:ŒŒ²#~~yss>qqmmzz01††lB©R™™Û^  øb¤¤ÿa¤¤ÿ`££ÿ`££ÿ_££ÿ_££ÿ^¢¢ÿ]¡¡ÿ\¡¡ÿ\¡¡ÿ`¤¤ÿf§§ÿn¬¬ÿt¯¯ÿw±±ÿx±±ÿw±±ÿv°°ÿv°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿt°°ÿt¯¯ÿt°°ÿt°°ÿt¯¯ÿt¯¯ÿt¯¯ÿt¯¯ÿt°°ÿt°°ÿs°°ÿs°°ÿs¯¯ÿs¯¯ÿs¯¯ÿs°°ÿs°°ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿq¯¯ÿq®®ÿq®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿp®®ÿo­­ÿo®®ÿp®®ÿp­­ÿo­­ÿo­­ÿo­­ÿn­­ÿn­­ÿn­­ÿo­­ÿo¬¬ÿn¬¬ÿo¬¬ÿn¬¬ÿn¬¬ÿn««ÿo¬¬ÿo¬¬ÿm««ÿh¨¨ÿa££ÿ[  ÿXŸŸÿYŸŸÿYŸŸÿZ  ÿWžžúO™™ç@½'z rr<hhbb oo)$~~aD‘‘³Xœœça¢¢üb££ÿa££ÿa££ÿ`££ÿ`££ÿ_¢¢ÿ^¢¢ÿ]¡¡ÿ\¡¡ÿ\¡¡ÿ_¢¢ÿc¥¥ÿk©©ÿq®®ÿv°°ÿx±±ÿx±±ÿw±±ÿw°°ÿw°°ÿw°°ÿv°°ÿv°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿu°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿs°°ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿq®®ÿq®®ÿq®®ÿq®®ÿq®®ÿq®®ÿq®®ÿp­­ÿo­­ÿo­­ÿo­­ÿo­­ÿp­­ÿp­­ÿp­­ÿo¬¬ÿo¬¬ÿp­­ÿo¬¬ÿo¬¬ÿm««ÿh¨¨ÿa¤¤ÿ[  ÿYŸŸÿYŸŸÿZ  ÿYŸŸþVžžøO˜˜è<º$}}~ssBnnÿÿjj ss5,‚‚yL””Ç[ï`¢¢ûb££ÿa¤¤ÿa££ÿa££ÿ`££ÿ_££ÿ_¢¢ÿ^¢¢ÿ]¢¢ÿ\¡¡ÿ]¡¡ÿ`££ÿe¦¦ÿl««ÿt¯¯ÿx²²ÿy²²ÿw±±ÿw°°ÿw±±ÿw±±ÿv±±ÿv±±ÿv°°ÿv±±ÿv±±ÿv±±ÿv±±ÿv°°ÿv°°ÿu°°ÿu°°ÿu°°ÿt°°ÿu°°ÿt°°ÿu°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿs¯¯ÿs¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿq®®ÿq®®ÿq®®ÿq®®ÿq®®ÿp­­ÿq®®ÿq®®ÿq®®ÿq®®ÿq­­ÿq®®ÿq­­ÿq­­ÿn¬¬ÿj¨¨ÿd¦¦ÿ_££ÿ[  ÿYŸŸÿZ  ÿZ  ÿYŸŸýS››òF””Ó5ŠŠ§%€€yssBppffUU mm%{{D7ˆˆ}F““´RššÜ[žžó`¡¡üb¤¤ÿb¤¤ÿa££ÿ`££ÿ`££ÿ`££ÿ_££ÿ_¢¢ÿ]¢¢ÿ]¡¡ÿ]¢¢ÿb¥¥ÿlªªÿt¯¯ÿw±±ÿx±±ÿy²²ÿy²²ÿx²²ÿx²²ÿx²²ÿw±±ÿw±±ÿw±±ÿw±±ÿw±±ÿw±±ÿw±±ÿw±±ÿu°°ÿu°°ÿu°°ÿt°°ÿt°°ÿt°°ÿu°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿs°°ÿr¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr®®ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿs®®ÿr®®ÿq­­ÿlªªÿf§§ÿ_££ÿ[  ÿZ  ÿZ  ÿ[  ÿZ  ÿXžžûP™™êD’’Ê/……—xx] nn.qqffcctt.%€€Y5††ŽG’’¾S››à[ŸŸó`¡¡ýb££ÿa££ÿ`££ÿ`££ÿ`££ÿ_££ÿ_¢¢ÿ^¢¢ÿ]¢¢ÿ]¢¢ÿ`££ÿe§§ÿk©©ÿp¬¬ÿt¯¯ÿw±±ÿy²²ÿz²²ÿz³³ÿy²²ÿy²²ÿy²²ÿy²²ÿy²²ÿy²²ÿx²²ÿw±±ÿw±±ÿw±±ÿv±±ÿv±±ÿv±±ÿv±±ÿv±±ÿu±±ÿv±±ÿu°°ÿu°°ÿu°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt°°ÿu°°ÿt°°ÿt°°ÿt°°ÿt°°ÿt¯¯ÿt¯¯ÿt¯¯ÿs®®ÿq­­ÿmªªÿi¨¨ÿf§§ÿ`££ÿ\¡¡ÿZ  ÿZ  ÿ[  ÿ[  ÿYžžûS››ïL˜˜ß>ŽŽ¼(„uuNqq$ss __ ppvv8$~~a5ˆˆŽJ••ÃSššáWî^¢¢ú`¢¢þa¤¤ÿa££ÿ`££ÿ`££ÿ_¢¢ÿ^¢¢ÿ]¡¡ÿ]¡¡ÿ]¡¡ÿ^££ÿ`££ÿd¦¦ÿi¨¨ÿmªªÿq­­ÿt¯¯ÿv°°ÿz²²ÿz³³ÿz³³ÿz³³ÿz³³ÿz³³ÿz³³ÿx²²ÿx²²ÿw±±ÿw±±ÿw±±ÿw±±ÿw±±ÿw±±ÿw²²ÿw²²ÿw±±ÿw±±ÿv±±ÿv±±ÿv±±ÿv±±ÿv±±ÿv±±ÿv±±ÿu°°ÿu°°ÿt¯¯ÿr®®ÿm««ÿj©©ÿe¦¦ÿ`¤¤ÿ\¡¡ÿ[  ÿ[  ÿZ  ÿ[  ÿ[¡¡ÿZŸŸüVòN˜˜ß=»*„„‰`ww>qqmmqq ppxx9&\1……‚?ŽŽ®M––ÐXžžì`¡¡ýb¤¤ÿa¤¤ÿa££ÿa££ÿ_££ÿ_££ÿ^¢¢ÿ^¢¢ÿ]¢¢ÿ]¢¢ÿ]¢¢ÿ^¢¢ÿ_££ÿ`££ÿc¥¥ÿi©©ÿo¬¬ÿq®®ÿs¯¯ÿt°°ÿv±±ÿw±±ÿw±±ÿw±±ÿw±±ÿx²²ÿy²²ÿz³³ÿy³³ÿy²²ÿy³³ÿy²²ÿx²²ÿw±±ÿu±±ÿv±±ÿu°°ÿt¯¯ÿq®®ÿn¬¬ÿkªªÿg§§ÿe¦¦ÿc¦¦ÿ`¤¤ÿ]¡¡ÿ\¡¡ÿ[  ÿ[  ÿ[  ÿ\  ÿ[ŸŸþXžžùS››ïOššáB‘‘Á0ˆˆ” ~~grr< qqqq UUmm ff ss*xxJ0††ƒL——ÅXœœê\ŸŸñ\žž÷_¡¡üa££þa££ÿ`££ÿ`££ÿ`££ÿ_££ÿ^££ÿ^¢¢ÿ^¢¢ÿ]¡¡ÿ^¢¢ÿ^¢¢ÿ]¢¢ÿ_££ÿ_¤¤ÿ`¤¤ÿa¥¥ÿc¥¥ÿd¦¦ÿe§§ÿe¦¦ÿh©©ÿkªªÿl««ÿkªªÿkªªÿi©©ÿh©©ÿf¨¨ÿd§§ÿd¦¦ÿf§§ÿd¥¥ÿa¤¤ÿ^¢¢ÿ\¡¡ÿ\¡¡ÿ[  ÿ[  ÿ[¡¡ÿ[  ÿ[¡¡ÿ\¡¡ÿ[  þXžžûUõO˜˜äH””Ì9¨.……„!jwwBss!jj UUff nn{{<5‡‡i7‰‰€8ŠŠ™E’’ºL••ÓV››æYîZžžó\  ú_¢¢ý_££ÿ`££ÿ`££ÿ_££ÿ_££ÿ_££ÿ_¢¢ÿ^¢¢ÿ]¢¢ÿ]¢¢ÿ]¢¢ÿ\¢¢ÿ\¢¢ÿ\¡¡ÿ\¡¡ÿ\  ÿ\¡¡ÿ\¡¡ÿ\¡¡ÿ[¡¡ÿ\¡¡ÿ\¡¡ÿ\¢¢ÿ\¡¡ÿ\¡¡ÿ\¡¡ÿ\  ÿ\¡¡ÿ\¡¡ÿ\¡¡ÿ\¡¡ÿ[  ÿ[  ýYùUœœôQ™™èM˜˜âI––Ô<´/‡‡–#€€mvvG rr(mmjj UU__ff mmrr1xxH0„„d3ˆˆx1‡‡‹@§I••¿Q››ÞWðXó[ŸŸø^¡¡ü^¡¡ü^¡¡ý^¡¡ý^¡¡þ]¡¡þ^¡¡ÿ_¢¢ÿ_££ÿ_¢¢ÿ_¢¢ÿ_¢¢ÿ_¢¢ÿ^¢¢ÿ^¢¢ÿ^¢¢ÿ^¡¡ÿ^¡¡ÿ]¡¡ÿ\  þ\¡¡ý\¡¡ý\ŸŸüWœœ÷VœœôV››óQ˜˜êH””ÙCÉ8ŠŠ«.„„&r!d||Rss3iiuu __uu hhxx"ss3*ƒƒY.„„}.ƒƒŠ:ŠŠF°F““´D‘‘¿BÅE‘‘ÊE’’ËN˜˜ÚVœœìTššïXôYžžõYžžôXœœôXžžòWðUœœëT™™ãR™™ßSššàJ••ÔBÇ@¿C‘‘¯7‹‹˜/……‘-„„‹'€€uyyVssB pp+ttmm__mmffmmrruu%pp+uu2vv6ww>wwB ||V/……|1……‚=@Ž>ŽŽŠ>ŒŒ†@ŽŽ?x<ŽŽq'h{{a$\zzOss>uu2 xx& kkjjhhmmUUÿÿUUªªooxx*"ˆˆ"ˆˆ* .ss 8qq  jj jj __ffUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿÿÿÿÿÿþp?ÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿð?ÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€#ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿø?ÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(€ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ !#$%')*+,,-/001122222222222222222110/.-,++)(&%$"  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ !#&'*+-.011344566778889999:::::::::::::::::99998887765443210/-,*)&%"  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  $(*-/02457789:;;<==>>>????@@@@@@AAAAAAAAAAAA@@@@@@@???>>>==<<;::98765320.+)&" ÿÿÿÿÿÿÿÿÿÿÿÿ $(+.024679:;<==>??@@AABBBBBCCDDCDDDDDDDEEDDDDDDDDCDCCCBBBBAA@@@??>=<<;:87532/,)&!  #'+-024578::;<=>>?@@@ABD HO!!Z--e''a--f//l22p--e!!ZUP MHDDDDDDDDDDDCCCCCBBBBAA@@??>==<;:986531/,)%! "%(+-/12456789:; AL++^<>>>==<<;::988754310.,*'$    "$&()+/<44XHH†TT¶XXÕ[[è]]ö]]ý^^þ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^þ]]ú\\óZZçWWÒQQµCC‡''U @97777766554433210/.-,*('%#!   %%"GGNTTYYÈ\\è]]ú__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿ__ù[[ßTT·IIƒ44R4*(('&%$$#"! $$TT'[[o]]¶^^é__ü__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿ bbÿ bbÿ aaù__ä[[³QQn==2  mm\\/]]~__Ò``øaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿ bbÿ ccÿ ddÿ ddþ bbö ``Ð[[uOO33??[[*^^‰``Þaaûbbÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿbbÿ bbÿ ccÿ ddÿeeÿeeÿ ccö ``À]]e XXÿÿWW__x``Ôaaûbbÿbbÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿaaÿ bbÿ ccÿ ddÿddÿffÿffÿddô aa¾``ZTTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿff \\P__Âbbø ccÿbbÿbbÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿ bbÿ ccÿ ddÿeeÿffÿggÿggþffð bb«]]?HHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^^#^^‘bbê ddþ ddÿ ccÿbbÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿbbÿbbÿ ccÿ ccÿ ddÿeeÿffÿggÿhhÿggýddá aa~ [[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿUU]]Gaa ccú ddÿ ccÿ ccÿbbÿaaÿaaÿaaÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ``ÿ``ÿ``ÿ``ÿaaÿbbÿbbÿ bbÿ ccÿ ddÿeeÿffÿggÿhhÿiiÿgg÷ddµ]]?ffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿ þþÿÿÿÿþþÿÿÿÿþþÿÿ þþ þþÿÿÿÿÿÿii__s bbâeeþ eeÿ ddÿ ccÿbbÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿaaÿbbÿ bbÿ ccÿ ddÿeeÿeeÿggÿhhÿjjÿiiýffÚ bbeUU ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþÿÿþþÿÿþþþþÿÿþþþþÿÿþþÿÿþþ þþ {{!``— ddòffÿffÿ eeÿ ddÿ bbÿaaÿ``ÿ``ÿ``ÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ^^ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿbbÿbbÿ bbÿ ccÿ ddÿeeÿffÿggÿhhÿjjÿkkÿhhí bb† XXÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿÿÿþþÿÿþþ#þþ'þþ*þþ+þþ+þþ)þþ%ÿÿ þþžž2qq«kkùiiÿffÿ eeÿ ddÿ ccÿbbÿaaÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿaaÿbbÿ ccÿ ccÿ ddÿeeÿffÿggÿiiÿjjÿllÿiiöccŸ__ ÿÿÿÿÿÿÿÿþþÿÿ ÿÿþþþþþþþþ%þþ,þþ1þþ6þþ9þþ9þþ7þþ2ôô0˜˜Œ)ˆˆ÷zzÿooÿ iiÿ ffÿ ddÿ bbÿbbÿaaÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿ ccÿ ccÿ ddÿeeÿffÿhhÿiiÿkkÿllÿkkùdd¬\\$ÿÿÿÿÿÿÿÿþþþþ ÿÿþþþþþþ#þþ*þþ2þþ9þþ?ÿÿDþþFÿÿDþþ?ÔÔZ,œÍ%‹‹ý||ÿ ppÿ iiÿ ffÿccÿaaÿ``ÿ``ÿ__ÿ^^ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿbbÿ ccÿ ddÿeeÿggÿhhÿiiÿkkÿmmÿmmúgg¯\\$ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿþþþþþþ#þþ,þþ5þþ=þþFþþLÿÿPþþOþþK××f'  Ð!ŽŽý~}ÿ qqÿjjÿeeÿccÿa`ÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿ ccÿ ccÿ ddÿffÿggÿhhÿjjÿllÿnnÿnnúff«\\!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þþþþþþþþ!þþ+þþ5ÿÿ@þþJþþSþþXþþYÿÿUÛÛm$¦¥Ðý€€ÿ rrÿjjÿffÿbbÿ``ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿaaÿbbÿ ccÿ ddÿ eeÿffÿhhÿiiÿkkÿmmÿooÿooùhh¡ bbÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ ÿÿ ÿÿþþþþþþ)þþ5þþAþþMþþXþþ^ÿÿ`þþ]ßßr"¨¨Ñ’’þ‚ÿssÿkkÿeeÿbbÿ__ÿ]]ÿ]]ÿ]]ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ^^ÿ__ÿ``ÿ``ÿaaÿaaÿbbÿ ccÿ ddÿeeÿggÿiiÿjjÿmmÿooÿ"qqÿ"ppöhh__ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ þþ ÿÿþþÿÿ(þþ5þþBÿÿPþþ\þþcþþeþþcààw««Ó””þƒƒÿttÿkkÿeeÿaaÿ__ÿ^^ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ]]ÿ__ÿ__ÿ``ÿbbÿbbÿbbÿbbÿccÿccÿccÿccÿbbÿbbÿbbÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ^^ÿ__ÿ``ÿ``ÿaaÿbbÿccÿ ccÿ ddÿffÿhhÿiiÿkkÿnnÿ qqÿ%ssÿ#rrïhhpmmÿÿÿÿÿÿÿÿÿÿÿÿþþþþ þþþþþþ'þþ5þþCþþSÿÿ`þþiþþkþþiââ}­­Ö––þ „„ÿuuÿkkÿffÿbbÿ^^ÿ\\ÿ\\ÿ[[ÿ\\ÿ\\ÿ\\ÿ\\ÿ\\ÿ]]ÿ^^ÿ``ÿaaÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿ``ÿ``ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿccÿ ddÿ eeÿggÿhhÿjjÿmmÿppÿ#ssÿ(uuÿ#qqàeeKÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿþþþþ'þþ6þþFÿÿUþþdþþmþþpþþn å尰ט—þ ……ÿvuÿllÿffÿaaÿ__ÿ]]ÿ\\ÿ\\ÿ\\ÿ]]ÿ]]ÿ__ÿaaÿ ccÿ bbÿ ccÿ ccÿ ccÿ ccÿccÿccÿddÿddÿccÿccÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿ``ÿ``ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿ ccÿ eeÿffÿggÿjjÿllÿooÿ!rrÿ'uuÿ*wwþ ooÄ bb'ÿÿÿÿÿÿÿÿÿÿ ÿÿþþÿÿ(þþ7þþHþþYþþgþþpþþtþþs ææ…³³Øþ Œ‹ÿ~}ÿuuÿppÿkkÿggÿffÿffÿhhÿjjÿ iiÿ iiÿ hhÿ hhÿ ggÿ eeÿ ddÿ ddÿ ccÿ ccÿ ccÿ ddÿ ccÿddÿddÿddÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿ ddÿ ffÿggÿiiÿkkÿnnÿqqÿ%ttÿ+xxÿ,wwùmm“[[ÿÿÿÿÿÿÿÿÿÿ ÿÿþþþþ*þþ:þþLþþ\þþlþþuþþzÿÿx è艺ºØ££þ ““ÿ……ÿ{{ÿuuÿppÿooÿppÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ qqÿ ppÿ ooÿ mmÿ llÿ jjÿ hhÿ eeÿ ddÿ ddÿ ddÿ ddÿddÿddÿccÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿ``ÿ``ÿbbÿ ccÿ eeÿffÿhhÿjjÿmmÿppÿ#ssÿ)wwÿ-zzÿ*wwêkkXUUÿÿÿÿÿÿÿÿÿÿ ÿÿþþþþ+ÿÿ<þþNÿÿ`þþoþþ{þþ~üü} ëë½½Ú¦¦þ ––ÿ‡‡ÿ~~ÿzzÿ wwÿ uuÿ rrÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ qqÿ rrÿ ssÿ rrÿ rrÿ qqÿ ooÿ mmÿ kkÿ iiÿ ffÿ eeÿ ddÿ ddÿddÿccÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿ``ÿ__ÿ^^ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿccÿ ddÿ ffÿhhÿjjÿllÿooÿ"ssÿ'vvÿ,yyÿ1||þ'uuÄee#ÿÿÿÿÿÿÿÿÿÿ þþÿÿþþ-þþ>þþQþþcþþtþþ~ýýƒýý‚ íí‘ÀÀÛ©©þ ššÿ ŽŽÿ ††ÿ ÿ yyÿ vvÿ ttÿ rrÿ rrÿ qqÿ qqÿ qqÿ qqÿ rrÿ rrÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ ppÿ nnÿ jjÿ ggÿ eeÿ ddÿddÿccÿccÿccÿccÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ__ÿ__ÿ__ÿ__ÿ``ÿ``ÿaaÿbbÿ ccÿ eeÿggÿiiÿkkÿooÿ!rrÿ%uuÿ*xxÿ0||ÿ2||÷ ppHHÿÿÿÿÿÿþþ þþÿÿ þþ/ÿÿ@þþTþþhüüxýýƒýýˆýý‡ íí•ÃÂܯ¯þ  ÿ ’’ÿ ‡‡ÿ €€ÿ zzÿ wwÿ ttÿ ssÿ ssÿrrÿ rrÿ rrÿ qqÿ rrÿ rrÿ rrÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ qqÿ ooÿ kkÿ ggÿddÿddÿccÿccÿccÿccÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ``ÿ``ÿ``ÿaaÿccÿ eeÿggÿiiÿkkÿnnÿqqÿ$ttÿ)xxÿ.{{ÿ4ÿ.zz×ee2ÿÿÿÿÿÿþþÿÿ þþþþ!þþ1þþCþþWþþjþþ{ýý‡ýýŒýý‹îî™ÅÅÞ²²þ££ÿ ””ÿ ŠŠÿ ‚‚ÿ {{ÿ wwÿuuÿssÿssÿrrÿssÿrrÿssÿrrÿssÿ ssÿ ssÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ ppÿ nnÿkkÿffÿccÿccÿccÿccÿccÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿ``ÿ``ÿ``ÿ__ÿ__ÿ``ÿaaÿaaÿbbÿ ddÿ ffÿhhÿjjÿmmÿppÿ#ssÿ)wwÿ-{{ÿ3~~ÿ7û&ttŒTT ÿÿÿÿÿÿþþþþ þþÿÿ"þþ2þþEþþYþþnüüýý‹ýý‘ýýîížÈÈ൵þ¦¦ÿ ˜˜ÿ ÿ „„ÿ~~ÿyyÿvvÿttÿssÿssÿssÿssÿssÿssÿssÿssÿttÿ ttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ ppÿjjÿffÿddÿccÿccÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿ``ÿ``ÿaaÿaaÿ``ÿ``ÿaaÿaaÿbbÿ ccÿ eeÿggÿjjÿllÿooÿ!rrÿ'vvÿ,zzÿ2~~ÿ9‚‚ÿ2}}Ùmm1ÿÿÿÿÿÿþþþþ þþþþ#ÿÿ3þþFþþ[þþpýýýýŽýý•ýý•ïï£ËÊḸþ©©ÿ ››ÿ ÿ††ÿÿzzÿwwÿuuÿttÿttÿttÿttÿttÿttÿssÿttÿttÿttÿ ttÿ ttÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿppÿllÿggÿccÿccÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿ``ÿaaÿaaÿaaÿ``ÿaaÿaaÿbbÿ ccÿ ddÿffÿiiÿllÿooÿ rrÿ&uuÿ,yyÿ1}}ÿ8‚‚ÿ:ƒƒù%uu‡ff ÿÿÿÿÿÿþþþþþþþþ$þþ4þþHþþ]þþqýýƒýýýý˜ýý™ññ¦ÍÍ⻺þ¬¬ÿ žÿ ’’ÿˆˆÿÿ||ÿxxÿwwÿvvÿvvÿuuÿvvÿvvÿvvÿuuÿuuÿuuÿuuÿuuÿ uuÿ ttÿ ssÿ ssÿ ssÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿqqÿmmÿhhÿddÿbbÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿ ddÿffÿiiÿkkÿnnÿrrÿ$uuÿ*xxÿ0}}ÿ7ÿ=……ÿ8ßoo@ÿÿÿÿÿÿþþþþþþþþ$þþ5þþHþþ^þþrþý„ýý“ýý›ýýžññªÐÏã¾¾þ±°ÿ ¢¢ÿ––ÿÿ……ÿÿ{{ÿyyÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿwwÿvvÿ uuÿ uuÿ ttÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿrrÿqqÿnnÿggÿccÿbbÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿccÿ ccÿ eeÿhhÿjjÿnnÿqqÿ#ttÿ(xxÿ/||ÿ6€€ÿ=……ÿ@‡‡ü/{{•ff ÿÿÿÿÿÿÿÿþþ þþþþ#þþ4þþHþþ]þþsþþ†ýý•ýýŸýû¢ññ®ÓÒåÂÁþ´´ÿ ¦¦ÿššÿÿ‡‡ÿ€€ÿ||ÿzzÿxxÿxxÿwwÿwwÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿ vvÿ vvÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ rrÿ rrÿrrÿrrÿppÿllÿeeÿbbÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿbbÿbbÿ ccÿ eeÿggÿjjÿmmÿqqÿ"ttÿ'wwÿ.{{ÿ5€€ÿ<……ÿC‰‰ÿ:ƒƒÔoo)ÿÿÿÿÿÿþþþþ þþÿÿ"ÿÿ3þþFþþ]þþsþþ‡ýý˜ýý¡ýý¥óò²×ÕæÅÄþ··ÿ¨¨ÿ››ÿ‘‘ÿˆˆÿ‚‚ÿ}}ÿzzÿxxÿxxÿwwÿwwÿxxÿxxÿwwÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿ vvÿ vvÿ ttÿ ssÿ rrÿ rrÿ rrÿqqÿrrÿqqÿqqÿooÿiiÿccÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿbbÿccÿ eeÿggÿjjÿllÿppÿ!ssÿ&wwÿ-{{ÿ4ÿ;ƒƒÿBˆˆÿBˆˆô*xxaÿÿÿÿÿÿþþÿÿ þþþþ!þþ2þþFþþ]þþtýýŠýýšýý¥ýý©õóµ××çÆÆþ¹¸ÿªªÿÿ’’ÿ‰‰ÿ‚‚ÿ~~ÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ ttÿ ssÿ rrÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿllÿffÿbbÿaaÿaaÿaaÿaaÿaaÿaaÿbbÿccÿccÿccÿ eeÿggÿiiÿllÿooÿ rrÿ%vvÿ,zzÿ3~~ÿ9ƒƒÿ@‡‡ÿE‹‹ý2}}ž\\ ÿÿÿÿÿÿÿÿþþ ÿÿÿÿ þþ1þþEþþ]øøyåã¨õô¨ýý¨ýü¬óò¸ÙØèÉÈþ»ºÿ¬¬ÿŸŸÿ••ÿ‹‹ÿ„„ÿÿ||ÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿwwÿ wwÿ vvÿ uuÿ ssÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿppÿkkÿccÿaaÿaaÿaaÿaaÿaaÿbbÿccÿddÿ ddÿ eeÿffÿiiÿllÿnnÿrrÿ$uuÿ+zzÿ2~~ÿ8‚‚ÿ?††ÿF‹‹ÿ<……Òkk&ÿÿÿÿÿÿþþ þþÿÿ ÿÿ0ôôK¾¾¸¸ìÒÑÜôó­ýý«ýý¯ôô»ÛÚéÊÉþ¼¼ÿ®®ÿ¡¡ÿ––ÿŒŒÿ……ÿÿ||ÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿ wwÿ wwÿ vvÿ uuÿ ttÿ rrÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿmmÿffÿaaÿaaÿaaÿbbÿbbÿccÿddÿ ddÿ eeÿ ffÿhhÿkkÿnnÿqqÿ$uuÿ)yyÿ1}}ÿ8‚‚ÿ>††ÿDŠŠÿC‰‰ï$ttSÿÿÿÿÿÿÿÿÿÿ þþââ$šš ‘‘í¤¤ÿº¹ýÒÑÞöô¯ýý¯ýý³õõ¾ÝÜêÌËþ¾¾ÿ¯¯ÿ¢¢ÿ——ÿÿ††ÿ€€ÿ||ÿ{{ÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿ wwÿ wwÿ vvÿ uuÿ ttÿ ssÿ rrÿ qqÿqqÿqqÿqqÿqqÿqqÿooÿiiÿccÿaaÿbbÿbbÿccÿccÿ eeÿ ffÿ ggÿhhÿkkÿnnÿqqÿ#uuÿ(xxÿ/}}ÿ7‚‚ÿ=……ÿCŠŠÿF‹‹ü1~~TT ÿÿÿÿÿÿÈÈzzb xx僃ÿ’’ÿ¦¦ÿººýÓÒßõõ³ýý³üü·÷÷ÀÞÝêÍÌþÀ¿ÿ±°ÿ¤¤ÿ˜˜ÿŽŽÿ‡‡ÿÿ~~ÿ{{ÿzzÿyyÿxxÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿ wwÿ wwÿ vvÿ vvÿ ttÿ ssÿ rrÿ qqÿqqÿqqÿqqÿqqÿppÿppÿllÿeeÿbbÿbbÿccÿddÿ eeÿ ffÿ ggÿhhÿkkÿmmÿppÿ#ttÿ(xxÿ.||ÿ5ÿ=……ÿC‰‰ÿIÿ>††Ïnn%ÿÿ¿¿ddG iiÚrrþyyÿ„„ÿ ““ÿ¦¦ÿ¼»ýÖÓáöõ¶ýý¶ýýº÷÷ÃßÞëÏÎþÂÁÿ³³ÿ¦¦ÿššÿÿˆˆÿ‚‚ÿ~~ÿ{{ÿzzÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿ wwÿ vvÿ uuÿ ttÿ ssÿ rrÿ qqÿppÿppÿqqÿppÿppÿooÿmmÿffÿbbÿbbÿccÿ ddÿ ffÿggÿhhÿkkÿmmÿppÿ"ssÿ(xxÿ.||ÿ4€€ÿ<……ÿB‰‰ÿHŒŒÿEŠŠë$vvEZZ- ccÇiiþmmÿqqÿyyÿ ……ÿ ””ÿ¨¨ÿ½¼þÖÕãõõ»ýýºýý½÷÷ÅàßëÐÏþÃÂÿ´´ÿ§§ÿ››ÿ‘‘ÿ‰‰ÿƒƒÿÿ||ÿ{{ÿzzÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿ wwÿ wwÿ vvÿ uuÿ uuÿ ttÿ rrÿ qqÿqqÿqqÿppÿppÿppÿooÿooÿhhÿccÿccÿ eeÿ ffÿggÿiiÿkkÿmmÿooÿ!ssÿ'wwÿ.{{ÿ4€€ÿ:„„ÿ@ˆˆÿGŒŒÿHŒŒ÷+{{i[[ aaªhhûiiÿiiÿkkÿ ppÿ yyÿ ……ÿ••ÿ ª©ÿ¿¾þØÖåöö¾ýü½þý¿÷÷ÇáàìÑÐþÄÃÿ¶¶ÿ¨¨ÿÿ““ÿ‹‹ÿ„„ÿ€€ÿ}}ÿ{{ÿzzÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿwwÿ wwÿ vvÿ vvÿ uuÿ ssÿ qqÿqqÿppÿppÿppÿppÿooÿppÿooÿkkÿddÿ ddÿ ffÿggÿiiÿkkÿmmÿooÿ ssÿ'wwÿ-{{ÿ3ÿ:„„ÿ@‡‡ÿF‹‹ÿIý1}}ŒUU__ aaƒgg÷iiÿhhÿggÿhhÿ kkÿ ppÿ yyÿ††ÿ——ÿ ¬¬ÿÁÀþÚÙæ÷÷ÁüüÀýýÂ÷÷ÉâáìÒÑþÅÄÿ··ÿªªÿŸŸÿ””ÿŒŒÿ……ÿÿ~~ÿ||ÿ{{ÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿ wwÿ vvÿ vvÿ ssÿ rrÿ qqÿqqÿppÿppÿppÿppÿppÿppÿppÿllÿ ffÿ ffÿggÿiiÿkkÿmmÿooÿ rrÿ&vvÿ,zzÿ3ÿ9ƒƒÿ?‡‡ÿE‹‹ÿJŽŽþ7ªbb ``Jhhäjjÿhhÿggÿffÿeeÿ ggÿ jjÿppÿ{{ÿ‰‰ÿššÿ ±°ÿÆÅþÜÛè÷÷ÄüüÃüüÅ÷÷ËãáíÓÒþÇÆÿ¹¹ÿ¬¬ÿ¡¡ÿ——ÿŽŽÿˆˆÿƒƒÿÿ}}ÿ{{ÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿxxÿwwÿwwÿwwÿ vvÿ wwÿ vvÿ uuÿ ssÿ qqÿ qqÿqqÿppÿppÿppÿppÿppÿppÿppÿ nnÿ hhÿggÿiiÿllÿmmÿooÿ rrÿ&vvÿ,zzÿ3ÿ9ƒƒÿ?‡‡ÿE‹‹ÿJŽŽÿ>……ÉjjZZ"ffÀllþjjÿhhÿffÿeeÿeeÿ ffÿ iiÿooÿxxÿƒƒÿ’’ÿ££ÿ ¸·ÿÊÉþ ßÞé÷÷ÈüüÆüüÇ÷öÌ ãâîÕÔþÉÈÿ¼»ÿ¯¯ÿ¤¤ÿššÿ‘‘ÿŠŠÿ„„ÿÿ~~ÿ||ÿzzÿzzÿyyÿyyÿyyÿxxÿxxÿxxÿwwÿwwÿ vvÿ vvÿ wwÿ vvÿ vvÿ ssÿ qqÿqqÿqqÿppÿppÿppÿppÿppÿqqÿ rrÿ ooÿhhÿiiÿllÿmmÿnnÿrrÿ&vvÿ+zzÿ2ÿ8ƒƒÿ?‡‡ÿDŠŠÿIÿF‹‹å#tt9mm dd‚kkøllÿjjÿiiÿhhÿjjÿ llÿ mmÿ ooÿqqÿuuÿ}}ÿ‡‡ÿ––ÿ§§ÿ»ºÿÍËþ àßê÷÷ÉüüÈûûÉ÷÷Î äãî×ÖþËÊÿ¾¾ÿ²²ÿ§§ÿÿ””ÿÿ‡‡ÿƒƒÿ€€ÿ}}ÿ{{ÿzzÿyyÿyyÿxxÿwwÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ vvÿ ttÿ rrÿqqÿqqÿppÿppÿppÿppÿqqÿ qqÿ rrÿ ssÿppÿjjÿllÿnnÿooÿrrÿ%uuÿ+zzÿ2~~ÿ8‚‚ÿ>††ÿDŠŠÿIÿE‹‹í"uuJaa<hhÝnnÿooÿppÿppÿppÿ ooÿ nnÿ nnÿ mmÿnnÿqqÿvvÿÿ‹‹ÿššÿ«ªÿ½½ÿÎÍþ áàë÷÷ÌüüÊüüË÷öÐ åäïØ×þÍÌÿÁÀÿµµÿªªÿ  ÿ˜˜ÿÿŠŠÿ……ÿÿ~~ÿ||ÿ{{ÿzzÿyyÿyyÿxxÿwwÿwwÿwwÿwwÿ vvÿ vvÿ vvÿ uuÿ ssÿ qqÿqqÿqqÿqqÿppÿppÿqqÿ qqÿ rrÿ ssÿttÿqqÿmmÿnnÿooÿrrÿ%vvÿ+zzÿ2~~ÿ8‚‚ÿ>††ÿCŠŠÿHÿHô*xxY__ ii¤rrüuuÿvvÿttÿrrÿqqÿ ooÿ nnÿ mmÿmmÿmmÿnnÿrrÿxxÿ‚‚ÿŽŽÿÿ­­ÿ¿¿ÿ ÐÏþ âáìøøÍüüÌüüÍ÷öÒ æåïÚÚþÐÏÿÄÃÿ¹¹ÿ®®ÿ¥¥ÿ››ÿ““ÿÿˆˆÿƒƒÿ€€ÿ}}ÿ{{ÿzzÿyyÿxxÿxxÿwwÿvvÿwwÿwwÿ vvÿ uuÿ vvÿ uuÿ rrÿ qqÿ qqÿqqÿppÿqqÿqqÿ qqÿ rrÿ ssÿttÿvvÿssÿppÿppÿrrÿ%vvÿ+zzÿ1~~ÿ8ƒƒÿ>††ÿCŠŠÿHŒŒÿIŒŒö.zzb ooNvvëyyÿwwÿvvÿttÿrrÿ ppÿ ooÿ nnÿ llÿllÿllÿmmÿooÿssÿzzÿ„„ÿ‘‘ÿŸŸÿ¯¯ÿ ÃÂÿÔÓþ åãìø÷ÏýüÍüüÏ÷öÓ èçïÜÛþÒÑÿÇÆÿ¼»ÿ²²ÿ¨¨ÿŸŸÿ——ÿÿ‰‰ÿ……ÿÿ}}ÿ||ÿzzÿyyÿxxÿxxÿwwÿvvÿwwÿwwÿ vvÿ vvÿ uuÿ ssÿ qqÿ qqÿ qqÿqqÿqqÿ qqÿ qqÿ rrÿ ttÿuuÿvvÿxxÿttÿppÿrrÿ%vvÿ+zzÿ1~~ÿ8‚‚ÿ>††ÿCŠŠÿHŒŒÿIŒŒø-{{kqqtt­yyýyyÿwwÿuuÿttÿrrÿ ppÿ ooÿ nnÿllÿkkÿkkÿllÿmmÿppÿttÿ||ÿ††ÿ””ÿ¤¤ÿµµÿ ÆÅÿÕÔþ ååíø÷ÑüüÏûûÑùùÕ éèñÞÝÿÔÓÿ ÉÈÿÀ¿ÿµµÿ¬¬ÿ¢¢ÿ™™ÿ’’ÿŒŒÿ††ÿ‚‚ÿÿ}}ÿ{{ÿyyÿxxÿwwÿvvÿvvÿvvÿ vvÿ vvÿ uuÿ ttÿ ssÿ qqÿ qqÿ qqÿ rrÿ qqÿ qqÿ ssÿttÿuuÿwwÿxxÿxxÿttÿrrÿ&vvÿ,zzÿ2~~ÿ7‚‚ÿ>††ÿCŠŠÿHÿJŽŽû.||ƒff ppTyyí||ÿyyÿwwÿuuÿssÿqqÿ ppÿ ooÿnnÿmmÿmmÿllÿllÿllÿnnÿqqÿvvÿÿÿ ššÿ ¨¨ÿ ··ÿ ÈÇÿÖÕþ ææíø÷ÒüüÑüüÓùùØ êêòàßÿ ÖÕÿ ÍËÿÂÂÿ¸¸ÿ®®ÿ¥¥ÿœœÿ””ÿÿˆˆÿ„„ÿ€€ÿ}}ÿ{{ÿzzÿxxÿwwÿvvÿuuÿuuÿ uuÿ uuÿ uuÿ ssÿ rrÿ ppÿ qqÿ rrÿ rrÿ rrÿ ssÿttÿuuÿwwÿxxÿzzÿzzÿ uuÿ%vvÿ,zzÿ2ÿ8‚‚ÿ>††ÿCŠŠÿHÿIü-{{†ffmmvvª||þ{{ÿyyÿwwÿuuÿssÿqqÿ ppÿ ooÿnnÿnnÿnnÿmmÿllÿllÿllÿnnÿrrÿ }}ÿ ††ÿ ÿ œœÿ ªªÿ ¹¸ÿ ÉÈÿØÖþ èçíøøÓüüÓûûÖùùÚ ìëóâáÿ ÙØÿ ÏÎÿÅÄÿ»»ÿ±±ÿ§§ÿžžÿ––ÿÿ‰‰ÿ„„ÿ€€ÿ}}ÿ{{ÿyyÿxxÿwwÿvvÿuuÿuuÿ uuÿ uuÿ uuÿ rrÿ qqÿ qqÿ rrÿ ssÿ rrÿ ssÿttÿvvÿwwÿyyÿ{{ÿ||ÿ!{{ÿ&wwÿ,zzÿ3ÿ8ƒƒÿ>‡‡ÿDŠŠÿHÿJû-yy€pp=yyä}}ÿ{{ÿyyÿxxÿvvÿttÿ qqÿ ppÿ ppÿooÿnnÿnnÿmmÿmmÿllÿllÿnnÿ ttÿzzÿ ÿ ˆˆÿ ’’ÿ žžÿ ¬¬ÿ »ºÿ ÌÊÿÙØþ éèíùùÕûûÕüü×øøÝ ììô ãâÿ ÛÚÿ ÑÐÿ ÇÇÿ½¼ÿ³³ÿ©©ÿŸŸÿ——ÿÿ‰‰ÿ„„ÿ€€ÿ}}ÿ{{ÿyyÿxxÿwwÿvvÿuuÿ uuÿ uuÿ uuÿ ssÿ rrÿ qqÿ qqÿ rrÿ ssÿ ssÿttÿvvÿxxÿzzÿ{{ÿ}}ÿ#~~ÿ'||ÿ,{{ÿ2ÿ8ƒƒÿ>‡‡ÿDŠŠÿIÿIŒŒú*zzwUUmmuu}}û}}ÿ{{ÿyyÿwwÿvvÿttÿ rrÿ ppÿ qqÿppÿooÿnnÿmmÿmmÿllÿmmÿ qqÿuuÿwwÿ{{ÿÿ‰‰ÿ ””ÿ   ÿ ¯®ÿ ¾½ÿ ÍÌÿÛÚþ ééîùù×ûûØûûÚùùÞ îíô åäÿ ÜÜÿ ÓÒÿ ÉÈÿ¿¾ÿµµÿªªÿ¡¡ÿ™™ÿÿŠŠÿ„„ÿ€€ÿ}}ÿzzÿxxÿwwÿvvÿuuÿuuÿ uuÿ uuÿ ttÿ rrÿ rrÿ qqÿ rrÿ ssÿttÿuuÿvvÿxxÿzzÿ||ÿ }}ÿ$~~ÿ'€€ÿ-~~ÿ3ÿ8ƒƒÿ>‡‡ÿDŠŠÿIÿIù.{{tUUmm#yyÒ"ÿ}}ÿ{{ÿyyÿwwÿuuÿttÿ ssÿ qqÿqqÿppÿooÿnnÿmmÿmmÿllÿ qqÿttÿuuÿvvÿxxÿ||ÿ‚‚ÿ‹‹ÿ——ÿ ¤¤ÿ ²²ÿ À¿ÿ ÐÏÿÝÜþ êéïùùÙûûÚûûÜùùà ïîô æåÿ ÞÝÿ ÕÔÿ ÊÉÿÀÀÿ¶¶ÿ¬¬ÿ¢¢ÿ™™ÿÿ‰‰ÿ„„ÿ€€ÿ}}ÿzzÿxxÿwwÿvvÿuuÿuuÿ uuÿ ttÿ ssÿ rrÿ rrÿ rrÿ ssÿttÿuuÿvvÿxxÿzzÿ||ÿ!~~ÿ%ÿ(ÿ.ƒƒÿ4ÿ9ƒƒÿ?‡‡ÿDŠŠÿIÿJŽŽù-{{vUUssT }}ñ"ÿ}}ÿ{{ÿyyÿwwÿuuÿttÿ ssÿ qqÿppÿppÿooÿooÿnnÿmmÿppÿttÿuuÿuuÿvvÿvvÿyyÿ~~ÿ„„ÿŽŽÿššÿ ¨¨ÿ ¶µÿ ÄÃÿ ÒÑÿßÞþ ëêðùùÜûûÝûûÞùùá ððôèçÿáàÿ×ÖÿÍÌÿÃÂÿ¸¸ÿ­­ÿ¢¢ÿ™™ÿÿŠŠÿ„„ÿ€€ÿ||ÿyyÿwwÿwwÿwwÿuuÿ ttÿ uuÿ ssÿ rrÿ rrÿ rrÿssÿttÿuuÿvvÿxxÿzzÿ||ÿ"ÿ&€€ÿ)‚‚ÿ/……ÿ4……ÿ:„„ÿ?‡‡ÿDŠŠÿIÿHõ*zz`qq wwœ%ý#ÿ}}ÿ{{ÿyyÿwwÿuuÿttÿ ssÿ rrÿppÿppÿooÿnnÿnnÿooÿttÿuuÿuuÿvvÿvvÿvvÿxxÿ||ÿ‚‚ÿˆˆÿ““ÿžžÿ ««ÿ ¹¸ÿ ÇÆÿ ÕÔÿ áàþ îíñùùÞûûÞûûßùùâ ððõéèÿáàÿØ×ÿÏÎÿÅÄÿººÿ®®ÿ££ÿ™™ÿÿ‰‰ÿ„„ÿÿ{{ÿyyÿxxÿwwÿvvÿuuÿ uuÿ ttÿ rrÿ qqÿ rrÿssÿttÿuuÿvvÿxxÿzzÿ}}ÿ#ÿ(ÿ*‚‚ÿ0……ÿ5ˆˆÿ;‡‡ÿ@ˆˆÿEŠŠÿJŽŽÿG‹‹ì"ssI rr& ||Õ'‚‚ÿ#€€ÿ}}ÿ{{ÿyyÿwwÿvvÿuuÿ ssÿ qqÿppÿppÿooÿnnÿooÿttÿvvÿvvÿvvÿvvÿvvÿwwÿyyÿ{{ÿÿ……ÿŒŒÿ——ÿ¢¢ÿ ¯¯ÿ ½½ÿ ËÊÿ Ø×ÿ ãâþîîòùùàûúàûúáùùã ðïöéèÿáàÿØ×ÿÏÎÿÅÅÿ»»ÿ°°ÿ¥¥ÿ™™ÿÿ‰‰ÿƒƒÿ~~ÿ{{ÿyyÿwwÿwwÿvvÿuuÿttÿrrÿqqÿrrÿrrÿttÿvvÿwwÿxxÿ{{ÿ }}ÿ$€€ÿ)ÿ,ƒƒÿ1††ÿ6‰‰ÿ<ŠŠÿAˆˆÿF‹‹ÿKŽŽÿBˆˆßkk2ssK%ï(‚‚ÿ#€€ÿ}}ÿ{{ÿyyÿwwÿvvÿuuÿ ssÿ qqÿppÿppÿooÿnnÿ ssÿvvÿwwÿwwÿwwÿvvÿwwÿyyÿzzÿ{{ÿ}}ÿÿ‡‡ÿÿššÿ¦¦ÿ ³³ÿ ÁÀÿ ÎÌÿ ÚÚÿ ääþïïòùùáûúáûúâøøå ðïöéèÿáàÿØØÿÏÎÿÅÄÿ»»ÿ°°ÿ¦¦ÿ››ÿ‘‘ÿˆˆÿ‚‚ÿ~~ÿ{{ÿxxÿwwÿvvÿvvÿuuÿssÿrrÿrrÿssÿuuÿwwÿxxÿyyÿ{{ÿ!~~ÿ&ÿ*‚‚ÿ-ƒƒÿ2††ÿ8ŠŠÿ=ŒŒÿBŒŒÿHŒŒÿLÿA‡‡Öll(xx)ƒƒú(‚‚ÿ#€€ÿ~~ÿ{{ÿzzÿxxÿwwÿuuÿ ttÿ rrÿppÿppÿooÿppÿwwÿwwÿwwÿxxÿxxÿxxÿzzÿ{{ÿ{{ÿ||ÿ}}ÿÿ„„ÿ‹‹ÿ““ÿžžÿ««ÿ ··ÿ ÄÃÿ ÐÏÿ ÜÛÿ æåþðïòúùáûúâûúãùùä ñðõéèÿááÿÙØÿÏÎÿÅÄÿººÿ¯¯ÿ¥¥ÿœœÿ““ÿ‰‰ÿÿ}}ÿzzÿxxÿvvÿvvÿuuÿssÿssÿssÿssÿuuÿwwÿxxÿzzÿ||ÿ"~~ÿ'ÿ+ƒƒÿ/„„ÿ3‡‡ÿ9ŠŠÿ?ŽŽÿDÿIŽŽÿLÿ=„„¿jjoo ~~³-……þ)ƒƒÿ$ÿ~~ÿ||ÿ{{ÿyyÿwwÿvvÿ ttÿ rrÿqqÿppÿooÿttÿxxÿxxÿxxÿyyÿzzÿ{{ÿ{{ÿ{{ÿ||ÿ||ÿ}}ÿ~~ÿ‚‚ÿ††ÿŽŽÿ——ÿ¢¢ÿ®®ÿ º¹ÿ ÆÅÿ ÒÑÿ ÝÜÿ æåþðïóúùâûúâûúâùøä ððõéèÿâáÿÙØÿÏÍÿÄÃÿ¹¹ÿ®®ÿ¤¤ÿ››ÿ’’ÿ‰‰ÿ€€ÿ{{ÿyyÿwwÿwwÿuuÿssÿssÿssÿttÿuuÿwwÿyyÿ{{ÿ||ÿ$ÿ(‚‚ÿ,„„ÿ0……ÿ5‡‡ÿ;‹‹ÿ@ŽŽÿE‘‘ÿI‘‘ÿLý5~~™ff rr('‚‚Ù.††ÿ)ƒƒÿ%ÿ ÿ}}ÿ{{ÿzzÿxxÿvvÿ ttÿ ssÿqqÿppÿ rrÿxxÿxxÿxxÿyyÿ{{ÿ||ÿ||ÿ||ÿ||ÿ||ÿ}}ÿ~~ÿÿ€€ÿ„„ÿ‰‰ÿ‘‘ÿ››ÿ¦¦ÿ±°ÿ ½¼ÿ ÈÇÿ ÓÒÿ ÞÝÿ ææþððóùùãûúâûúâùøä ððõéèÿááÿØ×ÿÎÍÿÄÃÿ¸¸ÿ­­ÿ££ÿ™™ÿ‘‘ÿˆˆÿÿzzÿxxÿwwÿvvÿttÿssÿssÿuuÿvvÿwwÿyyÿ{{ÿ!}}ÿ%€€ÿ*‚‚ÿ.„„ÿ2††ÿ6ˆˆÿ<ŒŒÿAÿF’’ÿJ””ÿJ÷,xxnUUww<+„„è/‡‡ÿ*„„ÿ&ÿ!€€ÿ~~ÿ{{ÿzzÿxxÿvvÿ ttÿ ssÿqqÿqqÿvvÿyyÿyyÿyyÿ{{ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ}}ÿ~~ÿ~~ÿ€€ÿƒƒÿ††ÿÿ””ÿžžÿ§§ÿ³³ÿ¿¾ÿ ÉÈÿ ÔÒÿ ÞÝÿ çæþðïôùùãûúâûúâùùä ððõéèÿáàÿ×ÖÿÍÌÿÂÂÿ·¶ÿ¬¬ÿ¡¡ÿ——ÿŽŽÿ††ÿ}}ÿyyÿwwÿwwÿuuÿssÿssÿuuÿvvÿxxÿyyÿ {{ÿ#~~ÿ'€€ÿ,ƒƒÿ1……ÿ3‡‡ÿ8‰‰ÿ=ŒŒÿBÿG’’ÿK••ÿGï&ttOxxa/‡‡õ0‡‡ÿ+„„ÿ&‚‚ÿ"€€ÿ~~ÿ||ÿzzÿxxÿvvÿ uuÿ ssÿqqÿ ssÿzzÿzzÿzzÿzzÿ||ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ~~ÿ}}ÿ~~ÿ~~ÿ€€ÿ‚‚ÿ„„ÿ‰‰ÿÿ——ÿ  ÿ««ÿµµÿÀ¿ÿ ÊÉÿ ÔÓÿ ÞÝÿ çæþðïôùùãûúáûúâøøä ðïõèçÿàßÿÖÕÿÌËÿÀÀÿ´´ÿ¨¨ÿÿ““ÿ‹‹ÿƒƒÿ{{ÿxxÿwwÿvvÿttÿttÿuuÿvvÿxxÿzzÿ"||ÿ%ÿ)ÿ-„„ÿ2††ÿ5ˆˆÿ:ŠŠÿ?ÿDÿI““ÿM••ÿBÔoo)ff!||‰3ˆˆü1ˆˆÿ+„„ÿ'ƒƒÿ"ÿÿ||ÿzzÿyyÿvvÿ uuÿ ssÿrrÿwwÿ{{ÿzzÿ{{ÿ}}ÿ~~ÿ~~ÿ~~ÿ~~ÿÿÿ~~ÿ~~ÿÿÿ€€ÿÿ„„ÿ‡‡ÿ‹‹ÿ’’ÿ™™ÿ££ÿ¬¬ÿ¶¶ÿÁÀÿ ËÉÿ ÕÔÿ ßÞÿ ççþðïôùøãûûáûúáùùã ïïõççÿßÞÿÔÔÿÉÈÿ¾½ÿ±±ÿ¥¥ÿ™™ÿÿˆˆÿ‚‚ÿzzÿwwÿvvÿttÿuuÿvvÿwwÿyyÿ {{ÿ#}}ÿ'€€ÿ+‚‚ÿ/……ÿ4‡‡ÿ7‰‰ÿ<‹‹ÿAÿF‘‘ÿJ””ÿM––ý6‡‡¤mmss &§6ŠŠþ2ˆˆÿ-……ÿ'ƒƒÿ#ÿÿ||ÿzzÿyyÿvvÿ uuÿ ttÿ ttÿzzÿ||ÿ||ÿ}}ÿ~~ÿ~~ÿ~~ÿÿÿÿÿÿÿÿ€€ÿ€€ÿÿƒƒÿ……ÿ‰‰ÿŽŽÿ””ÿœœÿ¤¤ÿ­­ÿ··ÿÁÀÿ ËÊÿ ÖÕÿ ßßÿ èçþñðóùùâûúáûúâùùã ïîõæåÿÝÝÿÒÑÿÇÆÿººÿ®®ÿ¡¡ÿ••ÿŒŒÿ††ÿ€€ÿyyÿvvÿvvÿvvÿwwÿyyÿzzÿ"||ÿ%~~ÿ)ÿ-ƒƒÿ2††ÿ5ˆˆÿ8‰‰ÿ>ŒŒÿCÿG’’ÿK••ÿJ””ö+jUUff*ƒƒ²7‹‹ÿ3‰‰ÿ-††ÿ(„„ÿ$‚‚ÿÿ}}ÿ{{ÿyyÿwwÿ uuÿ ttÿvvÿ||ÿ||ÿ~~ÿ~~ÿÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿ€€ÿÿ‚‚ÿ„„ÿ‡‡ÿ‹‹ÿÿ••ÿœœÿ¥¥ÿ®®ÿ¸¸ÿÂÁÿ ÌËÿ ÖÔÿ àßÿ ççþððóùøâûúáûúàùùá ïîôåäÿÛÛÿÐÏÿÄÃÿ··ÿ©©ÿÿ’’ÿ‰‰ÿ „„ÿÿxxÿwwÿwwÿxxÿzzÿ!{{ÿ$}}ÿ'ÿ+‚‚ÿ0……ÿ4‡‡ÿ7‰‰ÿ;ŠŠÿ@ÿEÿI““ÿM––ÿF‘‘á{{<tt-……Å8ŒŒÿ3‰‰ÿ.‡‡ÿ)……ÿ%‚‚ÿ €€ÿ}}ÿ{{ÿzzÿwwÿvvÿ uuÿzzÿ}}ÿ~~ÿÿÿÿ€€ÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿÿÿ‚‚ÿ„„ÿ‡‡ÿ‹‹ÿÿ––ÿÿ¥¥ÿ®®ÿ¸¸ÿÂÁÿ ÌËÿ ÕÔÿ ßÞÿ çæþïïóùùáûúßûúßùùà îîóããÿÚÙÿÍÌÿÀÀÿ²²ÿ¥¥ÿ˜˜ÿÿ!ˆˆÿ"ƒƒÿ~~ÿyyÿwwÿyyÿ zzÿ#||ÿ&~~ÿ*€€ÿ.ƒƒÿ2††ÿ7ˆˆÿ9ŠŠÿ=ŒŒÿBÿG‘‘ÿK””ÿN••þ;‰‰° mmxx&3ˆˆØ:ÿ5ŠŠÿ/ˆˆÿ*……ÿ%ƒƒÿ!€€ÿ~~ÿ{{ÿzzÿxxÿvvÿvvÿ}}ÿ~~ÿÿÿ€€ÿ€€ÿ€€ÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿÿÿÿƒƒÿ„„ÿ††ÿ‰‰ÿÿ‘‘ÿ––ÿÿ¥¥ÿ®®ÿ··ÿÁÁÿ ËÊÿ ÔÓÿ ÞÝÿ æåþðïòùùàûûÞûûÝùùÞ îíòâáÿØÖÿÊÉÿ½¼ÿ¯¯ÿ¡¡ÿ••ÿ!ŒŒÿ#‡‡ÿ#‚‚ÿ{{ÿyyÿ zzÿ"{{ÿ%}}ÿ)€€ÿ-‚‚ÿ1……ÿ5ˆˆÿ:ŠŠÿ;‹‹ÿ@ŽŽÿEÿI““ÿM••ÿL””ô(jUUww17ŠŠã;ÿ6‹‹ÿ0ˆˆÿ*……ÿ&ƒƒÿ"€€ÿ~~ÿ||ÿzzÿxxÿvvÿxxÿÿ€€ÿ€€ÿ€€ÿÿ€€ÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ „„ÿ……ÿ‡‡ÿŠŠÿÿ’’ÿ——ÿÿ¥¥ÿ®®ÿ··ÿÁÀÿ ÊÉÿ ÓÒÿ ÝÜÿååþ ïïòùùÞûûÜûûÛùùÜ ììòàßÿÕÔÿÆÅÿ¸¸ÿ««ÿžžÿ!’’ÿ#‹‹ÿ&……ÿ$ÿ!{{ÿ"||ÿ%}}ÿ(ÿ,ÿ/ƒƒÿ4‡‡ÿ8‰‰ÿ<‹‹ÿ>ŒŒÿCÿG’’ÿK””ÿO––ÿE‘‘Øww1||-6ŠŠß<ŽŽÿ7ŒŒÿ2‰‰ÿ,††ÿ&ƒƒÿ"ÿ~~ÿ||ÿzzÿxxÿvvÿ{{ÿ€€ÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚‚ÿ‚‚ÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ƒƒÿ ‚‚ÿ!ƒƒÿ!„„ÿ!……ÿ!††ÿ!ˆˆÿ ‹‹ÿŽŽÿ’’ÿ——ÿÿ¥¥ÿ®®ÿ··ÿÀÀÿÉÈÿ ÒÑÿ ÜÛÿåäþ îîñùùÜûûÚûûØùùÙ êéñÞÝþÑÐÿÃÂÿµµÿ§§ÿ!››ÿ$‘‘ÿ'ŠŠÿ(„ƒÿ$~~ÿ%}}ÿ(~~ÿ+€€ÿ.‚‚ÿ2……ÿ7ˆˆÿ;‹‹ÿ=ÿAŽŽÿEÿJ““ÿM••ÿO––û7††˜uu ||-8ŠŠß=ÿ8ÿ3ŠŠÿ-‡‡ÿ(„„ÿ#ÿ~~ÿ||ÿ{{ÿyyÿwwÿ~~ÿ!ÿ‚‚ÿ‚‚ÿ‚‚ÿÿ‚‚ÿÿÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"ƒƒÿ"„„ÿ"„„ÿ#„„ÿ#……ÿ"††ÿ"ˆˆÿ!‹‹ÿ ŽŽÿ““ÿ——ÿÿ¥¥ÿ­­ÿ¶¶ÿ¿¾ÿÈÆÿ ÑÐÿÛÚÿãâþ îìðùùÚûûÖûûÕùùÖ éçðÛÚþÎÍÿ¿¿ÿ±±ÿ¤¤ÿ$˜˜ÿ'ÿ*ˆˆÿ(‚‚ÿ'ÿ*€€ÿ.‚‚ÿ2„„ÿ5‡‡ÿ9‰‰ÿ>ŒŒÿ@ŽŽÿDÿH’’ÿL””ÿP––ÿI‘‘â xxFzz29ŒŒã>ÿ9ÿ4ŠŠÿ/‡‡ÿ*……ÿ%‚‚ÿ ÿ}}ÿ{{ÿyyÿxxÿ!ÿ!ƒƒÿ!ƒƒÿ ƒƒÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ‚‚ÿ ‚‚ÿ ‚‚ÿ ƒƒÿ ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ"„„ÿ#„„ÿ$„„ÿ$„„ÿ$„„ÿ$……ÿ$††ÿ#‡‡ÿ#‰‰ÿ"‹‹ÿ!ÿ ““ÿ——ÿÿ¤¤ÿ¬¬ÿ´´ÿ½¼ÿÆÅÿÏÎÿÙØÿááþ ìëîúúÕüüÓüüÑø÷ÓææïØØþËÊÿ½¼ÿ®®ÿ#¡¡ÿ'––ÿ+ÿ+‡‡ÿ)ÿ-ÿ1ƒƒÿ5††ÿ9ˆˆÿ=‹‹ÿAŽŽÿCÿG‘‘ÿK““ÿO••ÿP––ý9‡‡¤cczz6:ŒŒæ?ÿ:ŽŽÿ5‹‹ÿ0ˆˆÿ+……ÿ&‚‚ÿ!€€ÿ}}ÿ{{ÿyyÿzzÿ#ƒƒÿ#„„ÿ"„„ÿ ƒƒÿ ƒƒÿ ƒƒÿ ‚‚ÿ ƒƒÿ ƒƒÿ ‚‚ÿ ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ"„„ÿ"„„ÿ#……ÿ#……ÿ#……ÿ$……ÿ%……ÿ%……ÿ&……ÿ&……ÿ%……ÿ%††ÿ%ˆˆÿ%ŠŠÿ#ŒŒÿ#ÿ!’’ÿ ——ÿÿ££ÿªªÿ²²ÿººÿÃÃÿÍÌÿÖÖÿàßþ ëêíúúÒüüÐüüÎ÷÷ÐææíÖÕþÉÈÿººÿ!¬¬ÿ'ŸŸÿ,””ÿ/ÿ,††ÿ0„„ÿ5††ÿ9ˆˆÿ=ŠŠÿAÿCÿEÿI’’ÿN””ÿQ——ÿK““è$zzSzz2<ãA‘‘ÿ<ÿ7ŒŒÿ1ˆˆÿ,††ÿ'ƒƒÿ"ÿ~~ÿ||ÿzzÿ||ÿ%„„ÿ$……ÿ#„„ÿ!„„ÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ!ƒƒÿ"„„ÿ#„„ÿ#„„ÿ#……ÿ$††ÿ$††ÿ%††ÿ%……ÿ%……ÿ&††ÿ&††ÿ&††ÿ&††ÿ&††ÿ&‡‡ÿ&ˆˆÿ%ŠŠÿ$ŒŒÿ$ŽŽÿ"’’ÿ!––ÿ››ÿ  ÿ§§ÿ¯¯ÿ··ÿÀÀÿÊÉÿÔÓÿÝÜþééìøøÏýüËýüÊ÷öÍããìÔÓþÆÅÿ··ÿ%©©ÿ+ÿ0””ÿ0ŒŒÿ3‰‰ÿ9‰‰ÿ=ŠŠÿAŒŒÿDÿEÿI’’ÿL““ÿQ––ÿR——û:ˆˆ¡ ff{{!=ÔB’’ÿ=ÿ8ŒŒÿ3‰‰ÿ-††ÿ(„„ÿ$ÿÿ||ÿzzÿÿ'††ÿ%……ÿ%……ÿ#„„ÿ"„„ÿ!„„ÿ!„„ÿ"„„ÿ!ƒƒÿ!„„ÿ"„„ÿ#„„ÿ#„„ÿ$……ÿ%……ÿ%††ÿ%††ÿ%††ÿ&††ÿ&††ÿ&††ÿ'††ÿ'††ÿ'††ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ&ˆˆÿ&‰‰ÿ&ŒŒÿ&ÿ'““ÿ'——ÿ'œœÿ%¡¡ÿ$¨¨ÿ"°°ÿ··ÿÀÀÿÉÈÿÔÓÿÝÜþééêøøËüüÈüüÆø÷ÈááêÒÐþ ÄÃÿ$´´ÿ*§§ÿ1ÿ3““ÿ6ÿ;ŒŒÿ@ÿDÿFÿH‘‘ÿK““ÿP••ÿS——ÿK’’ß!vvEtt8ŒŒÆD’’ÿ?ÿ9ÿ4ŠŠÿ/‡‡ÿ*„„ÿ%ÿ ÿ}}ÿ{{ÿ!ÿ)‡‡ÿ'††ÿ%……ÿ%……ÿ"„„ÿ"„„ÿ#„„ÿ#„„ÿ"„„ÿ#……ÿ#……ÿ$……ÿ$……ÿ%††ÿ%††ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ)‡‡ÿ*‰‰ÿ-‹‹ÿ0ÿ2ÿ2’’ÿ2””ÿ1––ÿ1™™ÿ/œœÿ,¡¡ÿ*§§ÿ'­­ÿ#µµÿ½½ÿÇÆÿÑÐÿÛÚþéçèøøÈüüÄüüÂøøÃáàè ÏÎþ$ÁÁÿ(³³ÿ0§§ÿ3››ÿ7““ÿ>ÿC‘‘ÿG‘‘ÿH‘‘ÿK““ÿO••ÿS——ÿR––ù7††’ffxx7ŠŠ¼E““ÿ@‘‘ÿ;ŽŽÿ6‹‹ÿ1ˆˆÿ,……ÿ&‚‚ÿ"ÿ}}ÿ{{ÿ#ƒƒÿ*ˆˆÿ(‡‡ÿ'††ÿ&††ÿ$……ÿ$……ÿ#……ÿ$……ÿ$……ÿ$……ÿ$……ÿ%††ÿ%††ÿ&††ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ(‡‡ÿ(ˆˆÿ)ˆˆÿ+‰‰ÿ/‹‹ÿ3ŒŒÿ5ŽŽÿ6ÿ6ÿ6ÿ6‘‘ÿ5’’ÿ5““ÿ4••ÿ3——ÿ2ššÿ/ŸŸÿ,££ÿ)ªªÿ%²²ÿ!ººÿÄÄÿÏÎÿÙØþçæçùùÄüüÀýý½öö¿ÞÝæ$ÍÍþ)À¿ÿ.±±ÿ2££ÿ9ššÿ@––ÿF””ÿI““ÿJ’’ÿN””ÿR——ÿT——þFŽŽÈrr1uu 6ŠŠªF””ÿB’’ÿ=ÿ7ŒŒÿ2‰‰ÿ-††ÿ(ƒƒÿ#€€ÿ~~ÿ||ÿ%„„ÿ,ˆˆÿ*ˆˆÿ(‡‡ÿ'‡‡ÿ&††ÿ&††ÿ%……ÿ%……ÿ%††ÿ%††ÿ%††ÿ&††ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ*‰‰ÿ-ŠŠÿ2ŒŒÿ5ŽŽÿ7ÿ7ÿ8ÿ8ÿ8ÿ8ÿ8‘‘ÿ8‘‘ÿ7’’ÿ6’’ÿ6””ÿ5––ÿ4™™ÿ1œœÿ.¡¡ÿ+§§ÿ'¯¯ÿ"··ÿÁÁÿÍÌÿ×ÖþææåøøÁýý»ýý¹ööºÝÝä)ÌËþ.¾¾ÿ1®®ÿ8££ÿAœœÿH˜˜ÿJ••ÿM••ÿR——ÿU˜˜ÿQ••ì+||dffmm1‡‡‘F””ýD““ÿ?ÿ9ÿ4ŠŠÿ/‡‡ÿ*……ÿ%‚‚ÿ ÿ}}ÿ'……ÿ-‰‰ÿ+‰‰ÿ*ˆˆÿ(‡‡ÿ'‡‡ÿ'‡‡ÿ&††ÿ&††ÿ&††ÿ'††ÿ&‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'ˆˆÿ(ˆˆÿ(ˆˆÿ(ˆˆÿ)ˆˆÿ*‰‰ÿ.‹‹ÿ3ŽŽÿ6ÿ7ÿ7ÿ8ÿ8ÿ9ÿ9ÿ:ÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ9‘‘ÿ9‘‘ÿ9’’ÿ8““ÿ7””ÿ6——ÿ3ššÿ1žžÿ-¤¤ÿ)¬¬ÿ$µµÿ ¿¿ÿËÊÿÕÔþåääøø½ýý·ýý³ööµÜÚâ.ÉÉþ1ººÿ7­­ÿA¤¤ÿGÿJ˜˜ÿP˜˜ÿT™™ÿT——ù:††nn(‚‚^E““õF””ÿA‘‘ÿ<ŽŽÿ6‹‹ÿ1ˆˆÿ,……ÿ'ƒƒÿ"€€ÿ~~ÿ)‡‡ÿ.ŠŠÿ,‰‰ÿ+‰‰ÿ*ˆˆÿ)‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ'‡‡ÿ(ˆˆÿ(ˆˆÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ)‰‰ÿ,ŠŠÿ1ÿ6ÿ7ÿ8ÿ8ÿ9‘‘ÿ9ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ;‘‘ÿ<‘‘ÿ<’’ÿ<’’ÿ<’’ÿ;‘‘ÿ;’’ÿ:““ÿ9””ÿ8––ÿ6™™ÿ3ÿ0££ÿ+ªªÿ%³³ÿ!½½ÿ ÉÉÿ ÕÔýäâãøøºýý³üü¯öô¯"ÙÙà0ÆÆþ7¹¹ÿ?­­ÿE££ÿLžžÿRœœÿU››ýEŽŽÃrr1$?D’’éH••ÿC’’ÿ>ÿ9ÿ3ŠŠÿ.††ÿ)„„ÿ$ÿ €€ÿ*ˆˆÿ0‹‹ÿ.ŠŠÿ,‰‰ÿ+‰‰ÿ*ˆˆÿ)‡‡ÿ(ˆˆÿ(‡‡ÿ'‡‡ÿ(‡‡ÿ(‡‡ÿ(ˆˆÿ(ˆˆÿ)‰‰ÿ)‰‰ÿ*‰‰ÿ+‰‰ÿ/ŒŒÿ4ŽŽÿ7ÿ7ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:‘‘ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ<‘‘ÿ<’’ÿ=““ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ=’’ÿ<““ÿ;““ÿ:––ÿ7˜˜ÿ5œœÿ1¡¡ÿ,¨¨ÿ'±±ÿ"¼¼ÿ!ÈÇÿ!ÓÒýááâ÷÷¶ýý¯ýû© ôôª$ÖÖÞ7ÆÆý<¸¸ÿC««ÿK¤¤ÿRŸŸÿL––ß$~~Uff ||'D‘‘ÙI––ÿE““ÿ@‘‘ÿ;ŽŽÿ6‹‹ÿ1ˆˆÿ+……ÿ&‚‚ÿ!ÿ*ˆˆÿ2ŒŒÿ/‹‹ÿ-ŠŠÿ,‰‰ÿ+‰‰ÿ*ˆˆÿ)ˆˆÿ)ˆˆÿ)‡‡ÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)ˆˆÿ)‰‰ÿ*‰‰ÿ,ŠŠÿ1ÿ5ÿ7ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:‘‘ÿ:’’ÿ;’’ÿ<’’ÿ<’’ÿ=’’ÿ=’’ÿ=““ÿ>““ÿ>““ÿ>““ÿ>““ÿ>““ÿ?““ÿ?““ÿ>““ÿ=’’ÿ<’’ÿ;““ÿ:••ÿ8——ÿ5››ÿ2ŸŸÿ-§§ÿ(¯¯ÿ$¹¹ÿ#ÆÅÿ"ÑÐýàßà÷ö²ýý©ýý£ ôô¤*ÖÔÚ;ÃÃýB··ÿI¬¬ÿI¢¢í+ŽŽzÿÿÿÿªªªÿÿÿxx>¹K––ÿG””ÿB‘‘ÿ=ÿ8ŒŒÿ4ŠŠÿ.‡‡ÿ)„„ÿ#‚‚ÿ+ˆˆÿ4ÿ2ŒŒÿ/‹‹ÿ.ŠŠÿ-ŠŠÿ,‰‰ÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*ˆˆÿ*‰‰ÿ*‰‰ÿ*‰‰ÿ-‹‹ÿ2ŽŽÿ6ÿ7ÿ8‘‘ÿ9‘‘ÿ9‘‘ÿ:‘‘ÿ;’’ÿ<““ÿ<““ÿ=““ÿ=““ÿ=““ÿ>““ÿ>““ÿ?““ÿ?””ÿ?””ÿ?””ÿ?””ÿ?””ÿ@””ÿ@””ÿ?““ÿ?““ÿ>““ÿ=““ÿ;””ÿ9––ÿ6ššÿ2žžÿ.¥¥ÿ)­­ÿ$··ÿ$ÃÃÿ$ÏÎýÞÝÞ÷ö­ýý£ýý óó-ÕÔÖ@ÃÃýB¶¶õ*§§ÂÂ*ÿÿ ÿÿÿÿÿÿ™ÌÌßÿÿÿÿÿmm6ŠŠ‘K––ýI••ÿE““ÿ?ÿ:ÿ6ŠŠÿ1ˆˆÿ,††ÿ&„„ÿ+ˆˆÿ5ŽŽÿ4ÿ1ŒŒÿ0‹‹ÿ/ŠŠÿ.ŠŠÿ-‰‰ÿ,‰‰ÿ+ˆˆÿ+‰‰ÿ+‰‰ÿ,‰‰ÿ,‰‰ÿ-‹‹ÿ4ŽŽÿ6ÿ7ÿ8‘‘ÿ9‘‘ÿ9’’ÿ:’’ÿ;’’ÿ<““ÿ<““ÿ=““ÿ=““ÿ>””ÿ>””ÿ>””ÿ?““ÿ@””ÿA••ÿA””ÿ@””ÿ@””ÿA””ÿA””ÿA””ÿA””ÿ@””ÿ@””ÿ?““ÿ>””ÿ<””ÿ:––ÿ7™™ÿ4ÿ/¤¤ÿ)««ÿ%µµÿ%ÁÁÿ%ÍÌýÜÛÝõõ©ýýýý– óó—.ÒÒÏ(Ãø××Søø(þþþþ ÿÿÿÿÿÿ‰°° ßïïÿÿÿÿÿ,ƒƒ[J••ñK––ÿG””ÿB‘‘ÿ=ŽŽÿ7ŒŒÿ3‰‰ÿ.‡‡ÿ)……ÿ,ˆˆÿ7ÿ5ŽŽÿ4ÿ1ŒŒÿ0‹‹ÿ/‹‹ÿ.ŠŠÿ-‰‰ÿ,‰‰ÿ,‰‰ÿ,‰‰ÿ-‰‰ÿ.‹‹ÿ4ŽŽÿ6ÿ8‘‘ÿ8‘‘ÿ9‘‘ÿ:’’ÿ:’’ÿ;’’ÿ<““ÿ<““ÿ=””ÿ>””ÿ?””ÿ?””ÿ@••ÿ@••ÿA••ÿB••ÿB••ÿB••ÿB••ÿA••ÿB••ÿB••ÿB••ÿB••ÿB••ÿA””ÿ@““ÿ@““ÿ>““ÿ=••ÿ;––ÿ8™™ÿ4œœÿ0¢¢ÿ*ªªÿ&³³ÿ&¿¿ÿ&ËÊýÙÙÜõô¥ýý™ýý‘õõ‹îîyþþRþþ:þþ%þþþþ ÿÿÿÿÿÿ²²áððÿÿÿww CËM——ÿI••ÿD’’ÿ?ÿ:ÿ6ŠŠÿ1ˆˆÿ,††ÿ-ˆˆÿ9ÿ8ÿ6ÿ4ÿ2ŒŒÿ1ŒŒÿ/‹‹ÿ.ŠŠÿ-ŠŠÿ-ŠŠÿ-‰‰ÿ.ŠŠÿ3ŽŽÿ6ÿ7ÿ8‘‘ÿ9‘‘ÿ:’’ÿ;’’ÿ;’’ÿ<““ÿ<““ÿ=““ÿ>””ÿ?••ÿ@••ÿA••ÿA––ÿB––ÿB––ÿB••ÿC––ÿC••ÿC––ÿC––ÿC––ÿC––ÿC••ÿC••ÿC••ÿB••ÿA””ÿ@““ÿ?’’ÿ?““ÿ=””ÿ;––ÿ8˜˜ÿ4››ÿ0¡¡ÿ+¨¨ÿ&±±ÿ&½½ÿ'ÉÉýØØÚõõŸýý“ýý‹üü|ÿÿfþþOþþ7þþ#þþþþ ÿÿÿÿÿÿ¯¯çþþ ÿÿÿqq <ŒŒ˜N——ýK––ÿG””ÿB‘‘ÿ=ŽŽÿ8ŒŒÿ3‰‰ÿ.‡‡ÿ,ˆˆÿ9ÿ:ÿ8ÿ6ÿ4ÿ2ŒŒÿ1ŒŒÿ0‹‹ÿ/‹‹ÿ.ŠŠÿ.ŠŠÿ3ÿ6ÿ7ÿ8ÿ9‘‘ÿ:’’ÿ:’’ÿ;’’ÿ<““ÿ=““ÿ=””ÿ?••ÿ?••ÿ@––ÿA––ÿB––ÿB——ÿC––ÿD——ÿD——ÿD——ÿD––ÿD——ÿE——ÿE——ÿD––ÿD––ÿD––ÿD––ÿD––ÿD––ÿB••ÿA““ÿ?““ÿ?““ÿ=””ÿ;••ÿ8——ÿ4ššÿ0  ÿ+§§ÿ'°¯ÿ'»»ÿ)ÇÇýÖÕØõõ›ýýŽýý‡üüxþþdþþLþþ6þþ#þþÿÿ ÿÿÿÿÿÿÿÿUU²² ÿÿÿÿÿ-‚‚ZL••òN——ÿJ••ÿF’’ÿ@ÿ;ÿ6ŠŠÿ1‰‰ÿ-ˆˆÿ9ÿ<‘‘ÿ:ÿ8ÿ6ÿ4ÿ2ÿ1ŒŒÿ1ŒŒÿ0‹‹ÿ2ŒŒÿ7ÿ8ÿ8‘‘ÿ9‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ<““ÿ=””ÿ?••ÿ?••ÿ@••ÿA••ÿA––ÿB––ÿB——ÿC——ÿC——ÿE——ÿE˜˜ÿE˜˜ÿE——ÿE——ÿE——ÿE——ÿE——ÿE——ÿE––ÿE––ÿD––ÿD––ÿD––ÿC••ÿA””ÿ@““ÿ?““ÿ>””ÿ:””ÿ8––ÿ5™™ÿ1ŸŸÿ+¥¥ÿ'­­ÿ(¹¹ÿ)ÅÅýÕÕÖóó—þýŠýýƒþþuþþaþþKþþ6þþ#þþÿÿ þþÿÿÿÿÿÿªªªÿÿÿ!&F‘‘ÒQ™™ÿM––ÿH””ÿC‘‘ÿ>ÿ9ŒŒÿ4ŠŠÿ/‰‰ÿ7ŽŽÿ?’’ÿ<ÿ:ÿ8ÿ6ŽŽÿ4ŽŽÿ3ÿ2ŒŒÿ2ŒŒÿ6ÿ8ÿ9‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ<’’ÿ=““ÿ>””ÿ?””ÿ?••ÿ@••ÿA––ÿB––ÿB––ÿC——ÿD——ÿD˜˜ÿE˜˜ÿE˜˜ÿF˜˜ÿE˜˜ÿF˜˜ÿF˜˜ÿF——ÿF——ÿF——ÿF——ÿF——ÿF——ÿE——ÿE––ÿD––ÿC––ÿB••ÿA””ÿA““ÿ?““ÿ=““ÿ;””ÿ8••ÿ5˜˜ÿ1ÿ+££ÿ(««ÿ)··ÿ+ÃÃý ÓÓÓòò“ýý†ýýþþsÿÿ`þþKþþ6þþ%þþþþ þþÿÿÿÿÿÿÿÿ ;ŠŠ—Q˜˜ýP˜˜ÿK••ÿF““ÿB‘‘ÿ=ŽŽÿ8‹‹ÿ2ŠŠÿ5ÿB””ÿ?’’ÿ<‘‘ÿ;ÿ9ÿ6ÿ5ŽŽÿ3ÿ5ŽŽÿ:‘‘ÿ:‘‘ÿ:‘‘ÿ;’’ÿ<’’ÿ<““ÿ=““ÿ>””ÿ?””ÿ@••ÿ@••ÿA––ÿB––ÿB——ÿD——ÿD——ÿD˜˜ÿE˜˜ÿF™™ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿG˜˜ÿG˜˜ÿH™™ÿH˜˜ÿG˜˜ÿG˜˜ÿF——ÿE——ÿE––ÿD––ÿD––ÿB••ÿA””ÿ@““ÿ?’’ÿ=““ÿ;““ÿ8••ÿ5——ÿ1››ÿ,¡¡ÿ(©©ÿ*µµÿ,ÀÀý!ÐÐÒôòŽýýƒþþ~þþrÿÿ`þþLþþ8þþ&þþÿÿÿÿÿÿÿÿÿÿ1……RO——îR™™ÿN——ÿI””ÿE’’ÿAÿ;ÿ6ŒŒÿ4‹‹ÿB””ÿB””ÿ?’’ÿ=‘‘ÿ;‘‘ÿ9ÿ7ÿ7ÿ:‘‘ÿ;‘‘ÿ;‘‘ÿ<’’ÿ<’’ÿ>””ÿ>””ÿ>””ÿ?””ÿ@••ÿA••ÿA••ÿB––ÿC––ÿC——ÿD——ÿE˜˜ÿE˜˜ÿF™™ÿF™™ÿF™™ÿF™™ÿG™™ÿG™™ÿH™™ÿH™™ÿI™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿF˜˜ÿE——ÿE––ÿD––ÿC––ÿB••ÿA””ÿ?’’ÿ>’’ÿ=’’ÿ:““ÿ8““ÿ5––ÿ0™™ÿ,žžÿ)¦¦ÿ+²²ÿ-¾¾ý"ÐÏÐ òòŒýý€þþ|þþqþþaþþNþþ:þþ)þþÿÿÿÿ ÿÿÿÿÿÿÿÿzzCŽŽ¶SššýQ˜˜ÿM––ÿH””ÿC‘‘ÿ?ÿ:ŽŽÿ5ŒŒÿ@““ÿE••ÿB””ÿ?’’ÿ>‘‘ÿ<‘‘ÿ9ÿ:ÿ?““ÿ>““ÿ=’’ÿ=““ÿ>““ÿ>””ÿ>””ÿ?””ÿ?””ÿ@••ÿA••ÿB––ÿC——ÿD——ÿD——ÿD——ÿE˜˜ÿE˜˜ÿF™™ÿG™™ÿG™™ÿG™™ÿHššÿHššÿIššÿI™™ÿI™™ÿI™™ÿH™™ÿH™™ÿH™™ÿH™™ÿH˜˜ÿF——ÿF——ÿE——ÿE––ÿD––ÿB••ÿ@““ÿ?’’ÿ>’’ÿ<’’ÿ:’’ÿ7““ÿ4””ÿ0˜˜ÿ,ÿ)¥¥ÿ+°°ÿ.¾½ü$ÍÍÐ óó‰þþ}þþyþþpþþaþþOÿÿ<þþ*þþþþÿÿ ÿÿÿÿÿÿÿÿ,QN——çSššÿP˜˜ÿL••ÿG’’ÿA‘‘ÿ=ÿ9ÿ;ÿF––ÿE••ÿB””ÿ@’’ÿ>‘‘ÿ;‘‘ÿ=’’ÿA””ÿ@””ÿ@””ÿ?””ÿ?””ÿ?””ÿ?””ÿ@••ÿ@••ÿA••ÿB––ÿC––ÿD——ÿE——ÿE——ÿE˜˜ÿE˜˜ÿF˜˜ÿG™™ÿHššÿH™™ÿIššÿIššÿIššÿIššÿIššÿIššÿI™™ÿIššÿI™™ÿI™™ÿI™™ÿH™™ÿH˜˜ÿF——ÿE——ÿE––ÿD––ÿC••ÿA””ÿ?““ÿ>’’ÿ=‘‘ÿ<‘‘ÿ:‘‘ÿ7’’ÿ4““ÿ0––ÿ,››ÿ*££ÿ,®®ÿ/»»ü$ÌÌÏ ññ†þþzþþwþþpþþaþþOþþ=þþ,þþþþþþ ÿÿÿÿÿÿÿÿxxD¨TššýR™™ÿO——ÿK••ÿE’’ÿA‘‘ÿ<ÿ9ŽŽÿF––ÿH——ÿE––ÿB””ÿ@““ÿ>’’ÿA””ÿC••ÿC••ÿC••ÿA••ÿA••ÿA••ÿ@••ÿB••ÿB––ÿB––ÿC––ÿD——ÿE——ÿE——ÿF˜˜ÿF˜˜ÿG™™ÿG™™ÿH™™ÿIššÿIššÿJššÿJššÿIššÿIššÿIššÿIššÿJššÿJššÿJ™™ÿI™™ÿI™™ÿI™™ÿH™™ÿG˜˜ÿF——ÿE——ÿE––ÿD––ÿB••ÿ@””ÿ?““ÿ>’’ÿ=‘‘ÿ;ÿ9ÿ6ÿ4’’ÿ0••ÿ-ššÿ*¡¡ÿ-­­ÿ0ººü&ËËÌ ññ‚ÿÿxþþwþþoþþaÿÿPþþ>þþ-þþþþþþ ÿÿÿÿÿÿÿÿ4††NP˜˜éUššÿR™™ÿN——ÿI””ÿD““ÿ?‘‘ÿ;ÿC““ÿJ˜˜ÿH——ÿE••ÿB””ÿA““ÿD••ÿE––ÿE––ÿE––ÿD––ÿB••ÿC••ÿC––ÿC––ÿC––ÿD——ÿD––ÿE——ÿE˜˜ÿF˜˜ÿG˜˜ÿG™™ÿH™™ÿH™™ÿHššÿIššÿIššÿJ››ÿJ››ÿK››ÿK››ÿJ››ÿK››ÿK››ÿKššÿKššÿIššÿI™™ÿI™™ÿI™™ÿH™™ÿG˜˜ÿF——ÿE——ÿD––ÿD––ÿA••ÿ@””ÿ?““ÿ=‘‘ÿ<ÿ;ÿ:ÿ7ÿ4’’ÿ1••ÿ-™™ÿ+  ÿ/¬¬ÿ3¹¹ü(ÉÉÊ ððþþuþþuþþmÿÿ`þþQþþ>þþ-þþÿÿþþ ÿÿÿÿÿÿÿÿBŽŽ¡W››üUššÿQ˜˜ÿM––ÿI””ÿC““ÿ>‘‘ÿ?‘‘ÿL™™ÿK˜˜ÿH——ÿF––ÿC••ÿG——ÿH——ÿG——ÿF––ÿF——ÿE––ÿE––ÿE––ÿE——ÿE——ÿE——ÿE——ÿF˜˜ÿG˜˜ÿG™™ÿH™™ÿH™™ÿIššÿI™™ÿJššÿJššÿJ››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿK››ÿL››ÿK››ÿKššÿJššÿJ™™ÿI™™ÿH™™ÿH˜˜ÿH˜˜ÿF——ÿE——ÿD––ÿC––ÿA••ÿ@””ÿ?’’ÿ=‘‘ÿ<ÿ;ÿ8ÿ7ÿ5‘‘ÿ2””ÿ/˜˜ÿ.  ÿ2¬¬ÿ5¸¸ü+ÉÈÉ ðð|þþrþþqþþkþþ_þþOÿÿ<þþ,ÿÿþþÿÿ ÿÿÿÿÿÿ0ƒƒDQ——áXœœÿUššÿP˜˜ÿM––ÿG””ÿC““ÿ?ÿJ——ÿNššÿL™™ÿI——ÿG––ÿK˜˜ÿJ™™ÿI˜˜ÿH——ÿG˜˜ÿG——ÿG˜˜ÿG˜˜ÿG——ÿF——ÿF˜˜ÿG˜˜ÿG˜˜ÿH™™ÿH™™ÿH™™ÿIššÿJššÿJššÿKššÿL››ÿL››ÿK››ÿL››ÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿM››ÿL››ÿL››ÿKššÿJ™™ÿH™™ÿH™™ÿG˜˜ÿE——ÿE––ÿC––ÿB••ÿA””ÿ@““ÿ>‘‘ÿ=ÿ<ÿ:ÿ9ÿ8ÿ7‘‘ÿ4””ÿ2˜˜ÿ1  ÿ5««ÿ8··ü,ÇÆÇ ððxþþnþþnþþiþþ]þþMþþ;þþ,þþÿÿÿÿ ÿÿÿÿÿÿ @ŽŽŠX››ùWœœÿS™™ÿO——ÿK––ÿG””ÿB’’ÿD””ÿPššÿOššÿK™™ÿJ˜˜ÿNššÿMššÿL™™ÿK™™ÿI˜˜ÿI˜˜ÿI˜˜ÿH˜˜ÿH˜˜ÿH˜˜ÿH™™ÿH™™ÿH™™ÿH™™ÿH™™ÿIššÿJššÿJššÿKššÿK››ÿLœœÿLœœÿLœœÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿM››ÿL››ÿKššÿJššÿI™™ÿI™™ÿH™™ÿF——ÿE——ÿD––ÿC––ÿB••ÿA““ÿ@’’ÿ>‘‘ÿ=ÿ<ÿ;ÿ:ÿ9ÿ8’’ÿ7””ÿ4™™ÿ3ŸŸÿ7ªªÿ;¶¶ü0ÅÅÇ ïïuþþiþþjþþdþþYþþJþþ9þþ)þþÿÿþþ ÿÿÿÿÿÿ+‚‚)L””ÇYœœþV››ÿR™™ÿN——ÿJ——ÿF””ÿB““ÿM˜˜ÿR››ÿOššÿM™™ÿP››ÿO››ÿN››ÿMššÿL™™ÿLššÿK™™ÿJ™™ÿI™™ÿJ™™ÿJššÿJ™™ÿI™™ÿJ™™ÿJššÿKššÿKššÿK››ÿL››ÿL››ÿLœœÿMœœÿL››ÿLœœÿLœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿMœœÿL››ÿK››ÿJššÿJ™™ÿI™™ÿH˜˜ÿG——ÿF——ÿD––ÿD––ÿB••ÿB””ÿA““ÿ@’’ÿ?’’ÿ>‘‘ÿ=ÿ=ÿ<ÿ;‘‘ÿ;’’ÿ9••ÿ7™™ÿ7  ÿ;©©ÿ>´´ü3ÃÃÅ ììpþþeþþeþþ_þþTþþFþþ6þþ&þþÿÿÿÿÿÿÿÿÿÿ9‰‰]UššëYÿV››ÿR™™ÿN˜˜ÿJ——ÿG••ÿG••ÿSœœÿRœœÿPššÿSœœÿSÿQœœÿP››ÿO››ÿN››ÿM››ÿLššÿLššÿLššÿKššÿKššÿKššÿLššÿL››ÿL››ÿL››ÿL››ÿL››ÿMœœÿMœœÿMœœÿMœœÿNÿMœœÿNœœÿNÿNÿNÿNÿNÿNÿNœœÿMœœÿL››ÿKššÿJššÿJ™™ÿI™™ÿH™™ÿG˜˜ÿF——ÿF——ÿE––ÿC••ÿC””ÿB””ÿA““ÿ@’’ÿ?‘‘ÿ?‘‘ÿ?‘‘ÿ>‘‘ÿ=’’ÿ>””ÿ<––ÿ:™™ÿ;ŸŸÿ>©©ÿD´´û7ÃÃÁëëkþþ_ÿÿ`þþ[ÿÿPþþAþþ2þþ#þþþþ þþÿÿÿÿÿÿooB’YøYÿV››ÿR™™ÿN˜˜ÿK——ÿG••ÿN™™ÿVÿSœœÿUžžÿVŸŸÿTžžÿTÿRœœÿQœœÿPœœÿO››ÿOœœÿOœœÿN››ÿM››ÿL››ÿM››ÿNœœÿNœœÿMœœÿNœœÿNœœÿOÿOÿNÿNÿOÿOÿOÿOÿOÿOÿOÿOÿOÿOÿNœœÿNœœÿM››ÿM››ÿKššÿJššÿJššÿH™™ÿH˜˜ÿG˜˜ÿG——ÿF——ÿE––ÿD••ÿC””ÿC””ÿA““ÿA’’ÿA’’ÿA’’ÿA““ÿ@““ÿ@””ÿ@——ÿ>™™ÿ?ŸŸÿD©©ÿH´´û:¿ëëfþþYþþZÿÿUþþJþþ=þþ-þþÿÿÿÿ ÿÿÿÿÿÿÿÿ&(M••ÁZžžýYÿV››ÿR™™ÿO™™ÿK——ÿI––ÿTœœÿWžžÿWžžÿY  ÿX  ÿVŸŸÿUžžÿVŸŸÿZ¢¢ÿY¡¡ÿSžžÿPœœÿPœœÿOœœÿOœœÿOœœÿOœœÿOÿOÿOÿPÿPÿQžžÿPžžÿPžžÿPÿPžžÿPžžÿPžžÿPžžÿPÿPÿPÿPÿOÿOÿOœœÿNœœÿM››ÿL››ÿLššÿKššÿJššÿJ™™ÿI˜˜ÿH˜˜ÿH˜˜ÿG——ÿF——ÿF––ÿE••ÿD••ÿD””ÿD””ÿE””ÿD””ÿD””ÿE••ÿE––ÿE——ÿD››ÿF¡¡ÿIªªÿM´´û>ÀÀ¼ìì^þþRþþSþþNÿÿDþþ7þþ)þþþþÿÿ ÿÿÿÿÿÿÿÿUU0……ES™™Ù\žžÿYÿV››ÿSššÿO™™ÿK——ÿL˜˜ÿWžžÿYŸŸÿ\¡¡ÿ\¢¢ÿZ¡¡ÿ]¢¢ÿq­­ÿy²²ÿy³³ÿk««ÿV  ÿRÿRÿRÿQÿRÿQÿQžžÿQÿQÿQžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿQžžÿQžžÿQžžÿQžžÿQÿPÿPÿOÿOœœÿNœœÿNœœÿM››ÿL››ÿKššÿK™™ÿI™™ÿJ™™ÿI˜˜ÿH˜˜ÿH——ÿG——ÿH——ÿG––ÿG––ÿG––ÿH––ÿH––ÿI––ÿI––ÿK˜˜ÿKššÿJÿK¢¢ÿNªªÿO²²ûB½½¸ççXþþKþþKþþFþþ=þþ1þþ$ÿÿÿÿÿÿÿÿÿÿÿÿ9ˆˆaV››ä\žžÿYÿVœœÿS››ÿPššÿL˜˜ÿPššÿZ  ÿ]¡¡ÿ_¢¢ÿ]¢¢ÿkªªÿ}´´ÿ~´´ÿ~µµÿ}´´ÿm¬¬ÿX  ÿUŸŸÿTŸŸÿSžžÿSžžÿSžžÿSžžÿSŸŸÿSŸŸÿSŸŸÿSŸŸÿTŸŸÿSŸŸÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿSŸŸÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿRžžÿQÿPÿOœœÿNœœÿNœœÿM››ÿMššÿLššÿLššÿKššÿKššÿJ™™ÿJ˜˜ÿJ˜˜ÿJ˜˜ÿJ——ÿK˜˜ÿL˜˜ÿL˜˜ÿM——ÿN——ÿO˜˜ÿOššÿPœœÿPŸŸÿN¢¢ÿP©©ÿS±±úF¼¼µèèPþþAþþAþþ=þþ5þþ)ÿÿÿÿÿÿ þþÿÿÿÿÿÿss 9‰‰oWœœè\ŸŸÿZžžÿVÿS››ÿPššÿM˜˜ÿTÿ]¢¢ÿb¤¤ÿa¤¤ÿp­­ÿµµÿµµÿ¶¶ÿ~¶¶ÿ|´´ÿc¦¦ÿX  ÿW  ÿV  ÿV  ÿUŸŸÿUŸŸÿU  ÿU  ÿUŸŸÿUŸŸÿU  ÿV  ÿV  ÿV  ÿU  ÿU  ÿT  ÿT  ÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSŸŸÿSŸŸÿSžžÿSžžÿRžžÿQžžÿQÿPœœÿPÿOœœÿOœœÿNœœÿN››ÿNœœÿMššÿLššÿLššÿMššÿMššÿN™™ÿOššÿO™™ÿQ™™ÿQ™™ÿSššÿT››ÿUÿUžžÿPÿO  ÿR§§ÿX°°úO¹¹±ääDþþ6þþ7ÿÿ3þþ+ÿÿ"ÿÿÿÿþþ ÿÿÿÿÿÿmm;‰‰wY››ì]ŸŸÿZžžÿWÿTœœÿQššÿOššÿXŸŸÿb¤¤ÿd¥¥ÿlªªÿµµÿ··ÿ€¶¶ÿ¶¶ÿ}µµÿg¨¨ÿ[¢¢ÿ[¡¡ÿZ¢¢ÿY¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿX¡¡ÿW  ÿW¡¡ÿW¡¡ÿX¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿW¡¡ÿV¡¡ÿV¡¡ÿW  ÿV  ÿV  ÿV  ÿV  ÿU  ÿTŸŸÿTŸŸÿTŸŸÿTŸŸÿSžžÿRžžÿRžžÿRÿQÿPÿPœœÿOœœÿPœœÿOœœÿO››ÿPœœÿPœœÿQ››ÿR››ÿR››ÿSššÿU››ÿVœœÿXÿYžžÿVœœÿS››ÿPœœÿR  ÿX§§ÿW¬¬á'¸¸Zøø)þþ+þþ+þþ'þþ!þþþþþþ þþÿÿÿÿÿÿoo>‹‹~Zí^ŸŸÿ[žžÿXÿUœœÿR››ÿQ››ÿ[  ÿe¦¦ÿg§§ÿt¯¯ÿ€¶¶ÿ··ÿ€¶¶ÿx²²ÿc¥¥ÿ^££ÿ]££ÿ]££ÿ]££ÿ\££ÿ[¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿY¡¡ÿZ¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿX¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿW¡¡ÿW  ÿW  ÿV  ÿV  ÿUŸŸÿTŸŸÿTŸŸÿSŸŸÿTžžÿSžžÿSžžÿSžžÿSžžÿSžžÿTžžÿTžžÿUÿUÿVÿWÿXÿZžžÿ[ŸŸÿZžžÿU››ÿSššÿS››ÿUžžÿOœœé1””|ÇÇ ÿÿþþþþþþþþþþÿÿÿÿ þþÿÿÿÿÿÿÿÿxx>ŠŠZœœì_  ÿ\ŸŸÿYžžÿVÿSœœÿSœœÿ^¢¢ÿg¨¨ÿh¨¨ÿo¬¬ÿs®®ÿq­­ÿg¨¨ÿb¥¥ÿa¥¥ÿa¤¤ÿ`¥¥ÿ_¤¤ÿ_¤¤ÿ_¤¤ÿ^¤¤ÿ]¤¤ÿ\££ÿ\££ÿ\££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[££ÿ[¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¢¢ÿY¡¡ÿX¡¡ÿX¡¡ÿW¡¡ÿW  ÿV  ÿW  ÿV  ÿW  ÿV  ÿW  ÿW  ÿW  ÿW  ÿWŸŸÿYŸŸÿZŸŸÿ[  ÿ[ŸŸÿ]  ÿ]  ÿXÿTššÿUššÿW››þP˜˜å0ˆˆx þþ ÿÿ ÿÿþþþþþþÿÿþþ ÿÿ þþÿÿÿÿÿÿxx;‰‰wY››ç`  ÿ]¡¡ÿZŸŸÿWžžÿUÿUœœÿ`££ÿj©©ÿj©©ÿi¨¨ÿh¨¨ÿg§§ÿf§§ÿe§§ÿd¦¦ÿc¦¦ÿb¦¦ÿb¥¥ÿb¦¦ÿa¥¥ÿa¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ_¤¤ÿ^¤¤ÿ]¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ]¤¤ÿ\¤¤ÿ\££ÿ\££ÿ\££ÿ[££ÿ[££ÿ[¢¢ÿZ¢¢ÿY¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿY¢¢ÿZ¢¢ÿZ¢¢ÿZ¢¢ÿ[¢¢ÿ[¢¢ÿ[¡¡ÿ]¡¡ÿ]¡¡ÿ^¡¡ÿ^¡¡ÿ_¡¡ÿZžžÿU››ÿV››ÿW››þN––Þ-ƒƒkooÿÿÿÿÿÿÿÿÿÿþþ ÿÿ ÿÿ ÿÿþþÿÿÿÿÿÿÿÿuu 9‰‰fWššÝ`¡¡þ_¡¡ÿ\  ÿYŸŸÿVÿVÿ`££ÿlªªÿl««ÿkªªÿj©©ÿi¨¨ÿh¨¨ÿg¨¨ÿf¨¨ÿf§§ÿe§§ÿd§§ÿd§§ÿc§§ÿc¦¦ÿc¦¦ÿb¦¦ÿb¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¦¦ÿ`¥¥ÿ_¥¥ÿ_¥¥ÿ_¥¥ÿ^¥¥ÿ^¥¥ÿ^¤¤ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ]££ÿ]££ÿ]££ÿ]¤¤ÿ]¤¤ÿ]££ÿ]££ÿ]££ÿ^££ÿ_¢¢ÿ_¢¢ÿ`££ÿ`££ÿa££ÿ[ŸŸÿV››ÿWœœÿW››üJ””Í*€€Yss ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ:‰‰NS——Æ^  ú`¡¡ÿ]  ÿZŸŸÿXžžÿVžžÿ`££ÿlªªÿn««ÿm««ÿlªªÿkªªÿkªªÿj©©ÿi©©ÿh©©ÿg¨¨ÿg¨¨ÿf¨¨ÿf¨¨ÿf¨¨ÿe¨¨ÿe¨¨ÿd§§ÿd§§ÿd§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿb§§ÿb§§ÿb§§ÿb¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿ`¦¦ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¥¥ÿ`¤¤ÿa¤¤ÿa££ÿa¤¤ÿc¥¥ÿc¤¤ÿ\  ÿVœœÿXœœÿV››÷F‘‘¶%{{>ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUU-||-M””ž\žžï`¢¢ÿ_¢¢ÿ\  ÿYŸŸÿXžžÿ]¢¢ÿk©©ÿp¬¬ÿp¬¬ÿo¬¬ÿn««ÿm««ÿl««ÿkªªÿjªªÿjªªÿiªªÿiªªÿiªªÿiªªÿh©©ÿg©©ÿg©©ÿg©©ÿg©©ÿf©©ÿf©©ÿf©©ÿf¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿe¨¨ÿd¨¨ÿd§§ÿd§§ÿd§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc§§ÿc¦¦ÿc¥¥ÿd¥¥ÿd¥¥ÿd¥¥ÿc¤¤ÿ[  ÿWÿXþQ™™ç:ŠŠ‘yy(&>ŒŒfU™™Ñ_  û`¢¢ÿ^¡¡ÿ\  ÿZŸŸÿ[  ÿi¨¨ÿr­­ÿq­­ÿq­­ÿp¬¬ÿo¬¬ÿo¬¬ÿn¬¬ÿm««ÿl««ÿl««ÿl««ÿk««ÿk««ÿk««ÿj««ÿj««ÿjªªÿiªªÿiªªÿhªªÿhªªÿhªªÿh©©ÿhªªÿh©©ÿh©©ÿg©©ÿg©©ÿf©©ÿg©©ÿf©©ÿf©©ÿf©©ÿf¨¨ÿf©©ÿf©©ÿf¨¨ÿf¨¨ÿf§§ÿf§§ÿf¦¦ÿf§§ÿf§§ÿa££ÿYŸŸÿXžžÿWœœùK””Ê-ƒƒ_qqff2‚‚3I““˜[è`¡¡þ`¢¢ÿ]¡¡ÿ[  ÿ\  ÿe¦¦ÿp­­ÿs®®ÿs®®ÿr®®ÿr­­ÿq­­ÿp­­ÿp­­ÿo­­ÿo¬¬ÿn¬¬ÿn¬¬ÿm¬¬ÿn­­ÿm¬¬ÿm¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿk¬¬ÿk««ÿk««ÿj««ÿj««ÿj««ÿj««ÿj««ÿi««ÿi««ÿi««ÿiªªÿiªªÿh©©ÿh©©ÿh©©ÿi©©ÿi¨¨ÿh¨¨ÿh¨¨ÿi¨¨ÿf§§ÿ^¢¢ÿYžžÿXžžýP™™â;’}}3UU"ww:‡‡OO––°]  ð`¢¢þ_¢¢ÿ^¡¡ÿ]¡¡ÿ`££ÿj©©ÿr®®ÿu¯¯ÿt¯¯ÿs®®ÿt¯¯ÿs¯¯ÿr®®ÿr®®ÿr®®ÿq®®ÿp®®ÿp®®ÿp­­ÿp®®ÿo®®ÿo­­ÿn­­ÿn­­ÿn­­ÿn­­ÿm¬¬ÿm¬¬ÿm¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿk¬¬ÿl¬¬ÿl¬¬ÿl¬¬ÿk««ÿkªªÿkªªÿkªªÿkªªÿi©©ÿb¥¥ÿ[  ÿXžžþSœœðG””³)‚‚Pww!yyA^Q™™µ[ŸŸì`¢¢þ_¢¢ÿ^¢¢ÿ]¡¡ÿa¤¤ÿk©©ÿr®®ÿu°°ÿv°°ÿu°°ÿu¯¯ÿt¯¯ÿs¯¯ÿt¯¯ÿs¯¯ÿs¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿq¯¯ÿq®®ÿp®®ÿp®®ÿp®®ÿo­­ÿo­­ÿo­­ÿo­­ÿo­­ÿn­­ÿn­­ÿn¬¬ÿn¬¬ÿn¬¬ÿm««ÿm««ÿk©©ÿd¥¥ÿ]¡¡ÿXŸŸþTœœñH””½3ˆˆc{{UU!yy:ŒŒ[R˜˜À]  ó`¢¢þ`££ÿ^¢¢ÿ^¢¢ÿ`££ÿg§§ÿo¬¬ÿu°°ÿw°°ÿw°°ÿv°°ÿv°°ÿu°°ÿu°°ÿu°°ÿu°°ÿt°°ÿt°°ÿt°°ÿt°°ÿs¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿr¯¯ÿq¯¯ÿq®®ÿq®®ÿp®®ÿp­­ÿp­­ÿp­­ÿp­­ÿn««ÿj©©ÿd¥¥ÿ]¡¡ÿYžžüRššèF””¸3‰‰hww UU' ?aK””¨VœœÜ]¡¡ö`¢¢þ`££ÿ_¢¢ÿ_¢¢ÿc¥¥ÿlªªÿr­­ÿu°°ÿx±±ÿx±±ÿx±±ÿx±±ÿx±±ÿv±±ÿu°°ÿu±±ÿu±±ÿu°°ÿt°°ÿt°°ÿs¯¯ÿs°°ÿt°°ÿt¯¯ÿs¯¯ÿs¯¯ÿr®®ÿo¬¬ÿlªªÿf§§ÿ_££ÿ[  þVôO˜˜Û@ŽŽ-„„Ozzww-„„2?lK••¦T››Ó]  ô`¢¢þ_££ÿ_¢¢ÿ_££ÿa¤¤ÿe¦¦ÿi¨¨ÿo¬¬ÿt¯¯ÿu°°ÿv±±ÿv°°ÿv±±ÿw±±ÿw±±ÿw±±ÿu°°ÿt°°ÿt¯¯ÿq®®ÿn¬¬ÿkªªÿg§§ÿb¥¥ÿ^¢¢ÿZ  ýWžžóP™™ÞE““­4‹‹j(‚‚3wwUUuu %‚‚)@jO˜˜©QššÈXä[  ð\  ù^¡¡þ^¢¢ÿ_¢¢ÿ`££ÿ`¤¤ÿa¤¤ÿb¤¤ÿc¥¥ÿe¦¦ÿd¦¦ÿc¦¦ÿb¥¥ÿb¥¥ÿ`££ÿ^¢¢ÿ[ŸŸþYžžùUœœïS››ãK——ÅB‘‘•5ŠŠ`%7 xxUUUUuu #{{2††78ŠŠQ@rI••¢M——¼S››ÍRššÕS››ÚVèXžžñZŸŸóYžžñXžžîWœœçTœœâO™™ÖN——ÆI””¹CŸ8‹‹y/‡‡Q(9{{ff UUjj yy*"||%",2‡‡B=SB‘‘TDNDG/……A.7 ||' xxss ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÀ?ÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿøÿÿÿÿÿÿÿðÿÿÿÿÿÿø?ÿÿÿÿÿÿüÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿàÿÿÿÿÿÿÿðÿÿÿÿÿÿÿøÿÿÿÿÿÿðÿÿÿÿÿÿð?ÿÿÿÿÿÿøÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿàÿÿÿÿÿÿÿðÿÿÿÿÿÿÿàÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿøÿÿÿÿÿÿðÿÿÿÿÿÿðÿÿÿÿÿÿàÿÿÿÿÿÿàÿÿÿÿÿÿÀÿÿÿÿÿÀÿÿÿÿÿ€ÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿþÿÿÿÿÿþÿÿÿÿÿþÿÿÿÿÿüÿÿÿÿÿüÿÿÿÿÿüÿÿÿÿÿüÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿø?ÿÿÿÿÿø?ÿÿÿÿÿøÿÿÿÿÿøÿÿÿÿÿÿøÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÀÿÿÿÿÿÿàÿÿÿÿÿÿà?ÿÿÿÿÿÿðÿÿÿÿÿÿðÿÿÿÿÿÿøÿÿÿÿÿÿüÿÿÿÿÿÿüÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿ€?ÿÿÿÿÿÿÀÿÿÿÿÿÿàÿÿÿÿÿÿð?ÿÿÿÿÿÿøÿÿÿÿÿÿüÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿð?ÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿð?ÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(0` $ÿÿÿÿÿÿÿÿÿÿ  "$&'(((((('&$#  ÿÿÿÿÿÿÿÿ )/379;=?B GH GDBAAA@@?>=;:740*!!(-1B11aCC…MM§RR½UUÊUUÎTTÇQQ¼JJ >>‚((_E<;:98641-)"33 LL2VVZZÊ]]î^^ü^^ÿ^^ÿ^^ÿ^^ÿ]]ÿ^^ÿ__ÿ__û^^éZZÀPP|554 ^^#aa…``Þ``ü__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ``ÿaaÿ bbÿ ccú ccÎbbj \\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ__``W bbÐbbüaaÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ``ÿaaÿ bbÿ ddÿeeøee¼eeAÿÿÿÿÿÿ þþþþþþþþ ¦¦ ii… ddï ccÿ``ÿ``ÿ__ÿ^^ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ``ÿaaÿ bbÿ ddÿggÿiiäggjqq ÿÿÿÿÿÿ þþþþ&þþ5þþ:½½^€€ç kkÿccÿ__ÿ^^ÿ]]ÿ]]ÿ]]ÿ]]ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ``ÿbbÿ eeÿhhÿjjñii{ff ÿÿÿÿÿÿ þþþþ*ÿÿDùùW»»”††ðllÿaaÿ]]ÿ\\ÿ\\ÿ]]ÿ]]ÿ^^ÿ^^ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿaaÿ ccÿeeÿjjÿmmòmmrUUÿÿÿÿÿÿÿÿþþ)þþKüüfÄÄž‹‹ñllÿ``ÿ]]ÿ\\ÿ]]ÿ__ÿaaÿbbÿccÿccÿbbÿbbÿaaÿ``ÿ__ÿ^^ÿ^^ÿ__ÿ__ÿaaÿ ccÿggÿmmÿ"qqëooRÿÿÿÿþþ+þþTüürË˧ ••òwwÿllÿkkÿ kkÿ kkÿ jjÿ hhÿ ffÿ ddÿccÿccÿbbÿbbÿaaÿaaÿ``ÿ^^ÿ__ÿ``ÿbbÿ eeÿkkÿ"rrÿ(uuÐ"nn%ÿÿÿÿÿÿ0þþ\úú~ ÓÓ¯  ô ……ÿ wwÿ rrÿ qqÿ rrÿ rrÿ qqÿ ooÿ llÿ hhÿddÿccÿbbÿaaÿaaÿ``ÿ__ÿ__ÿaaÿ ddÿiiÿppÿ*wwý,yy*ÿÿþþþþ5üüeûûˆ ÙÙ·ªªõ ‹‹ÿzzÿttÿssÿssÿ ssÿ ttÿ ssÿ rrÿ ooÿjjÿeeÿbbÿaaÿaaÿ``ÿ``ÿ``ÿbbÿggÿnnÿ(wwÿ3~~ä,zz4ÿÿÿÿþþ7þþiýû ÞÞ¾²²ö““ÿÿxxÿvvÿvvÿvvÿ uuÿ ttÿ ssÿ rrÿ ppÿkkÿeeÿbbÿaaÿaaÿaaÿbbÿ ffÿllÿ%uuÿ5þ7˜*ÿÿÿÿþþþþ6üükûû˜ ããźº÷ššÿ„„ÿzzÿxxÿxxÿwwÿwwÿ vvÿ ttÿ ssÿ qqÿooÿhhÿbbÿaaÿaaÿccÿ eeÿkkÿ"ttÿ3~~ÿ>……Ý1$ÿÿÿÿãã@ ÅŲèè½ ççËÀ¿øžžÿ††ÿ{{ÿxxÿxxÿxxÿwwÿwwÿ vvÿ ssÿ qqÿqqÿllÿddÿaaÿccÿ eeÿjjÿ rrÿ0}}ÿ?††ø9ƒƒY‹‹ ttˆˆˆ÷µ´ðêèÅ êéÑÃÃù¢¢ÿ‰‰ÿ}}ÿyyÿxxÿxxÿwwÿwwÿ vvÿ ttÿ rrÿqqÿnnÿggÿccÿ ffÿjjÿqqÿ-{{ÿ?‡‡ÿ@††3ffee_hhðooÿ ‰‰ÿ ¹¹òììÌëêÖÇÆù¦¥ÿÿÿzzÿyyÿxxÿwwÿwwÿ vvÿ ttÿqqÿppÿooÿjjÿ ffÿjjÿppÿ,zzÿ=……ÿC‰‰Å/ii.iiÙhhÿ hhÿttÿ““ÿ ÃÃôïíÒììÚÊÉú««ÿ’’ÿƒƒÿ||ÿyyÿxxÿwwÿ wwÿ vvÿ ssÿqqÿppÿppÿ mmÿkkÿppÿ+zzÿ<……ÿDŠŠß2ƒƒ#qq nnppþooÿ mmÿooÿ}}ÿžžÿÊÉõñðÖîíÝÑÐú³³ÿ›šÿ‰‰ÿÿzzÿxxÿwwÿ vvÿ uuÿ rrÿppÿqqÿ ssÿqqÿqqÿ*yyÿ;„„ÿEŠŠë9‚‚1uuCwwìuuÿ qqÿmmÿllÿqqÿƒƒÿ§¦ÿ ÐÏöòòÚðïá ÖÕû¼¼ÿ££ÿÿ‚‚ÿ{{ÿxxÿvvÿ uuÿ ssÿ qqÿ rrÿ ttÿwwÿuuÿ+zzÿ;„„ÿE‹‹ð8;mmzz zzþuuÿ qqÿnnÿmmÿmmÿ xxÿ ÿ ­­ÿ ÓÓ÷óòÞòñå ÜÛûÃÂÿ©©ÿ““ÿ„„ÿ{{ÿwwÿuuÿ ttÿ rrÿ rrÿttÿxxÿ||ÿ,}}ÿ;……ÿF‹‹ð9ƒƒ:yy*||ázzÿuuÿ rrÿooÿnnÿ qqÿvvÿ}}ÿ““ÿ µ´ÿ ØØøóóãòòé àßüÈÇÿ­­ÿ””ÿ„„ÿzzÿvvÿ uuÿ ssÿ rrÿuuÿyyÿ"~~ÿ.‚‚ÿ<††ÿF‹‹ì:„„4~~e"~~ûzzÿvvÿ rrÿooÿ qqÿvvÿvvÿzzÿ……ÿœœÿ ¾¾ÿ ßÞùööæõôë âáüÊÊÿ¯¯ÿ••ÿ‚‚ÿyyÿvvÿssÿrrÿuuÿzzÿ%€€ÿ0††ÿ?ŠŠÿFŠŠà1$ff& %ÿ{{ÿwwÿ ssÿqqÿvvÿyyÿzzÿ||ÿÿÿ¦¦ÿ ÆÅÿ ãâùööèõôë âáüÊÊÿ¯®ÿ””ÿÿxxÿuuÿssÿwwÿ{{ÿ)‚‚ÿ4‡‡ÿBÿEŒŒÊ(xxxx,„„È'‚‚ÿ}}ÿxxÿ ssÿuuÿzzÿ||ÿ}}ÿ}}ÿÿ……ÿ••ÿ®®ÿ ÊÉÿ ãâú÷öèõôë áàüÈÇÿªªÿÿ}}ÿvvÿuuÿxxÿ#}}ÿ-„„ÿ8ŠŠÿE‘‘ÿC¢3ff$ƒƒ#1ˆˆß(„„ÿ~~ÿxxÿ uuÿzzÿ}}ÿ~~ÿÿÿ€€ÿ‚‚ÿŠŠÿššÿ±±ÿ ÌËÿ äãúöõèõôêßÞûÁÁÿ¡¡ÿˆˆÿzzÿwwÿzzÿ(€€ÿ2††ÿ=ŒŒÿH’’ú?ŒŒd-„„25‰‰ë+……ÿÿyyÿxxÿ~~ÿÿ€€ÿÿÿÿ‚‚ÿ……ÿÿœœÿ²²ÿËÊÿ ããùöõæóòçÚÙû¸¸ÿ——ÿ!„„ÿzzÿ%}}ÿ.ƒƒÿ9‰‰ÿCÿH’’à7ˆˆ)-„„88‹‹ï-‡‡ÿ €€ÿzzÿ||ÿÿÿÿ‚‚ÿ ‚‚ÿ ƒƒÿ!ƒƒÿ"„„ÿ!‡‡ÿÿÿ±±ÿÉÈÿ ààùõôáññâÓÒú¯¯ÿ%‘‘ÿ'ÿ,ÿ6‡‡ÿ@ÿI’’þE$mm5††9;ð1ˆˆÿ#ÿ{{ÿ€€ÿ!ƒƒÿ ƒƒÿ ƒƒÿ!ƒƒÿ"„„ÿ#……ÿ$……ÿ%……ÿ%††ÿ$‰‰ÿ"ÿœœÿ­­ÿÃÃÿÜÛ÷ôôÚ îîÜËËù$¨¨ÿ-ŽŽÿ4‡‡ÿ?ŒŒÿG‘‘ÿL””ê;‹‹@1ŠŠ.>é5ŠŠÿ'ƒƒÿ}}ÿ$„„ÿ$……ÿ"„„ÿ"„„ÿ#……ÿ%††ÿ&‡‡ÿ&‡‡ÿ'‡‡ÿ(‡‡ÿ+‰‰ÿ-ÿ.““ÿ,œœÿ&ªªÿ¿¾ÿØ×õòòÒ ìëÔ ÆÆ÷/££ÿ<’’ÿG‘‘ÿN””ýH˜qq /‡‡ @‘‘Ý9ÿ,……ÿ!ÿ(††ÿ(‡‡ÿ&††ÿ%††ÿ&††ÿ'‡‡ÿ'ˆˆÿ*‰‰ÿ/‹‹ÿ4ÿ7ÿ9ÿ9‘‘ÿ7““ÿ4˜˜ÿ.¤¤ÿ$¸·ÿÓÓóññÊééË+ÀÀö>££ÿL˜˜ÿN””Ó<……*6C‘‘Á?ÿ1ˆˆÿ%‚‚ÿ,ˆˆÿ,‰‰ÿ)‡‡ÿ(‡‡ÿ(‡‡ÿ)‰‰ÿ-‹‹ÿ4ŽŽÿ8ÿ:‘‘ÿ;’’ÿ<’’ÿ=’’ÿ=’’ÿ<““ÿ9––ÿ2ŸŸÿ(³²ÿÏÎò ïïÁååÁ8¼¼ôI££íA’’Y¿¿¿3™™C‘‘˜D’’ÿ7ŒŒÿ+……ÿ0‹‹ÿ0‹‹ÿ-‰‰ÿ+ˆˆÿ+‰‰ÿ/ŒŒÿ6ÿ9‘‘ÿ;’’ÿ=““ÿ>””ÿ?””ÿ@””ÿ@””ÿ@””ÿ?““ÿ<••ÿ5ÿ*®®ÿ ÉÉð ìì¶ââ³(ÇÇ’ääÿÿ¿¿Ÿ¿¿AŽŽVH””÷>ÿ1‰‰ÿ4ŒŒÿ6ŽŽÿ1ŒŒÿ.ŠŠÿ0‹‹ÿ6ÿ9‘‘ÿ;’’ÿ=““ÿ?••ÿA––ÿC––ÿC––ÿC––ÿC––ÿC••ÿ@””ÿ=””ÿ6ššÿ,ªªÿ#ÅÅíêêªüüuþþ?þþÿÿ=‹‹!K••ØF““ÿ9ŒŒÿ7ÿ<‘‘ÿ6ÿ3ÿ7ÿ:‘‘ÿ<’’ÿ>””ÿ@••ÿB––ÿD——ÿE˜˜ÿF˜˜ÿF˜˜ÿF——ÿE——ÿD––ÿA””ÿ=””ÿ6˜˜ÿ-¥¥ÿ%ÀÀëèç þümþþ>þþÿÿÿÿ3ffJ’’M––ýBÿ:ÿA““ÿ=‘‘ÿ:ÿ=’’ÿ>““ÿ?””ÿA••ÿC––ÿD——ÿF˜˜ÿG™™ÿH™™ÿI™™ÿH™™ÿH™™ÿF——ÿD––ÿ@””ÿ<’’ÿ6••ÿ-  ÿ(ººéæäšþþjþþAþþÿÿÿÿA/P——àK••ÿ@‘‘ÿE••ÿC””ÿB””ÿC••ÿB••ÿB––ÿC––ÿE——ÿG˜˜ÿH™™ÿIššÿJ››ÿJššÿJššÿI™™ÿH˜˜ÿE——ÿC••ÿ?““ÿ;‘‘ÿ5’’ÿ.œÿ+·¶çáá•þþhþþCþþÿÿÿÿ3ffL••…S™™ûI••ÿF••ÿK˜˜ÿJ˜˜ÿI˜˜ÿG——ÿG˜˜ÿG˜˜ÿH™™ÿI™™ÿJššÿK››ÿLœœÿLœœÿMœœÿL››ÿJššÿG˜˜ÿE––ÿB””ÿ>’’ÿ;ÿ7‘‘ÿ3œ›ÿ1³³æààŽþþbþþ?þþþþÿÿ? TššÈR™™ÿI––ÿOššÿQœœÿP››ÿMššÿLššÿKššÿKššÿL››ÿM››ÿMœœÿNœœÿNÿNÿNœœÿL››ÿJ™™ÿG˜˜ÿE––ÿB””ÿ@’’ÿ>‘‘ÿ=““ÿ;œœÿ;±±äÞÞ„þþVþþ6þþÿÿÿÿK””JV››æRššÿN™™ÿWžžÿZ  ÿc¦¦ÿ`¥¥ÿRžžÿPÿPÿPÿQžžÿQžžÿQžžÿQžžÿPÿOœœÿM››ÿKššÿI™™ÿH——ÿF––ÿE••ÿF••ÿG——ÿHžžÿG±±à&ØØwþþEþþ)ÿÿÿÿ3™™M––fXœœíS››ÿUÿb¤¤ÿy²²ÿ{´´ÿ`¥¥ÿW  ÿV  ÿU  ÿV  ÿV  ÿU  ÿU  ÿTŸŸÿSŸŸÿRžžÿPÿOœœÿN››ÿMššÿMššÿOššÿRššÿTœœÿQ ŸÿO­­Õ!ÚÚLþþ,þþÿÿÿÿ$‘‘P——lYìWÿ\¡¡ÿn««ÿs®®ÿd¦¦ÿ_¤¤ÿ^¤¤ÿ\££ÿ\££ÿ[££ÿ[££ÿ[££ÿZ¢¢ÿY¢¢ÿX¡¡ÿW¡¡ÿV  ÿV  ÿVŸŸÿVŸŸÿXŸŸÿ[ŸŸÿYÿS››õHššÈÈþþÿÿÿÿÿÿ$‘‘P––_ZžžÞZŸŸþ_££ÿj©©ÿj©©ÿh¨¨ÿf¨¨ÿe§§ÿd§§ÿc¦¦ÿb¦¦ÿa¦¦ÿa¦¦ÿ`¦¦ÿ`¥¥ÿ_¥¥ÿ^¥¥ÿ^¤¤ÿ^¤¤ÿ_££ÿ`££ÿ]  ÿTššêI””z3ˆˆÿÿÿÿÿÿÿÿÿÿUªªM––8X°[  õa££ÿlªªÿp¬¬ÿn¬¬ÿm««ÿl««ÿk««ÿj««ÿiªªÿhªªÿhªªÿg©©ÿg©©ÿf©©ÿf¨¨ÿf§§ÿe¦¦ÿ]¡¡úRššÆC‘‘O$mmK––Tšš^YŸŸÂ^¢¢÷h¨¨ÿp¬¬ÿs¯¯ÿs¯¯ÿr¯¯ÿr¯¯ÿq®®ÿp®®ÿo­­ÿn­­ÿn¬¬ÿm¬¬ÿj©©ÿb¤¤úXŸŸÕK––z=ŒŒÿÿE––Qšš[X¥]¡¡Ûd¥¥öj©©ýp­­ÿr®®ÿr¯¯ÿr®®ÿp­­ÿmªªþg§§ø`££äVœœ¶J••j<ŽŽ"UU$‘‘H‘‘#N˜˜KRššrU››•Wžž¦[ŸŸ³YŸŸ«Tœœ™P˜˜|H””TA””+*”” ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿàÿÿÿÀÿÿÿÿÿÿÿÿ?ÿÿ€ÿÿÀÿÿàÿÿàÿÿðÿÿøÿÿøÿÿðÿÿðÿÿàÿÿÀÿÿÀÿÿ€ÿÿ€ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€?ÿÿ€ÿÿ€ÿÿÀÿÿÀÿÿàÿÿðÿÿøÿÿüÿÿþÿÿÿÿÿÿÀÿÿÿðÿÿÿÿÿÿ( @ ÿÿÿÿÿÿ"&)+-....-,*'# (0B00c@@‚HHšLL¤JJžBBŠ22nO>;974/)PP#YYx\\Ê\\ï]]ü^^ÿ^^ÿ^^ÿ]]ý^^ô\\Ö WW”GG9 ÿÿÿÿÿÿÿÿÿÿqq ccZaaÎ``û__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿaaÿ bbýddÜffuccÿÿÿÿþþþþ!ïï!ww‚ eeñaaÿ__ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ^^ÿ__ÿ__ÿaaÿ ccÿfføhh¨gg ÿÿÿÿÿÿÿÿ(ûûF±°•yyóccÿ]]ÿ\\ÿ]]ÿ^^ÿ__ÿ__ÿ__ÿ^^ÿ^^ÿ^^ÿ__ÿaaÿ ddÿhhýll²mmÿÿþþ þþ*ùù[¼¼¦~~õeeÿ``ÿbbÿccÿccÿccÿbbÿaaÿ``ÿ__ÿ^^ÿ__ÿaaÿffÿmmü#ss˜ss ÿÿÿÿ ÿÿ0úúj Èȱ ö wwÿ qqÿ ppÿ ooÿ llÿ iiÿeeÿccÿaaÿ``ÿ__ÿ``ÿ ccÿkkÿ&vvò,yyVÿÿÿÿ þþ7úúw ÓÒ¼÷~~ÿuuÿttÿ ttÿ ssÿ ppÿ llÿffÿbbÿaaÿ``ÿbbÿhhÿ$ttÿ2~~À3ÿÿþþ þþ9úú ÚÚÆ¨¨ù††ÿyyÿwwÿwwÿ uuÿ ssÿ qqÿkkÿccÿaaÿbbÿggÿrrÿ5ö;„„Qÿÿëë ¯¯t ÌËÎ ÞÝÔ°°úŠŠÿ{{ÿxxÿxxÿwwÿ uuÿ rrÿnnÿffÿccÿ ffÿppÿ3ÿ?††˜?hh8llÜ ‹‹ü ÌÌß ááÛ¶µúÿ}}ÿyyÿxxÿwwÿ uuÿqqÿooÿjjÿ ggÿooÿ0}}ÿAˆˆÊ8 kkkk·jjþ qqÿ˜˜ýÖÖä åä༻û——ÿ‚‚ÿzzÿxxÿ wwÿ ttÿqqÿppÿnnÿppÿ/||ÿAˆˆâ>‚‚%uudtt÷ ppÿmmÿyyÿ¥¥ýÞÜèééäÇÆü££ÿ‰‰ÿ||ÿwwÿ uuÿ rrÿ qqÿttÿuuÿ/||ÿBˆˆë?„„0qq{{Âvvÿ ppÿnnÿqqÿ „„ÿ °¯ýâáëîíé ÐÐü««ÿÿ||ÿvvÿ ttÿ rrÿvvÿ{{ÿ0€€ÿB‰‰ë?„„0{{@}}ðvvÿ qqÿppÿuuÿzzÿÿ »ºþèèïñïíÕÔý¯¯ÿÿ{{ÿuuÿrrÿvvÿ"~~ÿ3††ÿCŠŠã<&&v"þxxÿ rrÿuuÿzzÿ||ÿ‚‚ÿœœÿ ÆÅþëêñññîÕÔý®®ÿŠŠÿxxÿttÿxxÿ'€€ÿ9ŠŠÿDŽŽÌ3-……¡%‚‚ÿzzÿvvÿ||ÿ~~ÿÿ€€ÿŠŠÿ¥¥ÿ ÊÉþëëññïíÒÑü¥¥ÿ‚‚ÿwwÿ {{ÿ.„„ÿ?ÿE?3‰‰¶(„„ÿ{{ÿ{{ÿ€€ÿÿÿ‚‚ÿ„„ÿÿ¨§ÿÉÉþééðííêÇÆü˜˜ÿ"ÿ)€€ÿ8‰‰ÿF‘‘öBŽŽT8 9º-††ÿ}}ÿ€€ÿ ‚‚ÿ ‚‚ÿ!ƒƒÿ"„„ÿ$……ÿ$‡‡ÿ!‘‘ÿ¦¦ÿÄÄþ çæë ééâ»»ú*‘‘ÿ5‡‡ÿCÿI’’Å9‹‹*=®3‰‰ÿ!€€ÿ%„„ÿ$……ÿ#……ÿ%††ÿ&‡‡ÿ(ˆˆÿ-ŠŠÿ0ÿ0””ÿ+¢¢ÿ ½½ýáàäååØ*³³ø@––ÿL““óFŽŽ[@:ÿ(„„ÿ+ˆˆÿ)ˆˆÿ(‡‡ÿ(ˆˆÿ-‹‹ÿ4ŽŽÿ9‘‘ÿ;‘‘ÿ<’’ÿ;””ÿ4œœÿ'µ´ýÜÛÝÞÞÍ=±±óI™™œ?”” ¿¿¿C’’^Aú1ˆˆÿ1‹‹ÿ/ŠŠÿ,‰‰ÿ0ŒŒÿ7ÿ;’’ÿ>””ÿ@””ÿA••ÿA””ÿ?””ÿ8™™ÿ+¯¯üÔÔÔßÞªÙÙDþþD)H““à;ÿ8ŽŽÿ7ŽŽÿ3ÿ8ÿ<’’ÿ?””ÿB––ÿD——ÿE˜˜ÿF——ÿD––ÿA””ÿ9——ÿ,¨¨ûÎÎÈøøoþþ,þþ ÿÿ*ªªJ””™G““þ>‘‘ÿ@““ÿ=’’ÿ?““ÿ@••ÿC––ÿE˜˜ÿG™™ÿIššÿI™™ÿH˜˜ÿE––ÿ@””ÿ8””ÿ-¡¡û!ÈÈÁõõkÿÿ0þþ ÿÿI““4O——ãF””ÿH––ÿH——ÿG——ÿF——ÿG˜˜ÿIššÿK››ÿL››ÿL››ÿKššÿH˜˜ÿC––ÿ?’’ÿ8‘‘ÿ2ŸŸú'Äü÷ôeþþ.ÿÿ ÿÿ?P˜˜|P˜˜ùM™™ÿSœœÿSÿPœœÿL››ÿMœœÿNœœÿOÿOÿNœœÿKššÿH˜˜ÿD••ÿA““ÿ?““ÿ>ŸŸú5ÀÀ´ óóUþþ#ÿÿC““T™™£S››ûXŸŸÿm««ÿm¬¬ÿW  ÿTŸŸÿTŸŸÿU  ÿTŸŸÿSŸŸÿQžžÿOœœÿM››ÿL™™ÿM™™ÿPššÿO¡¡ùAºº˜ùù1þþÿÿN““W››¤Yžžùf¦¦ÿm««ÿb¦¦ÿ`¥¥ÿ^¤¤ÿ]¤¤ÿ]¤¤ÿ\££ÿZ¢¢ÿY¢¢ÿX¡¡ÿX  ÿZ  ÿZžžÿSššÝETïïþþ ÿÿLŒŒXœœ\  åe¦¦þm««ÿl««ÿjªªÿiªªÿg©©ÿg©©ÿf¨¨ÿe¨¨ÿd§§ÿd¦¦ÿ_¢¢øT››»H””<UUÿÿ3™™Qšš8YŸŸc¤¤âk©©ûq­­ÿr¯¯ÿr¯¯ÿp®®ÿo­­ÿkªªþf§§ò\  ÆQ™™jE‹‹H‘‘N——*Wc\  •b££³d¥¥Ác¥¥¼^¡¡§YŸŸ}Q››E?™™ÿÿÿÿÿÿÿàÿÿÿ€ÿþÿøÿøÿüÿþÿÿÿ€ÿ€?ÿ?þ?þ?ü?ü?ü?ø?øøøÿøÿüÿüÿüÿþÿ?ÿÿ€?ÿàÿðÿÿþÿ(  ÿÿ266T??h==d**N 4*$ÿÿÿÿjj ]]][[Å[[ï]]ù\\÷]]æ \\¦ TT9 ÿÿþþ ©©] llé^^ÿ^^ÿ__ÿ__ÿ__ÿaaþffØmmFÿÿÿÿðð4«««wwø jjÿ hhÿeeÿaaÿ``ÿ ccÿnnÒ(xx&þþ÷÷A ÀÀ½Œ‹úwwÿ ssÿ mmÿeeÿbbÿmmý1~~…ƒƒ! žž¾ ÄÃæ••ü{{ÿvvÿ qqÿiiÿllÿ5€€Ê8qq‡ ppû£¢õ ÐÏí¢¢ý€€ÿ vvÿ qqÿrrÿ6€€â?††${{#xxÜ qqÿ ||ÿ µµø ÝÜó¯¯ýÿttÿyyÿ8……ã?††$+„„M||÷wwÿ}}ÿÿÅÅú ááô¬¬ý||ÿ$~~ÿ=ŒŒËC††4ˆˆa#ýÿ‚‚ÿ!„„ÿ——ÿÆÆùØ×ð ››ü6ˆˆýC‡UUU˜˜ÿ+°°ïÓÓ‘óóÿÿT I””£C““þC••ÿD——ÿH™™ÿJššÿG˜˜ÿ>••ÿ/¥¥åÖÖrþþÿÿN––1Q™™ÖY  ÿX  ÿRžžÿRžžÿPÿJ™™ÿG——ÿD¦¦Þ&ÑÑOÿÿS››@^¡¡Èf§§ûe§§ÿb§§ÿa¦¦ÿ^¤¤ÿ[  öVœœ©?©©$ÿÿ[šš_¢¢ng¨¨³k©©ÎjªªÊc¥¥¦Z¡¡WOÿÿð?ààððàààààààðøþSlic3r-1.2.9/var/Slic3r.png000066400000000000000000000503721254023100400153150ustar00rootroot00000000000000‰PNG  IHDRššQÿQgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs  šœP^IDATxÚí½y¼%W}ø=çÔrë.oíM»„µ0"¶;±ñ‚œÎtÏDÀ$1aqbLˆ;v·ž'ƒÉx‰?Ç XÄtljã;8x°±x‚A!¡½[½¾÷îZÛ9ç7œ:U§êÖ}Ýõë–’óùÔ{u«êÖ­[õ½¿åû[#"üÏQŒßrË!vÿýéÈ‘#zÑqŒ1Àà½ù͸rïÞ=«QtZg¹ç…élv*øáÓr2AòÛ¿Å;îî‘# à¹õ`ØÿšÌ]w-c¬ÿº×aåùÏ¿ùú(ÚûrÎý+£¨`}"ÕeŒAй`«œs€ ‰T¢Mˆ´”29ð³yŸÈó÷©íí/ÿÙûÞ7ûÒ™34v/÷رÃüþûѳtÿÃ1Æ:Ä­ƒëÕ¯fûo»íy·//_û­ËËûÿV§Ó¹^ ðA¤¡µ†”Ji €cÂçŒq0Æ™˜>1ÆJ)i´–Å¢8@Œs€1!Ë2¤i~.Ïãû²lú—çÎ=ù‡ŸýìÃ÷~øÃtÒ^g¥^Ÿ]€û h‡GUöõë_Ï®¾ùæÛ¿iyùÊÞ7û¾]¯× †<—ÐZ‘ÒHó< 2Î=áAˆÌŠ.·ó÷ÓÜb*!2’ Ф”&¥2R*‡RgŒçæ<³Y"Ó4y`:Ýþ¯ÃáÉ?þøÇúÄÇ?Ngб Æîº DÏ‚‡øœZ%ÁpD3ÆÄOýÔÁïX_¿úÕAÐÿÎ^¯{SE ¤TRi" @3!B†}x^Î=pÎ0°˜Å ]¬ï2»¯R«f+Áªµ‚ÖŠ¤ÌIÊZçÜóˆ4f³DÏfã?N~è‹_üܾçzØ~ÆÆã—;àžÓ@ÛØ`ÜU1o}+»ýE/úž[ZZ;Üïw=" ψ´RJ2@3ÆóýAÁ÷;`L€ÈhÙE`j߈´`;`%€p¬þÿüê¯ÒgÌwßàwÝu×e'ÝžS@cŒ±£GñC‡Œöæ7³ƒ~ÏÛƒÕ;——½pò䃿ð/þŃ ¢™y·‘Ç—úù<ëÖ”bGŽÜþ·öï¿ñ'ÖÖÖ_ÌGžgÊP `D Z+„áQ´ Æ8ˆŒÚô í¨ÀG…ºk^ÁÎ*²¹¯ý±WRoÞds€3œ@ÈóY6ÓD„N'äq<£ÑèÜG}ô“ÿú—~)þàò°ÝžÕ@+n  Ã‡ÙµßøßqdmíŠèõzHÓT7tB" ×[CöÉJÒÚ\ÀΫì¶êxgoý5UgóX†ºsGísŒ=ç‚–Ãðu Y6ƒ”™Â玭­áö™3üü‘#Ÿû%"]jÛíY 4÷WzäÈ‹_{Å/ø¹ÕÕµ›¤ÔPJ*@ &¥$„ðÑë­Áó‚âêZ¤Ò<8Ûj¬å,8Oco©BQjþ}–±@³^ªÙ.e‚,›c\û~(ò<ùs'û‹_ü£÷¼Ç: —Fº=+vìØaqèÐQÅëý«õ-ÿtïÞ«t0èûiš*"ʼn4³ ÓZ"zèvWa¤—½ÇMºâé€ì··ØÑ  ¶×uu]²ò£N…µÝ@DZ‡íí­'üóÿäï|ôàBÁ6o$4®íiçY4 ²W½Š]÷ÊW~Ïÿ}àÀÕß+%A©\Z˜Xb²NgQ´\ó&ë^ep‹<ÎêØâÆÍQMPÌ;õ[íî¯(s Xp^WuÖ·ÙuδVØ4qîéN'£Ñ8>qâñ3?sÿ/Ñll¬~¢‡s… gÐ68pDßy'»íå/ÿïÙ·ïŠ;²,W† Ó¼ˆ)Bk "…(ZF`™{"Ô$šyøÕ½nYeŸµÙ^óg'é´X6m4;šÒÌÚ‘F]ÖU¬k×qO;†Öœsåy¾Èó§N=þÞßøOýÄç?O§ŽcâÐ!h笱Î0o¸Kyeç{rÏ" ½å-ì›o¿ýµïÙ»wÿóã8QDJ˜,ŠJ’)t:Ëèt6¬S“Zƶ± s鋦gØäĪ{íz€î0``µ×õQ'msrÅ3&€j×ml8kËUÒ­òD‰PH6ÚBNuÕ¹Ó˜w¬td-Àmž¿nÛ¹ç¬Àf.Șs~«F%ò<µ?ÂCzìôéãxß}{Û»ßM_®ê'8:Øì PÅž@³ ûÁdßtÛmóCkkëW%I,µÖàÚcªdKC_p.Ðïï…ç…å=QJb4:)Ó–mvÛì¯æûÙÜùI­:iNòÕ÷³:Å‹VÏõDÍ:‡R9´– "Áˆ1AŽàgΜ¸w{ûÿþ‘#øÊtÚë÷zSl@M§P½r@ŽV Õ¯¸m\Ö@³ {ãÙËï¸ãûÿýúúúu³Y\PuI¦µB§3@ôН­±´t¾’ŽJð%É£ÑIpΡizÌGæÇùÿöã‰jìYãóç3>æ%a›mf‰]ã}(=Q@C©B0BÐ`Ðã“ɹ?Þ¿ÿwþñ+_Ù9•eºdP9€ÌYíü ò.H¾ÖaAö¦7±;¾îë^ýþµµµëâ8.m²ÊðWÐZ" {ð¼ Èz01L!h-Y—1M­mVœcv"`ÛìcàÂ炦ø$¢¹c*¡¥kŸ¥u=ºà˽6c¿Ùçfsk?jpÖér0xöÞp8,1Ö |?³êтʽ1_•#\¦@³ {íkÙßþí¯zïž={nr #Áìÿ¾D,sCkµa) « Œ ¤é&ç¬JrÁ`\“°¯ÛÂPí$®}íj±ZÓómJSdw%^ÓA´Çshm¥™†Ö œ[ð)xCpµ²øy~î¡Ûnû³w\}5)˾?Ö0àJ³ yÀ¾¶R¬á\¸:¼ì€Vð;êU¯b^ùÊï}ßÞ½W¼x6³†¿‘t첌 x~Z+pÎlæ*¤LJ£˜Hƒ1€sq<Ädrª $K£zè©î…Î3÷ç3Aêa+ Êqkž°Ï²þ™Õzõ°€«œ T»ÝÅ9ƒaè©•Õ®PróË/}é'~tÿþ­¡”½}ž7–0v˜ ƒ ´É$æÁö´@\v6Zá´3æ½ë]ßö¾k¯}Þ³Yª”’Â<å¨LYx˜+¥'ƹWœÁ€°Û]G¯·^ºýq¼ÑèD+åÐôæ\[­.Á\’µ~ŽÆwÙÁkuÓ”XT“šóê•5Žg·Xém h!Bžt»¾\Z =­ÇßqÇ't}}{+ËVVƒ`;“@êy˜˜Ë @ cŸ• ´Wötžìe%Ñ66îbGŽ€~ög¿ñÇ÷ï¿êÎé4ÓD’£,𨀦TŽNgœ¯Šs•ƒ`èdrI¼ á…Ð:G–M‡¥k©ÕCsÕÐ<¨\·:¶-Ä´ÈÓtÕ\/ÒfÔ6³CæÃbö½º Í†‘‚h¦²JC ^7T++‘Ƽä%ŸÚX_Ÿn*Õ[bjÕe `šç˜ø>Æ0Ò,F6h_`øEGÐŒ Æ:ª®¹†­ÝtÓ·½siiÐϲLš[iÕ¦ƒïGÐ*/¥\*ã(%mÅsg… £QÙwó!Õö£”ŽÚY*¢¼þ~×ãwATëžËÝV­»×ÖŒvØmälW… ¡À˜cF(1–AˆA Ð¸ZYñ°ýÄ×}ݽ?»¾>äyw¯SKÊJ)ó}Ì$“IIg4¤ÙW/•.‰ÆÐ;ßùWßyÕU7üX–)­uÎ- ë.Rfˆ¢¥’º07™ª³CÝÆ©gfÔm𯅰vŒ±ÅÇ—”…ã<˜Ï\»¬^9UçÏæ¥•ëeÒÜû«…ÙÇ9ŠEÃó4|Ÿ¡ÛóÕêJWp>=që­_øÙ}û¶6ó¼»ìû³@®!0000‘f ê@ûš@\íØ±Ãýø_÷í++ûÿR­sæ²ýf‘2-ê)ý"‘±ŠcV1Mó㳯Q“ ÊyÀznqæ¢`sÿùÒ¯»ïÿZ_ŸŒµŽÖ9ŸAk¥ … ‘šz&0 ³¦µÉžVÐü|ã’íØ±C~ú§_òwVVV_‘$)͇—ta›e"(X} ªÙOª´¹ê6[ÝŽ²Bul›áÞ–Ø”VÀ"Õ[?—}oý<Õ~àlî=óÒ±ú1)f&áy9ÂP£×óÕòŠ/¸Ÿ¼ýö/ýÂÊÊh,e´Îy ­Aœ«L̤TÏ›3ü›a&< .!ЬðŠW°++W¼Ý÷}(%µÖšÕUf­sh­Šàx¥RÊv³7š†¾ÝîV”Wª²­Ê¼®ê*» Î9Ï¿Ô%lݾrm1÷œ.ÅÑ´Ó¬ºt&„„ïKt:„þ@¨ÕU_þøÄ׽䋿¸º:k­z^¬µ†æ\Ä0vØÈó0*Ö­DKQçʾf»Ì—G» ðßñõ?Ðï/ß’$™ÖZŠºçeí0 Æ8÷ ” ¬Ê;+è£R"¹ »õ‚ûðv–LVU•H¶¾r˜®D²C·<*×vkªæzXªù0a$¯ÔBÁ÷%ÂC¿¨•_xÞøÔí·ù—ƒx¢ugˆ3’s$€šJ‰1ÆÑ´Ë. È€K4›Fü¦7±çÝ~û÷½EŽ×ž$M šƒ`0À¹žPð}…N‡Ðëùje%A0;yðÅü»¥þd¢u°Ìy’kŒs1UJ…À6àoù8I0ítæŒÿ¯‰ù?ߨuÍÚf¯}-»¾ß_ÿÛ¦g˜b•‡)Þf^Ä0¹CÈV6™•nmÑ7Ù€ˆ€šæ‚£ „ƃ‡+‰h¡Ç9*÷8ÿÛTeµˆœÔm]x¸VŠiøžBHt: ý¾§VW†³³_üÈ» È:γ @J„©Rj$¶lùÀdÈ.ŠÊ´c×%ZQð‹—½ì¯¼ceeåÆ4ÍuUXRI±Š®Pðý^éÖ»œ— c§ñòµy@¬&©¬ r³(š ©ˆWG :·¾ÙS£ùX·¯š'[ç½K÷šª çFšyÈŒwéiCaÄç|ü×ýÉDÊ`àyI ãcC)1°…ŠQ÷.®ìâ±÷»*ÑL—ÐÛßÎn]^Þ÷:­6¾¬²Õrs‘\Ô€ºú4€´ž©k»ÙÄÆöÑ$M›n÷Ö»RÇ~îbP5ß_?¶žª“¶n8ËüBX€åˆ"Á@èÕÕûÁôô‹_üð»ƒñPÊ çyY‘+Æ€˜çù›Åº¥2ÜlŒ]°ëí.Àž=w|_¯×]ÏsYÍëÜ—QŸZçeWE¥¬Äs%™*¤ ¬Rf·™b¨*•LÛÚpÖëÇÌ'žÿ™T¹põàyµßF0*IWI2]ÈM8Éó OÖéz}O¯,‡\xÓs·|ä}½^:–2èsž%cô«m T—CÔÃJ…=ߨ5‰f<Í#š1Öítzß͹p¨ŠzZ¶žQ‡^!dl®@f~ýÖSµ°Éü×y¬J"7¹eqÁâJ¼vã¬É•U5¤m kc¶Z¯ÒÄ-© .|?GJt»„¥%¡×V=Ósoyâ×{½t’eÁ€ó,‡¡*†6œ+þo£âÉ,йWpqÇ®H´»ï¾Û?tè(½ý탗FÑàë¥T$sIV7ȪPÆDIÊÖIÍfl߯çÝæAÖy³ùbªï…ñòÜ £¶f|mf=¬TÙ`û›ë.uÁ˜‚žg@æû9:m) îyñټ౒¬çyY`Ê9¶•¶8—çØòý’Âp¶kª²9.:Ð>ðÝ{ó›T)¬¯ßôm½^eY®µÖ¼’²¤+”²÷ea¸³yUIŽ µcFÝÚ°ÎbUJ%(+Þœ.Œ¨ql6ìdUk”ÆY[>§¹n»uW±Sóã²6™É¾È9:‘F¿Çõò²Ï…7Û~þÍÇöºéTJ¿Ëy6ø”s½ `KlØò} 'Ìú}ÄØe£Ѹhª“1Æ~åW~bõ5¯yK@Z4*ît»+ßnrû%Y°XJ£™[V‘™•f%X=^YyçJÉB²óAtû°ë¹aó*®®îê©;uÒóŽC] »¹dÐT)Ŭº ‚NŽnWaÐgzeÅç~0Û~þMÇÿC¿;+@–§œó)çz`[J2õ9)@¶ëFÿ¢qQ$cŒ½ç=ÿrÏ«_ý–p0<Å#"Â[ߊ›Â°s»” ¶ãO²ùEˆ ¸5M ¦ ×TZ%e¤Ìàûa)­lÍ@qu pXUݱ›ª´®êêíæóÔêÑ >·´ÏH1—º R]Däˆ"…~ŸéÁ’Ç}¶uã Çÿc·›Œó<ì AEZÁ€ëœça(ã— ªTÇ»tã¢íçþ«¯yÍ[£ÕÕÕÔÑ£GuàÀÁïèt‚å<ÏÉpg󯽕T–›€Û0Mõß›O¥6±ÉgÔXaë¥-á¢z–ë|*Q=&Ywµ‡ê!¤¦±_kâ&(nòÈ*Ï2Gfèv––˜^^öxÎF×_wâý^2U*èù~:ÓÒËydÏãq:¢w&Ï1ô}¸†Ãö_ ža Ý}÷[üw¼ã³~ÿÀ9± ˽å-¸Î÷ƒ›¤”0±õÓ_oqPÒÖsíëëuÂÓ>R@¥rÈlM04Ó[3¬¢PÚl»êð…¸ýø6à¡ñ§?}‡Þظ´žåNã¢òh̸›Ú¬óçÁÉvm>z2£)|­ÔY½¶±ÊÏjç²JnŒP†”4²,.'®¨81—sÏQïfŸ›»æ ¶ù戮Ño*Æ]I&K £×gºßçÜ÷§ã«®>õ‡Q(¥‚cj¢5’¯|åùßÏÖ_4ÆcbI"Dž“îtÖÖWV^ù/ßüæßìî»ÿâÏ~˜‰ËÉ.sÇE AU^ |?xžÙf%†ëeº’Äfo˜‡eç·¬Oì¥J³šAß›”2†™[©J–lY »R·™®í‚³ú¬Êà§2½ÇXYu)ÑëƒÎÃp6½úê÷F¡Ì¥ôúœ«Œs?òèó^1-¿h8R4›% C–qä9ãq¬ÔÚÚÞç_wÝ7ÿclùÐ!REâÂe7.êEmlQñ?€«c×)¥#àP5Ý!a+BÖ%[Û<Äfû€j_Ó¦³Ç*•}ùS Ftõxhó¼@Ý+­"Úü1“Ó_qcÖ3êR¢?--1t¦“«®ü‚ï“VÊë2&sÎ!O<µvÛt˜L4M'(Ô%/€FÈ2‚RdŒ)ØV\暊ü=žeKKkßõ¶·­€ÿX8—hM´:ZWkº¦Ê·àš’7“ê]©TÂ"OÔ–¨ÍG¼ëçhî¯ÈÜÂàçÏC±Pi‡A† H)Â0FN†3„™¡1ºŠz=*@¶õEß—Z*ÞcLjòä©Õƒ³Yt`:Õ4™h6›1Ìf¼”fFe*˜Yûl¾Í@®·¿×Z3)¥îõºÑ•Wüák®ak—‹c°Àêåcóëyig¼*3k&-XêÀq_»ŽFPuš¢yŽ:ðLÏ1£+¯RêQ# 5:nW£ÛUæO£Ûú}Nƒ>gŸ§{öŒ¿ìûš¤ä=ÁIgÏ®¼ Ž;{§SMÓ©fñ ˆc$á…ÊÒT!ÏU `6¹Ó$ ‘œ X–I,/¯}ËwÞpØ|¿»vWsc7j¤¶U#s9eu 7÷àµut-c¶ò$Ý*#Û»exª™?Vå¥UÚÄ¥?ªvTn{**T£õ* ¾oì±04€ …0Ô;taHèvEcžÈÓµµÙƒ¾'IJÞBgš Î[¾9NÂõÉÔ¨Ëx& u $ ¦@žS2;­„E^mm5Q\¿•lŒREQ(ÖÖ®ycìƒD4ªWPìþ¸ˆ©Üåª&Ry=„4ã—$.˜ ‘ªAºžgoÑ$o›Ò­=gQºPý¼6FYy–º0ö °:³D‘D·«uUA_hôû†Âðý<][?\€,bL+ ¨ÍÍ¥›’$\ŸN5Mu9›qöÈsêR:¿P‘ÆÑõ^½DŠg™D¯7øÆ·¿ýÀwÀáÇ.©ú܉FDq»ªr$Ù ÁŠ¢àBš9„mšúÕ¶TÆ}{0¼4·Ÿk }Ë‹‰Bm A…Ê$#½ •„Úx–¡•dÜH2OæKKÓÇ= ï0¦s! ·¶û×§i°:›išÍˆ%‰‘d†Â`HS Ë¥)m¡NÙnŒVëúÜOe‚‚fÖVóöî½áÿ`Œý"J.¥º(÷c½ùdÂ:p¨&Qìº*ØzÀ5üç”çš?Æ«³.íæí5퀬ȸ( #É,¨*‰fAE„^Q·Ë˜ïË|i0}\p­•b!cZƒCom÷¯ãp-މf3bqÌ‘ÄFŠ»Ì€LJ×åy^ÎS•$Ú̺CUÕÂj‰¹2)––V^ñC?´ô àÒ†¦.º× ósmªÊÚEõðNýg&Ë¢Øê Y”½á\ æé7œ¤JUiÕ¦U—Fj9 ‹Œº4Ž£(âdOø¾VD,`ŒçPãaÿš$ V’„h6Ó,‰’˜ÒŒ—v™”f1!¦¼¼_nóA7µÊ’݈ɤ”ºßïöxÁk€Kp¿è6¥Dê\]“NóÙª.àX1`³}úN’¬üÜ0›ço‹&IæÎ6B…±¯•Ù‰Šÿª@æIÙïÅÇ=´”¬Ãw©F£ÞUiæ/§)êÒ2þd„,c&X®Ì5Iéö‡kO>p{ûZî±zm „aïùë]\:©v%Ze¥éìxÕZª-¸½ØlÐ:+Jòì oõ€wõþE)ÖóÙ®T´Ç¸IЦç…B¨R’u:QT¬²N‡3Ï“ù`?éyZ)Å!H1‚žL{W¦™¿œ&M±R’™8¦Q™Z”²&•ªÓšõ9ãëóT¯Ë–çNïÖ[n¹þ;Ì·¾ë⣪e\TgàðáCüèQ(­óGŠ›ÀìÃ^,ÅÜ×Fæy† è8L¿ .`¾ s(Çñ†t«ºk[®Ì±†¾0’Œ Q¡. €…ˆÐ8…Èú½ø8DJ³@p’ŒAŽ'ÑYæ Ò4‹Áb ®‚õ7 ãÈs0WŠAkB–e¸L.;`~l¶Š_ëTó«•Ê G3"¥z½H,-x5cì׉H] §à¢:‡¡¸94i6U™—LÔ"© h”ª§ï𤯍Î9Ÿ ÛÖòÝMﱞeÝø­WÙ©x²NDˆ"Fa‡1Ï“²×Kž7|ÃxÈò`¦ 86’,MÒ„—)?V’eY†<Ï÷¤žÚ,Ω·ïª7„6ͧ QÔ{Ùwâ:àҨϋìudY<Ìs3µ´g¯6Ge§inþ|)\y4Í«Î9/Ó‰NØTk“‰ÑÂú‡TJ0«:KuYu©d¿—œð<­”æcZq@O¦Ñþ,³ K,¸JïÒ€ÌÌ Èò<-{ùZµ9?wB'®‚ên±Ž}OQÍͤ”ð}ÿŠýû×_ T•i»9.*ÐŽœ!MÏ<”eY˜‰ŒW‘&°êëö­•3: C· ,8ïq⣮„lx¨õ×–'«¼KÃö+„‘*Af$g¾¯ò~?9Á„ÖÈädíÍs¿ŸeDIL,MºLí’²Ò.3Þ¥Y†,3ßÑ4¶i–º9z6îë: -ßÙügJiÝëEÞòòÕßGÝõ–‹ ´ƒÍ£ÝÚÂcyžŸ,¦[®,¥F¼sdÖ–2)c{UÐPªÝøwÓ…Ü`ÎWeX©fUePMeZ›, Q‡QIÖë¦'…Њˆ{Œïr:öæyÐOS IÀ’”#M²ÔH4ãifø2“-›e9²,nTv¹ [¹_÷Àëa½ù™c¬§pt»Ë/gŒ­š“ï.ÍqQv×])÷܃R¦÷1Dhó´D²ò ë#ÏS@Õ©ÛcmÔFu.×V³Árß·^¦%e •YÚe0ÞeÄYૼ×KžbB’Œ4@:žvöæÒ¤)!MP°ü¸RËü[ÓPJ±Â&‹Íwà†ÕÚ²]êRk§”xW*&¥„Ñ ÿÎßÁ­@U8´[ã"—Û!n"M$Ÿª²Ôm«úpçJrè!Ïã2'¬nô»çj»áÅ™k Vê’, ­$+Øþ‚ˆ Ã÷UÞëÇOyžVDÌcŒ©xíÉ¥‘di $)G\ðdi‘ąúLyéXeY\ýàn±­þ¡Q©ÛfnÕXó>0f`ÆÄ ×»öz`÷í´‹‚²¿œ,‹¿”çT&îÏ¡º‘vÔ“­Ts‡áרƊ›ínÈjp–Î0æ$Y'T¥MæþO1N¤4÷-Èf³hOžû%ÈÒÔH0c“¹¤,«HYÉ$ Ò4ÛÙ»™cg¯}>Ú1/éš…< °1¥H{ž‡~ù&`÷í´‹´ûî3h™NO"MÓ˜sk©Õ“KÈ6 {÷æŠ"‡ßØ‚°¹Þgõ@ª§Zm²7ˆ66ÿoÿ†yžüƒ«Óö-a¡ Ds1RN ”œÛgm6Sß´ot™ÀhKâL9ܼ4ëFœ¢N2!´VÄ}FФc“õJ¥ÜQ‘¢´ÇÒ”!Nqœc:‘¦YIÌZ)ÜtˆlýD3GÏõH]òºn.uOÕüð¤$0¦™RBˆ}{÷âJ Ò6»1v¥+÷-·X;mú—¹\L¥ ·Sšj¤¾Ÿ#Ëb„aÏÝŠ*/«zˆ¦æÒ¦cW9ÿ&¼DU€<¢ÂðçÔ‰ó|™÷ûñq!H+“…¡9#5Fûré÷²Œ(IÀÒ´0»Ä1a6“ˆcYäýJqÏ,c¶ÍïÝtlÜûa¿£©ë4€4ëÖ󮺉W¶š¥oÜçánÄOx)©XÀ@ŠsÊg³hŸ”A/Í% ciÊ‘fÐb`:U˜L2L'9’D騖Ê0‹¹Î¦=Z­›ˆ€ûºn«Õ×ÏgÇÁ¶L°¯|?¼¨z×íÆØ¥Ju¢cǘ€$þVšf@!¢êÒËŽùɼæ÷›nÛY6-'¿(>Ëy¯®U1¹ê2²ê² ôz‚z=Á|_¦Ë˳G|OK¥LçÓYo–ƒ$5I‹qL˜ÎÓ‰Æd,1ç˜L$âØµÌáËì¢ IæzŒÍÄ‚jÛ"‰UáÓöøz¸Í8®' iB ŠúWU÷nŒ]+X¸ÿ~s—&“äI’ž+Ô'Õ'`­Z°»Ž·Û»>̼œY–Ô~Ñ)‹2#à ’w:„°Cˆ" ×· SéÚêìaß7 ãœrÆ §Óh’ˆAkšN›N5¦SÙTc6%Ä1ŠLQ²ÿ¶TÎä˜UñÌf9¡ ©™ïhTcU}e÷PmÝ¥>Ú¼PÀxßy^½Ïõ½c`L\eO¸[Ç®͆£~í×è4}¤°Et›CP5m±yVÍIYÝ!@”öZÅ)Ù@¹aþ©,1$¬ÍñÔïZ%_1]}XÈI"¨ñ8º"I¼A’èB’¡(‰ãOÖLÅnJ2c‹YÇĵËÜïáv•¬ú4Chm*³Ú^Ùz¦GG]VÇ€¬³Þ»0v hDD‡õ9ný¿ÓišsÎÙªÇø¯ß`Ö¢^ÝuÀ¡TŠ<ÏS— ‹ ~¬ÛÕèõ4º=Â`É£ÁÀc~ Ò={â}_J©xÈIj2‰®Œ' ÙŒØlSA^,Ó)ƒ)“ce°ÜJ² dnî\)‹Æ6{¯š¤ýì*Zì² u'Áf™´íTï¯öç9!ËP»?óïcàœ/íæsvhDDÌ#"$[Ǥ”àœWH ¤ Ln— ª³œmö83›)š†·§Ñë1ô ú •íÝ?à2W’‡BhÉ9ÔpÔ½:I‚Á,M’l6­çÖbÖí1`Õ´Anfqõh#¤›Þs;ÈܤƒJÂ¥©Ù×4»æ½{æ3¶»ý8.Eõ²€­-ýÁÉtz¿ŒL¹ÒÊ:v¸E²æÆYʼ6ž% ›ŒóBdˆ"B·k–^Ÿa°äÑÒÀcŽJ÷ï‹¿y&sq¡%¹½Ý»v6 –g3Ðd¢Ù´–Y%Ѭ4³6™-‘sy²zºR´ªyL5êÐ4öë£zmÕ2c@š6 æ ìKÎE@ìæCßu 9Búða&~ý×i”¥›¿˜eŒçãªB†ú².Õ\¹&¦mÐé°¢ËO†n—ЃGKŸuBØßy¦ï¡s0d›[dTÙtÊ10L[%+Õ•eü-…ÑÌ‘sÃHÕ¤cu.Í|×Z–쎒.M­—Ywœš÷Ï‚Wë2“rׯ%éÇ`3oxœÍâÏyãŒ1el²JzÕos&|u{—U ó}†0d†¶èqD‘é;x4ø,ì¨ìÀø>?È3)y‡ !;{¶ýt¬NKu9›šeZÿ®]f‹J¬‡Y_6ÃG•½åJ)Ó4‰M§ »µ”Œv.ð,#GRZý¼˜Àä£TÌS¹[ÕP—hVª}ä#4ÝÞÞú…Ñ(‡Öf*;›¬fÊêÐH!,È€Nh]BÀ±´äS¿/X§£Ò+ÄŸ‚,•9„йÖÈÏœíßÇÁúlF4+6™0®I!ͦÈL®¿aüm*¶YEc´…jpi ¤Wëu–¿mT’ÏJÓ9›ÌÞ¡ÆÌ}NâB Ï¥ìöa=Ð÷¼‡Þ7N>"%±,c:ÏT³ *nYùßJ3Ó²ÉÔ6z+Tf²CÀ°¼ÒòrÈ¢ˆÒÈ”â‘çéŒÙÙ3ƒã8\ŸN‰ÆcÍ&°ÉD`2áY2«.y©.‹Ô›š-ÖdnQcM¶-*ÐFsØsÕ^•³¦½•mî2?Ø‚ó0,Ã.K4—WËóÉ‘Ù,µæ"MYÊ@ÊJ•`µ En™ÐïKK!--…, ‘]qEò—Q˜%Rñ:Uùé3ƒ›fq°>™h›L€é„a2á˜Ò̪É*׿ íØÀ¸+ÅÜ9Pn¸ÛÊÉÔô"ɶXš1f$i[¯v¾¸§òÔÝó˜‚b¥Ò4íöó¾¤=³Ž5Sʼÿýô§q<}i®bÚ…Œ'âŸö¼àÛƒÞ •bŠ1‚Òš %+æ0rSL+ú’ƒÇòJHËË‹"J¯¹&þL§“ey.ºœ«€ÕÞOd@f%Y}Î5^×5yGš&'Ïœyð¸ÿþcϽìÆÑ£¤fâC¢ßNG¿hâBUÆe£Y®,ŠŒªìõ€¥%ŽÕÕVV:¬ÛÅìúëâOu:iœKÑcLeœ³ìÔSK/šL‚½“±¦Ñ¶bãjö˜•h&œT©Iå½Ì×\mé;îp“ë!){<°“údÌ8Ó©1š*Û"Õg;¤7 Á‘¦ÉCø¾T‰»1.  ·ö•¯äÿlkkö§žÇbŒñ$É$ñ}—âÙ^V@€} êî»™Ï=ô»J Èœ‘µËú}Žõu²þÓ<ü±n'™™™{eˆüɃÛ$Ó4I6.$™›æS5'®˜}ÛØíã,V—;ƒÌl©ŠD€º]V"+ìy3×€;ï;Ø‚×d®ÁŠÙù!8´–ãÙì¡/›½wíês½Ì€fnë›ß ýÐC,|ï{ñoýrHt»Œº]MÆ&ëèµõˆ÷ûzróóGEq"¥·$„L5„|âÉå—Œ†ÁþÑPÓpèþU¢¢™0¥-Öô(ë4E[¦*Ц]Õsé ž UÿÚƒàÆœMmXÉÚUM©EsÒ¬®«Mf$„@šfO~ùËxìR<ÙËhåâøò2|+¿ôKêgûýñot:’/- ìÙª½{;|0Ã[nÙþÈ ϤôBÈê‰Ç—ï¼£‘¢á0gã‘.) W’™0R]’µÅ)ík÷ÿb¢µ®&›Ùµkµ«L.Ùd‚Âè/b½å9]n­Îö›;W—r þ œ )L&[ÿåc£“üÈzîgoÌÚÏÑìÝ‹ I0•ŸüIõoöïtyÙc{ööDo Fo~¤¥‰2ùØã+/ýý£‘¢ÑP²É˜€¡T•n+O7§ßŒ6pµ±ümÒÌz¨Ö‘@MÂ1Ô?ð€Éqí7÷3XC}»YÅh‘d•Ê´gãœñé4ζ·¿øa¸ï¾»ž[/lÔî”8s>€@·ÓAOÊt/ Öî¼ÿþŠ+’OEQº}ðàø÷¢(q@æÉG[ù†áÈ;0J %— ãE£wªèùª¤ÊØ'´©Ìfa5Ìk7ݼ¢CÜ” ú7fÌ€~21©F;U}5§ënf"»¶œ±Éxù~Ƹö}4Mú³?“_**i7Ç% AÕ|pzï^tDRbÕó°šçj¿ïóå—ÓäOd6ùr7‚–R, !S"A_ytù¯Î&Þ£‘¤ñ(gÓ 01gÎKWUÚ:„E=t©æîÌÔÓ‚šÝÚÔ«™—Ôd^,2ø+ïÓݦkÙ<…šº¬î2/2p'ò™ÏÐl·Õ&pI$ZíÖZ›Ltt,Xõ<±¦ö ÁW¥Ö{Î{Ý.´R¢Ç¹Ê‰„zøáåo½+†ÛІÛ9ã i†‚ˆ%G]Rƒ«/Í&võ107MT´ÓŨ݆³ß8I€ñ¸ÙüÝif»×ÙF]˜Ûhç~wU)cŒ8b:1žú8|øð%Ñb—2¨n%™à£Y–aX°¦”Ú#VµÖ+èk舔è0¦2@à+¯|Ëxä_5K=å|21†[þÖ$aë9_óÍŒw?à’¸ÆcçÂÚê&µÇHU»­mÔÓ‰ªmH‹<ËöQFžÇY§?õÔÉ?€ƒw/¾éŽ]Fwͳ( UÙ°X°À!øºÖX0 _•üòƒkß2úWo¥ÞÚÊøh¤1™ Î‘Õ*ÅÛKÙìk{umR̶°ži]*:G–ä®ómAˆcÂxl@¶h¦½ê³…™\õhöW<«9ÖNBùüÉÿ÷›¿I»ßtÇ.J´ÚÏŽŸm%YÀ€e@¬)¥Ö…À²Öz@@‡`@¦µ¯¾ôÀÊwŽÇâêñ(×£QÆ'*HNV©J (isÛ«£-Ѱzøm¹þÍøc½dÎñuæ:ÒÔØ¢Ñþì­ÉyeÜÛÿ.È\iVQ €‰Ù,¦áð©ß€Ã‡™(jv}ìÐê {òIxW_]©K})±âyb kŒaÀÀ{€‰„/˜J4|õ¥/­~×öлn4Ìôh”ñéLc6cH¶ò,çcF•¦có¼Ü+­§g£EjÙsVÛªbß*—?Ïçç‹4®¡Ne8{ÇÖÓ¦ɧ}Ÿ‹étúÙ{ï=õG@•>)ÆnÚh¥$»új0F¿X–=+V!Ô24–´F¿™Ç }þók¯ÅõÃa®‡ÃŒO§ºÌ!«›˜%ÐLÇ©Ó nx©>y›‹¹6ÃêW]žWûƒ¬ÚfØûf öNõ®áo÷ñ"ÕÝ8qãôéÇ~ù³Ÿ¥íÇ_2i˜r«‹yz{'8Ú½Ë S)¬ ­Q©K`*Õçþr寷½†ÃLF)ŸL´“Ó_¬*)¥óPMižu æÕÙâê£vÍôÎÍsc*¹ˆkk—dóR«*4±\˜µ· 88÷ „W¬ûB€sž@žçÁóBÕéâôé3¿ó®wÝ÷j"3Oçnfk4ÇE”hs†W,! u ²+…º¼ è€ÈBäS­Cýß?»úšÑPܰ=Ìôh˜’ Ì ´$6-H,Ö.dÖž³’Ë:.pÛ3-êŸWmsÃKdói>Í쌪ȚóÊ! bÄÓiŒííÇ•Ȥ_]Ji\T‰Æ,ÀlXÉ¥0–‚kV´Æ çXÖ=h„¾çyyªuG}úÓk‡F#~ãp˜éá0á“©BW•IV’Uìîh÷öÊè¼*­¾½òµ6•áYÇþjk»[wHªÆ6•$«Jsp. 0‰VI6BØÿFºy^ ¢(gÏn~ô]ïú«ˆ(»ÔÒ ¸h­•ÆQÐA€> –µ¤5–P¨Jp­=æyùèÊOzåðpÈoÜÞÎôöv̧S]fº¼õtžæµÔAU1þ£Ùn'¥Ìgåy•éQñ\í6Ý…€Ì¼ÖhRdn‘t³¦µNmXibLˆÉ4¦Íͧþ-e—ƒ4. ÐêY¨ƒÌz˜ÏS­±Ä9zB­ÁµàyÙDë¾þ³?[úß¶·Ù F£\oo'%Èl•¸oÒ‹À¶³JsiŽ*¥»ó<‘ºÈ¦[²æú<ãß¼“uÊ¢R£m.cBÛ§Ïýλß}ö·€KëiºãÚœMfAæ²Kž‡%ÎÑE2æyÙè«O}jå ››ò››±Rž$¼˜®êÜcÔe½gEÛCoVMIgæ'0 Ê²ù"’¶Ïxº s³3ªØeyupí1V“föú­%RÎÀ‘çyb4'§N=ø¯KË›5Ç3´Af=Ì€RX]­yȹ¶ uォûܹü››±g|2É‘¦ZûÐÚs<Ë*ø½ÈúhJŸr,°X °fºPUQÞöþóÙtíïi¦w·‘ToÿTRÌ\WÅ›¹RsO AÞhtî½÷ÜC;zôòð̆ š¬¿Y/Ïч‘f!ÐÓ=€‡œkV¨Ë1°’~üã«o8sFÞræL¬·¶f|<–Å,$º˜Þ&…”ª–Ç_¯«¬/†˜Õ¥ 4óh¦S*ÂUÆ{5w÷çsÏÚǼgÙY5æ$å¶v®Œ•RÍítÉӞǽ­­í‡žzêø;ÝŸ&ñ|ãk”huIöÐC7ÝTz˜Ö&ëù>ú–”B_t9‡h®uγ1°¤>úÑ¥Õ Ï›éíí)7“Að"½‡)>€cÆÓ2“Ú×›[[ÌNk¥—ÝߌE6ãͧhÃÙ÷t·»Æ~Ók2üöÿ\FMr¹û Ðç‚O§3:{öáö›¿I>ÌÄå$Í€¯™Þ¨ ÿÈlü²0ü±` }­U—s­Có4Öõïÿ~ÿOŸQ·nž›©Í͉˜Nó¢ß….[õVÏx`L€sQQµžbT~9ƒ Xh£Õ~jö“í/sÊâ(*¥YWJô<=‡ À•ê!’°¦þóoïùÑÍsòe››µµ5³YVÄ,uiÄ»ù÷bCÍ?Ж71”=(æc‡(Êᜳ £SìwÖT®ª¬œ€zò"Šžou2Ö‚M8a(³x^ …?{öÌ/½ûÝ[ÿ0í¦/u˜i§Ñb£Í)7óÂC=8ÞÐ"cK×óÐQ Añ>•ç-D²ìKÿãÚ÷cgÏÐËΞ©sç¦Âðd@.m?2›¨X=ÄöŠíú/2ôkVe¹ÏÒ ZËr!ÇÆ2v–zF@¶h½öºÔr¥qUÙÄÀ¹'…ßÜÜüÍ_ûµíŒ]v9ƒ ˜Sl‘c°ùýCxX®©ËN±„Å(O@)H b¾o)uûOÿiåÈÖVþÒÍÍ©c1›Iä¹¥.ê“>Ø-W:ÍûS¬Õ¾Ú¿k1ÈÚTeµ¯)Q]»¬Zr³3€s.=ÞæææÇ>ó™so$2í¾.g•iG›ÆZ–Jš™ r?M†aée†0Àó”B1é+R KBÌÆR^é=ºö¯FcyÇÖæL ‡S‘$yÑìŽærÈ,#_í|´>—ÎÉoËv­ö5%Wu\“ u37ˆ„2¸·µµý‰F»>yØW; µÚfÂYÊ×i †¾ @I xòâ¿$âÀæ‡>$ÿnšâ67S¹½=Y&‹œ~^T¡¸±ÆrsùÝŒ óðÚHV{ÙOˆ_õ¸“·ñ:¿[Ön›¹s“o&02•æð¶¶¶>yòäèû÷wé̳ d@é ´¤A^‹`ÏõõûÆî’€©¶ît €µÖFº…!LP±úÖo½ãðé7¾÷½v»Ko2ê@+€ ÃÞ»™±+݈8'§T®n¯]¨cðÌŒ¸±z¢¢ÙWwvŠ_¶ àŠs.´Î1í}ïK~ˆòÇ™x6‚ ÑœkTÉð:µá®»¶œ•nÚYT±èG®¿)@tçìGQÿç¢Èƒ”ZjMžÍh°ÁçzfP'FÛ&ßj>øglí´²º$ž·ÍxCb7ƒäu ƒs!}_x³Y¢ãxøïþs€É-{¶þmÃuê\B½µŽFÀ¦;E˜›]§ë¯;~ÿýŒ>øAz×ë_Ͼ eçWúýà:"¦ Û‹W¡7ËÊÍ|ó¹Ý°ÉæG3fÒhái×ÐdÿÛø2—õgŒkCÆ2o2™>™$ÛoýÀèð±Áø³d@]¢•ÛÐÚâÔXÐX@Äc‡=Jêû¿Ÿí ü_ît‚ÃŒ h­¥ÖZÄÊ©ØÉ𒬽¥§{9ÏØí©¿jæjá¡ú¶yúÂúÍ&#!<%÷¤”˜Íâc³ÙôG¥Ç76¿ë.ÐåNÆ^Ðt²=w ž£ñº= ¾®¶tÙ׽޽=¢(Ëy“9F$ªÁzFj{oÿö¯ñ¶ÌoiØdó s%[»ºl®sÎçBx0™$çò|ô“ï?ÝݼOÏ…q¾XçÓþ«sí7¼ÝˆŸ‹¢àµBpHIŠH3"âó`k—j»´Å’l>@Þ.ÉÊì Řà¾ÏX’HdYüë³Y²qô(=Ô¼?Ï•q‘+Õwø`G•ÀÞÀþ–þFzÏ”ÒÒ€¸Íšp%Ù<ðܳMÉœçÝß #µ­×S®KI¦cÌó8Ïs…,Ëÿ8M§?óÁÒ ÀŽƒ~.¨Ê¹»v©¿“ûë=|˜-w:ø{œû?Eb_‘É¡ŠÀ¶X¬F‹µgllÇ}m kOû©M3ÆÉ÷¹PJa6Ë>'eüó'NàƒF=2vø0žõÿŽwõR 0Òí[¿%Gôú׳«9Ç?ä\¼.ÄPŠ´a?ˆ¦Æî™—j²âšÑô.Ké0Nœ3ÍgB0n& Ë>«Tü˧OãèG>BS๩&[ïÞå4;66¿ï>0GÂí"¼ðÞâû¸Yif›3Ý.ˆ Ê¥ÍVûêÖl5e÷ÍK³¹Ü2ÍÀˆqÎ8ç\@J¿­Û¥¿æy¸Íó°ÇL[U£qÔæ /l<¸/í0,™4Ƙ‰‰—Óšølᬌ³Œþb8TÿõøqÜ÷…/àI1¿ÛEEy=Íõ¤XpšQ˜¶E5þëæûv thÌIU›ß&œÿn*’=ÞÍðåÝ.X· áyà'O"‡ÉQ:/y ®Ú¿ŸìtpmÐõ¾ÏnæW A=ÆÌ\Ÿö6ËìÜu^Ëg©nš‚´f'¥¤³DìDšâ‘éT=rö,ýÜçð8€@8ÀC)Ai Çç aÐÛÎÂ6)¸hU Z6^k\dÐ]T 1ãß—É’Îâ­)Í\pÙu . Eà¸ài =—À#^¯‡¥›oƾõu~çÃûƒ€]éy|¯çñý@D@]Ætq]B‰“Z#VJO‰Tªf™ŽFúÁÇ>þ¸šÁ¨C«¦ü~A@¤)tš‚¤¼àøØN¯Ï6wÛù@çJ:KÞXÔÅÛEZ!ÉlK€y µ©Ë6 5ã­s-ª=¬Ó C³OJÐp]Ü<Æ)¢òž†è@šB®®REðÂ~žC=ù$¢úµk ìjÛ¢òåe!Ìç§)è<ಟ}¡ûžŽ„[8Ýò¿ l.ÈRÙÅê>Ô´…™øO ‘  Ùš‚6iæªË6iÖtÜõ¶`?ó<°04ÿ- Šc(MAÓi)Ü4'*Þ+ÂÜó÷½R‚”ó$Ï…üjÁå¾^¤^¿UÚT£.Ø2»]œøj[öÆŒ ]¡:]If%›k§µm'Õé®Ïo[÷<@÷:@Õ@´Ló¹JpÃ.`;-8v'•鮟ÏQP¨Ûh9ê@ËÈ‹¨:‹·†#z³.üb °µÕ…Ú©yÚì´VgÀží@»`°5ÖqÛ¿ÖñµØfvýéH0¼ÇiÍWš¹NÂÅvælžnTãBžàÑMz£ p;Ùphy}!Òn7À¶ë¿ÅÑ&ÁšG›¶ÈãT-ÛÊ÷îÅÁˆ Ü×;Þ˜ GÚnÞWe¿ÙÑfø·¥“·mk{½h|Æù;¿Ö±ˆÕíÀaËgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs  šœ?IDATxÚݽy¸%Gu'ø;¹Ýý-õjÑŠ(,!0cŒ1 ûsÓx阮7n7c<ã™ákO{ÃÍŒ=¦©z46¸gì¦{üaã­±1®jÛÐ^hšñH 0…dÀHB F»jyÛ]óffDœù#2n.÷>-¨^•ä¬/ëÞ—7ï’ñ;ë$fÆßßh}ý˜8yòzŽ›ùW©ñ#?‚æÑ£7“I¤©Z-ÿÒfSò¼€¥D¬§»»Y2š1m©áîîߨ_ÿuÄÌ<­æ©SëòÔ)àäÉSxö.ý}€ ×]wŒàر“5ê§ÖÖ¾ýᇛ¡¸ªÙl|«òR)ý%fÕ0†!ä €@@&cÆ”Ù$̦OÄ›Z«a–egâ8¹7ËÔÝ?¾¹yûc¿ú«ñc̬K¿Fœ:u';ö솿7°±±!àøñBÓ‰HþäOâê«®zý?hµº¯óþáóYÀëë§ _$ žSpêÔºtÀï÷Rïû¿ÿ{ÿQ§³üþÝÔjEátšAk¥!|- ‚FîË Ì¦:Ï€Ì@~œPŠêøTßçÎ/>“JïˆÀÆh“e‰2 ŽN’égwvÎ}è¶Û>ÿ±?þcÞ€õõuyêÔ…„ç„llP@Y?ÿîw¿êºì­Fó Íf$§Ó JeÚ%„ð)Š:‚FIÛu ì@ WA&€˜Á Ì¿{þÌ ”³c…K™—&˦`N„ïqœš8ŽÿË™3÷ÿÚÆÆ]n¯sC?~‚/dÆð¬€²Ö¿ýí×¾èŠ+^ôSKKKÿ¤ÙŒ–’¤…aaØÎ5žaÌ, Ì·²—ý<Í7?.‹À¶Â@0Ê.ÄeyŒ"‚ZgœeScŒ’A 1wƒß¿÷Þ›ßÿ¿ÁwNŽ_ØàY+e_ODá¿ù77ý³••ÿÐëu¯VJC©Ti­¥1Šˆ$šÍ%A37ó&×Â"u+›þòVÌ ‹; —U5¥ÏÄì·˜¥–Y–°RS#%I)úýáÝgÏ>üK'NÜñf6Ê%§l ìÕ‰ü˜F’ĆY#ŠB1™L&;;Û¿uÇÿ߯üÁðÃÂ%<ë`cƒÄñãvDßõ®Wï%—\ö˽ÞÒK³,C–¥0¦[aØF#²¤1e³î@©‚S7óö±¬­‹„döì ÇjQÊX ó!/ŸlpšeÎ²Ôø¾/}_`ssë3÷;¯zU‹âØFìÀ×°Üs ÇA? ðp·øô‹¿øº·>|ÉqÏó(Ib 8Í×0F! [ðý&´V‚&¢¨ W™Ï àyâxBˆZÚWÏõ ‚fï¸`q`¸?ØWãÂÒP-F 0ÓÌØŠ£€.•T2M3µ²²Ôèõÿj{;~¸ãV).±Óô4{)~b ¿ˆ@tüø À/þâwý‹#G.Ùð<ßK’©´4Æ€YAë ¾ß€ç…³2¬”>Êà»âŒ1˜Œ‘sn Êâ¹÷,²õ@¯ü-Š r×Bv¬«n…aLk œ4* É@@"†BŽ¢@4„0T·\{íàkA9ðI’ C,ôÿOnÞ/Š1Ÿ à¸y×»^ûÏ®¸âòwAÆñ4×|kòµN!¤ÏÁÆ€‰r€³ Dãñ6¦Ó]XÚUa^›iµï°Í]Â:iTŒšÃá©ÒÏ„j\ r¦Ð´B¤TE/-"Î}âå/ÿ‹_n·‘*Õ[ö¼~@¥)LÂÖ¢ ÿŸ ÃSóíENž<&€ãúï¼ñ»®ºê;O4³‘€Ê5Ý–Ï}¯6†‘xq¼)Ac4ÚÎý¿žEü.úçšI®–E‹Ü@Õ8-/»—z–…¤|®³0ÆXÁ+~§Í\ÜßVû™›Í€—–¤‚íOÝxã'ß×j!ÐziÉóvc¥@žHQø~'O+¨»àA ãößúVºâe/{ÓÉ^oéÛÇãX1+ÏštUòûHéÙ@®Vo¶ rܾ8zôõÿ[¯×ýöé4ÑÌJÚß‚¯u ß‹ „„Ö)\EÏ5KÉLw‘$ýYu®¨˜P¥¿s÷hÿ.ÊEÀ|ðX~ÝÔ´ß¾ß ¬™}—_ì£ã.ìuÂÍl­6³Q !x^ÊÍ&ñò²hó–o}É_þF§©ugUÊÖÀ4=‘ÀZU\ÌÓÓè l6pÜüÒ/½zýÈ‘+>(¥×H’©q¼>`Á'ˆ¢.\þ/¥‘È%`7l8­_T½«ZT‚3LÖ·²¯Ÿo$© O•‚Æ‚ãE¡"Ï3ð|æv+àÕÕ¦ˆ¢áçn¸á³èv¡³¬Óòýá$}VóöÇUxú`^0 `{÷Ž›·½­qõÊÊÚ;Â0h$I2ß}´Vð¼€É£z«]V{ \€ÈÆ€gVCåQ¡ÝNóª€9pl(—ÞË öBèêà»÷;ËS3³…õpœQÏËŠ;mÁ++¾h4·?ÿâöw»]cÚK¾?Ì´–©RÞÀ(ß'°–Àùþoºt|A‚@õÛa¼üò×þx§Ó½!ŽØ¢NáûSáAof.mPU l•x)ƒº—öššÏ·uù:ÏÿT¶½ FåŒšà˜šEà<×gÁR# ·Û¯¬ø¢ÙÜýë¿ø‹j6!Œit€Q ÈTJŒ•RC£Jøà›Ñ~àYõƒßñŽ¿¦Óéü÷Æ0ŒQlŒ&§±Zg0F—´_×´Ùi¿x§meÿl5­^ö]ÜPÕî½%¡êrêT2P&ܪ¦ÞÔ¬’ˆ ˆ4ÏrÍ/üh‘rÙ÷˜Ò`,öáÕcõ¨¿ ÏÊ>zþ3«ÀÌ2ŠyA©Ç œo ¥†ïgÔ;Á«+èt†wÜpÃWþ0 !Œ‰ZžOµ–@÷¥Ô;°ßÀ¸þÓN÷.Š”Ò¾æêêÒ›[­¨“e©b6‚šæé‘õõRŠª¹7¦”6¹ Î¹7ÈzÏÔmñ^ݪ„M=`œâòù»W¸6UÁ"ÒBÁó4‚ C£¡¸ÛóøÀ@t»ƒ;n¸áoÿ0 !”ŠZBL'ZË woV󰦊o’ìy¢m_ƒ@Çõ¿ó7ÞäûïKc´pL_aæ³ÜÌ 0«šßwý}…¶V¢¼Sfù °Üù››÷<^´é-¹Þ%TÌíÜŠð öl{º†”&×|ÅŽÇ««¡èt†_yáu_û£ Œ‰"Ï›Ž´–CÛRb PÛ¨jþy úêÛ¾YÛÊ}ܼñ´tàÀŸh6í,KµKû €-àžäæßeE0U¶.嫘ó'!ræújåZ=|f]ÆÕl j l”o5_J ÏS3Íïu%D·7úÛë®»÷£lL 1Áæö[Rês¶Päú.Ý;ïàûjN^úҗ݆­ï¶¾_S9¸sÑ¿ÕD1³ðöšm¼®áuN½¥^Í+(b*½·<”„¢¢:ŸÔéܽÙÃ"Ò—Ò@JßÏEVó——CÑ鎾ò£÷þI@[ðÓ ø›@¸$eŠ·föÏoÔ¾/çýZ­Þ? CߟNfÖ¢ªÉ¶²ç&fºTphåó'mÍyØ\äö¶”J3Ó_h­˜1oŽ'pϫ畅§\»/ “© ½ÞwœPq¾Ó|•›}åe_´;Ã»Ž¾à¾? °RA(Dº `ÐçÐÛ@2ÈÁFïSÝöÅœú‚•ò}ÏK6¹­56¥g¥ÔÛ°Ÿßu÷ì;øÀyrü?_;zôF¬¬¬Œà-o¡K}?x9€JI´ÈãM^×·Ý0…`äP.ã–Ø6×0¹7=;O /®áWÍý|Á†|–žïÚ¶¬¿×ˆ"…F#C³™bi‰Ìêª'º½áýW_õwéû‚•ñáy¼mŒ¿ è³Æˆ³@æ¢}çósÍç}5ûç]n¾ùO:ÏþK²#GŽl­¯[ݸ暗½. ý£I’Á-ʼ~1ØE[•ä±cPTÚ¸”X!(wâÖ™·EÏËuúàj9Ù BqÞ<9äÒ<çï=¯Ðúf3ÁÒxeÅíöøW^ñàg|_„1*MÒá0M…)åmú~¶ÌÌ~îïù‚¯%ôŒàŸø¿Ã×½îåÉ¥—^º€Ož<ÁEíWlj­³¡`.m[¸™~]—«€@‘kd*©…õÀ¬Ð¡z€gòò²©ýžª5)¿êk|)>·Ê…ü ¿=S 7¼á'pY 5ÿ7½‰V}_¾À¢«ø™E¸lŽuM@ækì3¡0 J%µÐE„¸òGþìÏ^ú¢[Òk¯}©ÚبIèÞÎkxýõç¤ì¬t §uɰ)iµºüùNš²).ƒ5ßàÉÌP*³”A),È"´}qÜ¥…åɚŹö±Ô£ß73Í"ðu{„^¨Õ=räÈã·KáyDÙ4M}zð¡+¿g4j_µ½m0y"M`„l{½Þ•oÿÙŸÞœüØu×­Ë‹>pž‰ ë®[cB>ÀªÖš™ U=§å`‹gÚ?oŠ÷.¹Î²ƒü}vNÝJ¥oq¯žcñLN2•N—‚;s_;Aà‚½ív‚Þso Ôî =røñ/K!}"eÒ4ÂÃ\õšá°yÕövÆã±@š ¤D§ºÕj­^~ùUïxãéšcÇNêõuºhBpÞ€ˆ(/ýÊ ð^†Rh­ 3S%çr·[Æ¥š¾¹4xqO^l¹Æ(dÙY6­Ä‹{þʾ¿Ü§gJ¾^CÊ ¾Ÿ" m”ßn'3ð—–ˆºÑ£GùŠ”ž4§i@>zéwÍçmoiôAÓ©D’HdCk#â83½ÞÒ¯|åMï$¢ÖÉ“¬mñìÂoçÍœ8aY•ïüN4ÃÐ;L„R1§(ê82Å¥]àµ(Þ¶Ü”ˆ›Š¸¡`ú¸ælš¨u! ¥)1{ahóüFC¡Ówz‚ºíñ㜽KXÍWiÐã~ÅpÔ¸t{Ç`89ø€RZ€!"ÅB,-­üÐÏÿü·Ýà?¸âÙsVÜvÅî|zUëŠ(òÿE=÷…/׿ (³e`  m—q!$„ð@$óçeest.òÎÎë÷^~T¿Ýw:‚Úíñéƒkç¾f Viê‰ÇO¼±?ŒŽlo) &8²Ì¹=AJˆ4…n4¢ààÁµ·½õ­ôù|€¿¶¾¾.Ož¦©Z6†×”20Æ ü ÍÊd‹k-Ú¶ŠÒk¡ù¶ÑÊýWôY®êQ)^(fç²2{PnêmŠWÖ|3¿ÑЈíŽàvKR³™m®,÷¿ÁD³6F{ØÜ\::+ý¾æÁ@Òx,1™Xð“Ää,6°d¦Y!ËÆG$’$5­VsíСk~œˆ¾ÈÌqy­¤çŒ¸M)á1¯Þ][Õš½.•„« 5?kôÞAaõïúd‘YGrnîßg9ðA¤Ñ ¢†B£a,ømÁ­– F” ‚A(k¥%mowŸ?û+ƒæá@Ðd,1 L§„éÔ Mm|c5¿^»€…žMwÓZ¢ÝîþàÏÿü·¼ÀÇ.d@xÞ@J¤g&n¦­åÜ>òŒó‘åÊäLY»µs]Ae ‹ã(™xä1<ÏîA`†ÎÜç{àÑ4h·ˆ›M¢F”îözÇ@“ežÜÙí\=ûKý¾æa®ùã1a:’HS ¥\G“‚”€1Q6ËJ˜ Ì$²,Ó­VÔêõÿS"úsfΪKMîßvÞrÏS§Ü ')³IëÔêül»Õ;}Êtí¢û¢y³^¼y¢âŽÉ‹8®WÏääŽ òšM»·Z ­¶F«¥Ñí€;¢F#ôzÃÇàcH¢þ ý¼ñØ[5† ñX`<ˆcB’²Œ¡”ªÐÉnö³{^¤ÁF+ÒÊ ÕjÝôÓ?}åwÀúú±  ìCO '˜!êE–ù¿Ë|~„¢ùÃm…–ÛÇÂߣ#ï+(_{~¡ýNó«¾Þä¾ÞZfÓ Õ"nµˆÂ0¶ZãÓù¬ 1¶/ŸL¼îhdx4"šÄ“‰Àt L§@š2²L#ËTþýùgf¿Å‚€Ú¦†‚Dše¦ÙŒÖ¼rˆ>ÃÌêBdçMÊ\È,|ÀøõôÏVhojÙÙ¾•ú|¹-b^pæBê© ›‡Wh½Õ|“k¾¾ÙTh4un4ÚmâVKPf£vk|F û½žÚ—àƒ&c‰ÉXäù>!M¥€,Sù†Žæ.Ïp*cŠYÍfÖÝí¶¾çmo;x}>¦ûnÎßä> &cf3$ÊáZ@îT™8Êóôò4¯útn jæ{óËßQ4o ï&¶Àû¾Aà7š¦A³i5ß‚O…Ù¨Õšœ1{—LcÙ™L˜ÇcP<‘ˆcñ¯-à¶Qž¢^‚¢¶ ³¡4ÍXïšn÷òï°pl¿ñ?p×õ…Ó§i®®WP×ÚêfL–w c¶ªV¯·2÷r#ŽP*,†›†í4¿_çš_Þ-øÍ¦ 066†h<ŽŽLÙO˜G# þd"rð Id`4#ËÜ\Ç¢°T^)¤>ã¹ÔKZ+EµÛïãi騱“û^#8ï>#f6›e­,ÏœoËv%Ý;XK™)4•z~^O¨Yköm´oµžmªh„¡A£a5¾,í6¸Õ$ ÃlÜjÇçXCN‡¦‰lO&Ì£1‘cøøiJÈ2@k Ë4Ò4©¤½àf¶àeµA¦(ŒÙ‰3Œ(j¼âÚk¯; îxÏz8~Ü"~Ï=PJ™é¢ ÜœP”Ò:ÛìYpÅÊõŒÀñ ¹µ0E1Éù|W¿Ÿ÷ù¹éokþVó AA¨Æ­v|$¤]r˜h?ŸÔ­I5¾(7pHÉð}]‰ö­ß·à·Zü0Tãf+>g»I  ‰'“ÆZšÊf3O&Vó§±Dœ|Vû­ J$Ét¶pe½¹^ )îkà®eÖAJC­VëUDØ;‡<ݵL.‚03»µ’$»+IR&bQJäæ‚·rÔ_tö¸Ú¾Õ¬rsè" œóӳܒkÖì[Ó¯g{T?Ô¤Ù˜nö&¬IHR¿O€É„h:•°Ú_€oK¼¥8?C‘’V5»ÎQ”I²òúÖåY7Ío{Ë[.¿Üé׳^ìv*3¾ŸÙœ‚ìý—PÖ^'0„ªïÐ:A–e3XD U5¿x¿Û…(|~á÷m´?3û-ð5"5j5ãs, láQ Ž›ÒÔkÅÆ$ΫzQòû@’”È2ƒ$‰¡µÊ•ÔT~oüªK¨[…bž¡R˜åU½^óE°±qb¿ð?¿p×]á$ÙÚ1Fo×o¨P¥4rm-i­ÀtF »A©—Œ‹¦’jºW=ŽÞå\ëUÅçÛh_£F¼A¹˜Çqt M½fñÔ•t]Ð'f¾?Ëiª1æàƒòBf½«i¾Í­ìû-à\ÀÖD2lµšWÀñãÇ÷-Ø—ã®»Ò‡Ó4ùzÞqÃÕ=в\P”k”šæ±€sBPÍ" ó)„ úaÈ¥€Ñˆ Úma}~ ÆÍF¼ A‚ ƒµÀd­ÍÀÏ52±D¥y-øiJ˜Nâ8ΣzªY¦òªêmçf¶p„=®QvayD™bãyšÍÖuN[ˆö'8¯ßÒD|êSÜŸNÓ/(¥ØÛðY³H%Sçþ¦ÙsçC]—¯Ø}Q«¸‹ü­é/Š;†A³eò<_p³)( õ¨ÕŠÏ’$"؉¢ñ$ßúüx ›ï[+0 ıÁxœ`:æK¨ü|+û| È%᯻€âýZ1k@Ì`C˜L¢IêÙToðyˆ'¶´; ‡)F£qœ"Ëläo³“j­¢Z賕e³_Ÿ°Z¸géf,Rsy¹×²Ÿº?´ð~€ˆ~ó7ùÞ8Ž¿Ptð,*ØÔ[¹Š5ý˜Ò4F±JHù}ÅàdOÑÅã|»-¹Ýò(ŠÌ°Õ‰ÏØ‚#34x7d™ßˆcâÉDX†o"1™£‘Æ`˜a8LÇ*/òhMКa Í,Ù"³_æ$ê©ê"ap­ÔL0ˆ™^»Ù\=²/Èï—03¯¯ÛÏMÓÑNÓ4 7P|¯ÏŒIJÔ*Jf³hß¶iŸ%zšMƒf‹ÑhÝ®äv[PÔPýN{ü¸ û)’ãik-I½æ$f™Æ`4b ý~†áPa26H’‚êU y B(ß’vQp;/ eÁ­Z  +ÅPj–=Ö¾/ƒf³{®¿~Á}É®Ï C››ýÏ'Iú”"˜ê õÕ=kB0ãâYVP.ò8¿o£ýÂô7›ÝžävÛ£fSïv;ãGíBlÈL&áÁx*[ã±æÁ@Óph0Œ†£‘ÁdBy7¯˜u÷Xð1ËHŠ˜ÄTÀ,wÙ@~Yʤ֌,«ãËLD¡”ÁÁýÀh_àÄ {¥ú?”$É'óÎ]QvùEÛÁ.ÙR_¶Õ=Ȳ6£(̾­îD ƒf“ÑjÙˆ¿Ó•ÜiKj6Õn¯;y„’ÐZ`8lžÄ²5iyÖÉcS=üQÞÙdåfßÅU+ä®Ã]K!u—Unˆ­çóæËål«éyü ìG*¸/`Ý€ïÇ£Äqºíû­Å×ßWm sÇ‹@;À”GÇSØõ9Ÿmz×4h·5ÚmƒÞ’ÇK=ŸÚ-µ³²:zì™™0EG&¯5Û’îh$0Î÷ÑH`4¢<¶­+-̾õû@9--š]òkG• Xñ±€5ýeí§}dþ/…¸å–øoŒIþÒó,)dA-8—ÿ½Õ«·þ^‚HÁ˜)„ÐCÌL~«mÐn3ºK÷º>µÚjgeeò€0B0‚GãÖ‘xâµFcÃh4’†9øã1å¦ßxÒÈrð‹h¿ê¿ Í.€¯w+@—g. 5#IÜ{Š« ëB$%5Ÿ“pü8› ò¾úUNG£ñG&q–I!e^ô*™ÎújÞN0 ­²³v¤TbŠ P3³ßî0––sÍo«+ãoÁ‚ØhAƒAë²ÑØk†ÌƒAÞÀ9´»_ä=}øJqaò+ *U«U¿ x1í¼ÞSô:03Ò”sŸw-6h&0Søœw½ðÕ¯Nþß4KÿÌóBv@ÛÝ©Î8ð‹Y;B øQ¤† šMN—±¼ìñR/ nWo¯­M¾.„CÁ;ýÖe£‘× Í†CáHb<3Í·?r"Æ–y ›Š¿vAhQç(Ҽʽ k…+‡ªÓð$a(U_«¸8·$Å,xÎ €³7ßÌ£Ñ`üÁÉ$›J)=€L±¼[õ&ŠvœÜJž”k?àû„("´Zí6£ÙJÐëIîõ|êtÕöÚÉ×€µfg»}Åpèuû̓Óh(0Zð#Z`öÙ¶u™"Ú¯VËÀ»"Ta)J([ËGœ ÉkYf÷ÙgÍq$f£ŒÉΟ³Èþ<‹Nœ°Qì¯ÿúä?÷ûÉ[²ƒLÕ‚à–}µlo¢èû@Í&ÐjÝ.ce%ä¥%º]½upmrŸZ1+%xs»sÕhì- †wwAÃÄ` 0 ‡…æÛfNBšr)ÍC¥€Sí5¨šj®D󘿘ô²„˜´r@?‹J·¶Éƒa»Xô¾mû.ÌÌ7ÝD3§ÛÛ;ïÒÇ•ÞtJÚ¥Yea°S§ÜÝ3 žG Ñ ´Ú@·c°¼ñòrD½.o:8¾—¤&"6™’¼¹Ù¹z4ô–vw5÷ûLÃ@¿/1È…`<¦RG!ËÌ,ÏŸïFžß &Ãè‚”šÏ*£"Û8š$ÎÅfÃ)A5M`f£µzn Ü|3«õu’ý(ßÇÓZŸ)(Iˆ“Ħ]i"ra°B ¥Ó|F³IhµÝŽÁÊjƒWVrðÇ÷Hi`“dÎí\;{Ký¾æÝ¦AüpHÕü½e÷ÌÌ×Ï÷T[Ñê¬^½e­þv´ü8.Ž—­Hõ|€H°­M6Nvb®ó½]°[Ǻ´ðܹá¿ó}ÿ¦f3z¥Ö¤µ†4&Ïÿ37_ßÕô 4ŒNXYiòÊjHÝ%½yÉ%“»…ÐÒ6Z{bë\ûÚáØ[ôújÇÊÑúü·b¹y”3ð§ÓúœÅœ9,­c\wÆpÆœîÅÄ›ó½]°uiŽg³¾Nòãç3;;ýwÅq:ö¸ñÁƒ Z[Ó]suÿ R¦ªP<ühïÅ;ÛÞ­-ÅÛÛ&÷ù„ñˆÇ®~Ï¥®™´¨._fØ¢H•ǨSÁÖh͘LY T)îú’´õÀ¯\±eq)%ŒÉþîñǹ8±Hìóú‹¾Ò]¹ ‡ht:à~ÿK¿ßúWGŽ´škkÖ¥ß8úüÍ/xž xY<Ú¹q0vv2ÞÝÑ4йЩZåº}jnùy•¼)^/fsaöÁ Ò¹6Ê·4r’ _ôÁ‚é¢|·:˜uRÚ›c{ž)xžÏ ¤ôáû Ól†bsóܯœ8ñ…·33ïçBØØQ¹ývH€Ð÷ÑüËôGñÙoœþ?ÝžL2=ºõ9ß3’dY(~´û²þ®8°½•ñζ¦áÀ‚_ÌÒ­víT˶óû^”mY㠲Ǻ‹™î’kã²¾¾öŠÏ©rÕÕLëu' Œ”BL&‰î÷‡·Úévër¿À.4»|@Üx#| í(BO©ì0‘<øêWë{;½û±å³/µm(IBï‘Gºß6èÓÚÎvÊý]CÃåeÜ"Ò×Ú{‹n-_l{eUã÷ç×ÓÔú{S›ºé|¾+wÛØÂݾ®¨}íceW 8<ÄñäîÁàÜpêÔ©}# û || Ðаà 2ë#ʈK.9o…áÔ $ üGê¼bwG¬mm¦¼½­©?†CÊWä²5u¥ê3Š«^Uã«¿Îçî"RL>€ªŸ·¬c<¶š_ŸˆÊz_c«, JÇìq!$Ædñw~çÜÀãNöo»PT°È¿k¦ùY†eßÇ­å!)ù 1f…Ùtµö;RfY‡­‡î¾²¿+mo§Üïk >š*C©E{Q1fÈòß®‡Ï OåŒZ±Æ[Ãw¾¾"Ú³÷ØÏ(“>nQ¨zð—Gü( K)Ätš™é4¹•™“ÜüïëÚÁûhf¢-aÁ`5¿ `Å÷qPk¶à〮lJ™Åñ´<ð@ïUÛÛâð¹s omeÔïF#Â8/ãfi‘æ•§[Uý𢿘¥ë&b˜šŽ¹…+ÝgY­çY1GÐ"*¥”³ï­ö6Ú]ÀÝ)­œ !çy”eÉ×úýû?Xó¿ßÛ>Y€J¤ï¾@À2àø ¯c–è€e×z<6š÷ßßý®Ý]:²³5åÝ"›â¹©Ù ¥|-†ŠÖÏû÷2Q3ßÖµ˜ 13òF)û}¶k×Eø•O]xå3K æS>€ f¡lþˆ &£É­ïÿà´ßæØ?P¿ëó—”Âx ’W³ ~$¥O&ÍÖ}÷woÚÙ¦K·¶¦¼»«h<É‹99¹ã˜½¼êÕרvçΟ‡ZZÇl¿§è²Ñ¥Ýë 6§ùõÂV¡ý4£ mŠH Úó¤&§·vý}X__ûmþó.Õh%Ÿ` À*@êÕüï–Ö2ò}=šN›Í{îë}ÏÎ.ÛÚœòn?£‚Öµe\­0·(Tuðçýr1‘´zž{¿ÓXc8_ãù­âõ*¸Å÷`W_'7ïê‚P¡|]lÀBaŒF¿¿ûá÷¿ÿ±[76H\íΫÌEûuðW¬‰UÀôŒ-&ø~ÖÚí¯ÝÓ}ýÎ6.ßÜŒ¹ßOÉFùTš™S¯ÄU ]‡/z^íïìçºLÂu)\õóËŸW¦€«ç´/Èþ£z뛢™5Ï“r2™>röììL`žç¦÷e;ß.@¢ˆøÏïX°j V„0cÐÐZú~˜íŽÇíÖwö¾ogWlnNxw7¥É…¿W‹ç T;qж¬2ï^wî<§í|ä·rA%ˆ¬.@µø;‹ï™uòÀu6 QüÊÁ ,e`";U}<Ÿüà9Ïý/ŒöçMJ9Õü@ Öï÷¹lŒ^¡1ôÃtg<ò•öîìàÊÍÍ ïì$TnÑ.À/hÛjß×4uÞä;ðC¨Ty¢gÙmÌó{ƒ¿¨t,f¯4ðÌÌÃ-_¶ 2AÈñ8~ðÌ ëë´ï©ßy€9ŸïÁ=-ØtoIk,K©—„@@˜¦éîx¼Ü¹ãŽÆ›67³«67c5Y“O³`ϦxUJõ ª%Z·Ò˜~UTô j–÷ îö^»`QÏÕ@¯výZ!¹öSÞðIð¥$fá°ÿÛþ­áÝÀ…Õ~à @¥‹¡ ¾cùº–¤Ä’1h @©À ‚t{2YéÜ~{ë¿=w.¹êìÙïìLi:•ÐÚ‡1²~ÞLÁóß<Ÿï[kábû¼\(έ/^Ußö¿X“ÈýŽEZ1óý.Ò/×ûñ#„Paèy£Ñø¶‡þú ¯ýÀ3we¢g¾Rèy–t-ø"0Æ“ž—n zwÜÑø¡³gÓkÏžò`0¥8–H;°·y³)ª$MÞÃéÜ‚‹ œy///_^ ¢ è¢m¯óÊ·º­Nà¬Mn!P•æ-ÇEàçyãq<ÝÙÙü•?ú#~d}äÉ“|à›.[Ÿ×]×]7Çòõ¬ä~¿+J‘祃~µ÷ùÿÚú6Ïeמ>½kúýXØe×x–~9šUˆDr6¸eò¦˜ª]þEÕ¨|QÀV׿ú¶Hó«KÒU;x‹[Ð@ á‚<‚Þìe¶ ìÛ’o™À—b{{ûý¿ökÿ‹ý.ù>Ñö4,@ÅÞQß=¥Ð#B@KJJ…Òó’Í~ÿàòç>×|ËéÓéµ?>0;»±˜Æ¢6%« tš´)í-ÞŽD©‚]ÜD‚Ë}™ ·½Æ¸¾ÆOµ}Ì]v9í£Úñ²ÿ/2ç ¡* „7¿øÀÿ’›J¡MÿS€…N®í;ÍoÃj×óÐ±ÇØÏ²Pø~²µ½}pù¯þªõcgΤ/8}z`vvÆ"žŠÙü¢×®øÖâ~€*¿³˜˜5W7ar`Uí®úùEç”^)DÙÕ ªÞÜhÔf4ܾÝýÖ:ÿ/´ïKo<žìœ9óø;þôOùÑ òNždu1ÀßC /]}ÙÑ9’Çi~;Ëбèj& ¥1|?9»³sxéÖ[[?völú‚Óg¦¿‹i"sðMÅ”/®Èë]Çqì…Ÿ-ß ´^¥›ŽZoi}¾Eß»hTæ;yëAž#yê„/Ó4C¿¿½ñá>™ß‡Ì.¹\±(§y€`:E#ŠÐü¶ïgm@¶I ‘e>ùþôÌææ‘ÕÏÜÚýççÎN¯;sf`úý©°«mriµ­zÎ>?µÊ¾VOõLp´)ϰ)ø÷jôO% ÷r‹{/ç×1(À/\«ôÍ´ÌÄBxDd0™ å7sëß¶ÓïøÅDsAà\jgÏÀƒ‰•Y´Ášþ&lÎß‚µ~–EžïO·Ï»|íÓŸnÿËs›ÉõgNï˜~$Ò”fK¢DLzжg?<í×75 {¿Rʺ•‰Êó|9 íM"{žoÂЗÃáø÷ßÿþÇÿ'fž^¬¨¿¾-²eÍ—$ÁàÇ1ÂF!,Ø XAµ†”Zë†ñýøÑÇ{Þ%ŸþtûmÛÛÓëÏœé›á0ijoŸ^õ÷‹îŠZž]'P†}k–®ûûzë¶¿:dîXqsHÁž'M9 ?ö¥/ùIfž^l¿_ÞJ@e­wWåL¾ÌÀ² !|σ„-Ð%J…ìyñðžû¯»ü¶ÿýÜöö䆳gf8œ ›â‰|}Ýrïnÿ^½è΂á'yýio‹}}ñZÙÜ»Gzèh^@ä­]’¥ìûB‡“?¿ûî3ÿó_ýoolw±ý~yÛ+p=·{Ó)<"È0ù>8Ë •BêyH=&Ëd ÿ¹ÏèõñX¿äÌ™¡ãxן_Ke+`Ò C~t&U+pñÀ¯¦{e®ß¡v—R‚Hj!¤ð}ˆÁ`ôŸî½wë­7ßÌ›Ï6ð™ÌMUY‚ Ú¢&I`ï#Míz΀Òz¢|c¢GëÁƒËŒÁ?ÑZB)£–ÅêvÙt”´¯Rî~nT}¶øåà³È6ªé]‘‘ åyÒ#2è÷§úÚ×¶æÖ[yçÙ>ó\ªç´¿ìÜswœpÃ4V¨ím˜•üøÓòdþb£ýs{›t­˜Ù+V׬wï–˯å¥×J?”Χ`,Êíbm¢*ðÖ¬—;ê‘>Á.êàé ð¼4ÍÌt:yïïþîè3gÏVðÅPNûê{yTLm×Ì'>ù†7pBDÞ›ßþLù¿†²—¦ÆäwɨÍÊ-æÜ-š­SÞž©ìÛ×Í|yH™}GúXß/´žCAqœ™LFïøÐ‡â߀›n"ïæ›ŸàU(®ØnuGXl{O¹¥6]ŠóÃ?L¯m6ï ï;l…Î(cŒ˜ê‹3T›0öWê±@BTÎ-"ý½4&ïæõ˜I’ý—ápçðüÀV÷ž ©ÞŽHiÆê" ÷ ¹”̪¼»ãe¾2Èáf3<òGÄ«@)£´fXÖYºù˜fßùÍ^êžÇëm[6¨[DøÈH)…ï Ê2“eYöÓéðÿü½ßãÏVëŸ+àßt9øém–ó¶îáMo¢ƒŽ÷?zžüÑ Gm›fÃÌÓ"ÊvïEžÒeîy|¾g¯ÐzWsˆ‰ÈH)É÷…È2ƒ,ÓŸNÓäó7é}õ«œ–-Þ¾èyÜ.Øôðº5ø¡¢oiµ¼·âÍž‡KÙ½ÚÆ ' À3µ´ðX5èsÕ±Äyž/|_ä4¶þl–%ÿáܹìOÿâ/øðÜÓúÊ(\èßì–‘w‚°¾N×µZâ¿!’?"%Í×ǃ10Æ0ÂPFñéüæ:ÏÍÁ®MÕf»D ± !¥gÓ?­Ù(eîISõþGI>zóͼ o}+ù;;0{õñ=â",a·õu’×_v&s}®h4Ä?"â,%}«t ˜¸ 6¦@Ý6Ð<•J•ê Õã€%„ Î3_‚„ç ²+tAgo)Å·§©þó³g³¿¼å|€ÿ¢1B?ðÊ~=#Ý)bþï‹*(D¨ªf³>¯C‡ ¿» <ð¦T»å׿/ó}ñRÒ+|ß\F„K¤t½³–î‚LfpY ÊëòT‡•Û¨"²ÏÝ>cÈdL}o–™¿ÞÞ6}Ë-¸À@°²‚loÃݾ˜ÛÔŽUïiùÆ–z=h4Ä•­žçûèIÉ!è r•m€2fí‘xccLpb ³Ìl‡t_ãq­ÍÖÖ6ï¾} ¯ÝF†ðò¹f:…Qó\Þ¢öág"eP¥=+í)u>ûÏ·ø(F‚’”/×ê3Æó ¢BJHÏ' Ôh4Óã>ëÀø««^£Õ=ßç–ÖPR“R‡BÀÏ29JSo ”ΔÒÙtJÙ7¾¡’|0á~[«Ïž$0I2×uðT¨É'³uXdöËÖ ,î7O¤çkîà^AåkzÊ’ @T€ºö;ðëÚ_É¿òç•cíB† x €R`­ÁÓ)X©™P”Úƒgƒ\þ.@­dÚX$×lN»—A÷<°ZÌâóS|¾(N(¿¶üº;([€V’ó(O§¯æÉ™?T͈Â%”]€ Y€z-‹=äyÅs)Að`õÅ‚7».¢{Ô¬Ô¸OÖcÄOð÷^îà‰2€EÁá"7¢êÒóìòA?±ڄ·§ÀùÏ}`ñ׃¿²”ƒÀEǺ@Ô…c¯zÅ^KÞ,è-{Ê‚¿—é/[›§júë&¿z=T¨‚ûB>Á Ø¾`nµê§öC¨ ×Äì²ô¸È:S„—§ë¥ì§RÐz:Û"@÷2ç¦ôšy’]ïñü‚¥‚Nêƒäþ®¯·V“gô£¨^tß»½h¯x’ã{müÎ)Ÿ·(Š/Ÿcö8§.,‹¢‹Býÿä åÅR ýIEND®B`‚Slic3r-1.2.9/var/Slic3r_192px.png000066400000000000000000001026161254023100400162570ustar00rootroot00000000000000‰PNG  IHDRÀÀRÜlgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs  šœ€IDATxÚí½i¸lIU&ü®ØcfžáÎ÷EME5óP3B¶`«ˆ`Å h+¶¶Ã×íˆÝ¶ %( SƒÈ'ÐÒÒ¨(~*ƒ´ÌRXP5Ýùž“™{ŒX߈;ϹU·Æs ‰ç9'wîÜó^ñƻ޵‚ðr‡•W¼h[€رcíØq&Î<ó~|ÑEïÝä¾gœÑãûöíL²,禙iæ¤Sªè®½ö þïÿ…¹ürÔº›:Î{ßû |ñ‹¤é´B]ç$VW×¼f«ŸÎö(tÛñ——¼øÃ?¼ùíþp,?ô¡Å½²¬Ü½s粬X2¦[xDDéh´ãœ$ÉÏIÓ|¢Tª˜M¨F)®Ú¶™5MU%‰ªêúÄA­ÛÃDêÚõõƒ‡û¾Ò³Ùúu_ý*®ý›¿Á€þæ®å§~ xík·úÉmMùN¸Ê³Ÿ ¼÷½›ÿö”§`ÏùçÏÙ±ãŒ3½yyϽF£÷ɲü>@2NÓb¢”J(f€P*‘³AxM»eû›1=c¸íû¶ »®9Úuók›fvùlvèó]7¿¦®çǯ¼rþµ?û3ܸÙ5þüϧøò—{|èC[ý$ïÜò p+ËK_:†1sL&Kôº×­s i² £ŸýÙ³·gÏÙO1ÆìÏóÉùy>>7MG;””J ”˜oø´ÅþN¤ `Œ­ ò;30†A·¯ÝÏæZëÎ3¯ëcÿR×'¾\UÓ€cˆz‰ÿôŸFT×80Æ/ÿò|«ó^¾SnAyÕ«€ßû½Í{Ô£°ãÁ¦s÷í»Ï£'“]Y]Ýÿä²\^Èá}v­¹5pf30Ö$I8IrR*H>­¥P*ÝÅÇÛ%ÌlȘZkÓCëÞU é5챌Ñкç¦YûF×Õ×ÌçÇþùàÁËÿük_«¿ðÑnì^õª ÷¸G‡Ÿú©­~úwLùN8…òÒ—««§áþÏëëñ¬^xa~ÿîóÄ;NR’¤gÅäîiZ…/Úˆ¡2lKN¤$%IÆJeH’”’$ç$I€bûj q\KÀn=l0!4ü¶`{>f†Ö=÷} ­[ô}ËÌD¤äգѶóCu}âËÇŽ]ó±cÇ®ù»¯½û—~‡â{}å+íçïÿþV¿Û·|§ÜLyÉKRd™¦7¾ÑšXž£xÕ«v?p×®³ž:™ì|ÒÒÒÎ¥éx9I2MB oØMJ%œ¦²¬ $)8I2(•1W9ˆM³5bx£‡[FÏLl{‚x=ˆHEƒüzf°mý;bî¹ëÒºôB¶’Ô'ÖÖ}æÄ‰k?rõÕWÿ¯w¼ÿ*ÏâÕ¯ÞMÓé~Ó›¶ú­Ü~å;`“ò›¿ ÌfÁ¯ÿúß ÖÿôOïzòÝïþ /-í|lQLØ*yƒw0ǵÀLI’"MK”å2Ò´€Â`€õ~¸ÄƒsË#Ÿl›ø7TÛß vLØ1BÇ}ß ëjÒº‹ŽMèûš§ÓCŸ[[»îÇ]ùWïÿÙoDÿÇœðÆ7nõÛºmå; */x0çxãÛÁúg>÷|ðƒú²½{ÏzaY®î"R [cgŠ +¥$9åù„³¬@’d®Eg\‘–QË=„:`g´ÒD¶·'b0#êIxpLÈÎàÁ¾r:²…™ÆhÒºá¶C뎉”²•¤EÓÌ=zÍÿ{Ýu_zïßÈŸàÐ+_™@kK.Ùê·wëÊw*@T^úR…7¿Ùøï¯~õž øw/\ZÚõÔÑhÇ9Y–ƒYÃãa†1š™ ’$£<sž‘$¹ƒ¶'ÆÍ‘QÚΘ­±*%Ë‚õÅ  Ì,¶ øÊ#i³JC ø¼íäà“A×ÕhÛ ÆôvÀáèØº^?~üøõ¹êªÏýá;ÞÁŸžÏQÀ‹_ üÑmõÛ»uåß|xùËS<á ¯À³žõz¿îE/Â}Î?ÿÑ/]]=ðÃe¹²?I”ÇôÒ¢£Ál¦ŠbY6†epà¶•£˜²YâßÂ÷xÝÄþ—ìr Ÿ6–á0Âö(Ýà×pŒ“‚@w躊û¾&€ T fÆlvô[kk7üÅ•Wþó½ùÍøG9ËG>ò øÈG^ƒ?øƒ­~«§^þÍV€¼8qøàúïùìÄ#pÑÞ½ç¼t2ÙyÏ4MÁ¬1ÚoÜa"…¢X¦¢˜°¥'ÙÁ•ÍZâÐâÆƒZÛroÈ¡Ž]&vc…(eÐÕ ™¢× sÜ{ÐBO¥Ä·A}ß mg TJÆÌfG¯¸îº/\òéO_Ù_þ%®“çxñÅÀú:ðþ÷oõ[¾ùòo®üâ/¿ñÀþ(ðŽw°hrò_ø…{ëùe¹ä,@Ã< »Ó÷=”Rw¢(&,‡c`n*g‰YöÌL`vâ«ä Ëñq‡ÐŠ<„quÄ,1ådÌ„!Ü‘eÚpÌá½g¬ß`†¾o™ˆÈ*8u½6=|øÊK¿ô¥/¾þ½ïÅ—å¼?ú£Àh¼ùÍ[ýö7/ÿf*À‹^ìÛw~ó7¯ò÷þK¿tî‹O;í>ÿy2Ù}^’¤`îs>‘kùÙêm:J’œ'“=HÓœ˜µgQ¤¥.ß¿ke9ÔÚõaømÁlÒº‡Þ#Ô Ô!b†‡IˆZ}a~† R¼o€U'cŠà{3åz»¾ïêº –ùJ TJZk;vݧ¯¾ú³¯{ýëgﻉŸø‰ ªjv«{‚˜Ê¬D©[w¬Åòo¦üØ%xÓ›l‹ý¼çá^zÐwýÚΧ?+Ï'°b2íXùcgèy>¡Éd'%pãöĬN ;‡Æ'pdp‹Q¥BTyb£ŒÙ!ÁýÑ ›a} âßngœ½D‰` û1ƒ½6ÙF‘1=7Í ÆôŽHÈö'Ö®¿þ«oùÊW®xã>`iÏ>ðö·ŸüýÓÐØãßå·Åßã”n¡E[W€—¿PªÄÞPûu¯~õ8óÌýÒŽ§=D©DÔ”ëkÍŽÀ›ñxŠbÉ1&‹Žªö áŠ,Ûõµb1"ˆS̱M^€Eþˆ×,@•j-B›“3D´éï¾ßbZØ^9’‚1Öwв“yR)ú¾ÁÑ£×üÕ•W~ê7.¹„ÿ*¼“M£ñÖ·Úïbø‹-¼TÙ&Æc™x}ÜØÞøÔ{ˆÛ©#Ùž…hú«¿úà_¼×½óŽ;ïþlLǶõ·¢2gø¬uÏD„Éd7Å2;ÊÓÓÃqÿ5˜ˆÙÃ8Ÿ—loØv˚ŇàþÀÜ»ãȘC;ÈÅþør^'€ãHëã)'{]ÆõJb¯´ÛJ®£õÆÍhÍá¼m££eÃÌšET€‹b‚,+œDCëÎ$Iн{ÏýîóÏÿî7¾üåêò.¹Dc4J‚ª`ì̃žÀ1o¾v’­ÖŒ û[)ù-°‘­6Ò;ª<ýéÀ‡?l—¿ïûpúÃvá¯8pÞ‹Êr­{1P0kŠŒŒÑœ¦Æã]”¦…g„éco«PŸ`° KpBŒÎZoqhŠc(øã¸ÞÂäzhá8CçW€I›Q¨]soX8G ëá!“RŠâ®«Ñu ;E+)•`6;zè[ßúüï}õ«×¾9ŽGXTrˆ³#çØ“tÃâ¶ó]]´ßÒ±Á·]xéKÑh'~÷wžòœó„'<ê÷öï?ï{“$sç1/sïeÉ{Ò4Çd²J¥Ë#7yÐf°^XI ÝöI`q‘ýh°ßÆíá÷Ö±EçY|VYˆ´ärM®®Ëõ¨è¼¢#Š·ß…%êû}_»ßHÔuóþÚk¿ô¶¿ýÛËåŸÀµpÅDgŸÍ¸½;4ytG›=Çßÿy>ÝTIï0KÜ¢2ïÁë^wð?€û<âyÝþýçý{¥ŒiÙDÀccL4-h4ÚÉÖÚS,làõõP³:2X\¼Úe!¶7p†ÌÍõ0a  ¶j‹˜ø¤鄉Zki9:ïf½Ðp{x9G½(w‰¡÷@4˜&·žœLø^H!IRrîûΩ´6&ËFéÝïþ€—<þñI9™|õWþ×ÿÂçžË|ô(ÔÎCXç+†5Bãâ?L·¨|[õßõ]Àßü]~á ñðûÞ÷ ¿·wï9ÁáU#FMÌÚ¿x { ŒF«”$©ÃüLJ%¬TK5Z]ý¢tÞ«ÊŒ ˆ×/BŽ“(E¯¡¤9l?Ü×Vò˯m3¯p ì1J áEǧ|Eµ½„œ— TBÌìb:Wá)N’”ŒÑ8xðëÿûŠ+>÷Ëoy >Åœ¥mÛ%yÅ E4øtW`á Mà CÀ¸õf¡78e6èÛ¢¸øb`ii¯ý:àÙÏÆð€'^²k×Y ´Ö~°ë*1¦CšT–ËPJ9½<£,—QË”$™;‹¦ºžb:=lß»w"1E/Š"¸CvŒa·‰©¸¥Š0.QÄE¯O(o€ö3Ù†Ð~´> #ì:-<_þ˜a\â5BA#Š4C$Lè!ˆõÈ b‰,Ãf¤'Ó³RЏ×S“„Ê—½üŸþ3Ð}.ÏUaŒÉˆÖøiíuD“nΑ±{H'ù}³’œê†Û¹<úÑ+ø½ß³ÆÿÃ?Œû>ìaO|ÓîÝg> `:hE„ã7öäT–+l…^v<™ìÂx¼“ˆ”k5 à,+Á¬©ëfî‡,üàh{q ObúA‰,†gü Ö¶š‹~9Ž5渵†çû7ƒa1¼a¿¯{tn}P­Æôp=Ò{ÄËøõ¶· Ëð=CØÞÁ3 +Z^ÞuöÒd|÷OüÍu—?æ1<%¢Œ3rl㜸Já@îž —ÄØ£Ÿˆ€_ù•S³o+ôìgãþ^øø7íÙsÎ#­ñwÎø™- lÖ§i޲´° hÝQQLxyùÀta3ú¾¥'®ectäa Ûø÷D9Y”+ z©<1ÎިwÙ&-Â^0úx9†Fr͆]#ì ™l£¢Ê,c" qL¹qQ"CZ·nûžf¥%©â¢((Ë2Ì¦ßøó»>ùߟþ4\`¼ÀühؼG=3z"´ò^¬5`™ Sõße!ÐË^$É?õSŸÀ=ïy!žô$œý‡<öõ»wŸõH@ǃ\¡:!Úç¹$ëö­4FžOœeIKëYÄ̰c=†Šö…3 ïÄŠ`“°CÞx_ØWZÚyVÉùbIX,À1P~9r¹ë—e@/»¬ýöaCLêáœxÑõh cU«=µ•@“ÍPáGÖ’•Rd¸@¤)I€Oða<å)Øû¸Ç=ìuû÷ßóû-D騾ÃÒòËØy;‘çKÞO”€¹àÙZâð\íö=Ö×op©H$iUàÿ-.Wh¥QËï}^îãïöð­5®áæŠÈ*¤†ðàXá6Æ"¥Ãó†Ö=æÿCªî3é5¤’„¡ƒ"OQ9§)SšÞø©Ç×_þ\923ch§­üÐÌâ”ÖöÓã·Yð·ÜâÖ¸ V€¼Àb»W¾Òºy÷¸ ~åÀóž¥T ­Û“ÀmŸ¦é)ËFHÓZk²Æ@XDc4šfÍA#×лJbèøñkÑ4ëH’ÔS¼µâü BÖ\àGÌé´°@v½ÝÞ‹ã¢õÞ8PG–Ý`Öm.Ћ\O±Èm„IáÒ˜,Óã{Ø#N2ËYŸ…¡$!Ø}4ÙJÂP ”¦„4%.Ë„²”¨#ŸzØÃÿþ—ˑ陱W)Û›¸Kí`[viá5à+Bï–ÔQ>±œâ–xƒïRàyÏvìXÁë^·øÏÿùn/Ú»÷œ—¥iоoY^¸ÕÝÄ2+iV*å,ÃíyV)N¦Ùì ãñNR*a€ÐusZ_¿«ê”JHkËØøˆr["$iKâe»M¬Ü´»›Á¾Õæ$ÇßE‡W`‡ì‰„) þ_Ä×aCødïl¨xu …»í!P€‘ €!¥œSL*!βe™Q’0ÒäàgþðüµÕÕº3{•‚rЦsçc×Z"tÌv xþ@¨·¦å—r—ažóàÐ!àã·ßê§öü‡óÎ{Ô;'“]»lÂ'ã^‚vN®!î˜Æã] X®Ÿˆ8IRgÆ¿lYN’ŒÒ4gfF×UdLÏÒ’-=;’‚3‹£1@ðòÆûF­ü¦ÇôhÍÍÙ!¡bŽ-:Ž´òN±dÂØ&h†tçÐqÆŽùÙxÌÌV̦¢1 H)p–Ò”)ÏOJNTšùÜcýÉÿZ–¨Áªœß  fFE„9€¹ûÞ8ö§w1<ºEN¯ÍÊ]¦¸ì²°üÔ§âôÓO¿ß/L&;wI>8–Ù`rQ]0¦'c4ÊrDŠ´î¥Ñø€` £ïô}MÌF¤¿Ñ6B,ÐvŽ ñTå&¯!̈=aqÃ,; 3#,MЫÁ10žæ\`Ÿ|K9¶üÑÛáP|ã`…¦è&$X¢ÄT €í­ ÈVD¥ò<åÑ(£,eʲ#ÿ|ÁŸúe‰ŒÝJA¹“ŠQkØJÐ2££'B/0a.øVB)w9ô3Ÿ–õ¨ü쎧=:È”!éI\wn—E²›e%'I†¾oYèQÇüH+ÇœDY³èràÎÐXŸX6,¿¸Ê![úmEí64Þ=&ú&=³8Ƀ$'ºiÜñý‘ý1I˜ èù˜Á½Ø}?f=‹lÛøçž³Ü‹f ¡gE+Õ#Ë çsY*Ê2ƒ¦%;ÙDæ˜!‘@³Ky"½ "a:¬ia@Žøs…ãx£ ÐZ·`O1Šæ&:&“À&‡‹Hä‚“(ÂL®‡„ÙqÇ €ºxáš=ŸŽä%Úñ1å:å™ý6ÊÑ€¨'¥ %I$é)Ë %ñhœQžCÊc_»à‚/½¾,u {R ÐÞTîO¨ÎÚ¿ ~E Ï …Û ÷ÇeÛV€W¼øß¹ð²—ùÃ++ûž`Šxwm€¹Åþš%X›YsšÆ&°"ưÇä0n öèuŽö•Ž84­Â›8#ZÂ,9ñšg/Øÿgw.TŽz“˜äqû’óxJ‚ˆ¯“#æ'>&G× ì9ÖÈx)Þ>~n–™2PʰRš“D#Ï™Ge“qNY”åñ¯=ô¡_~ãdÒvÌØ u•ÔÀÊZfÏñÏá*BdüÂø`@yŠÑŸ²:ä˶ôý™Ø³ç\<õ©8m÷îs^ž¦%´î]^Õ%ÓÿXv¦÷ŸJ%HÓ¾þàq6ìZ~JÃIÒ#I åsYÆ£Œ²˜LÖ¿þà_þæ²ì{fìt †sgø®õŸ!ÀqxIËøfvj±ãëö.Û²H9ë¬ûþèx¼ó¾’0J¨:qrYG—ôšvKàÍpFÆà¨²~кØ§<‘V5já Ú/H›£$Ú_¼ý!3œOJ 6–ØNíwcâßâë–ãÉ÷ᵄ{·ûYg€?a;í\ŽâD’h¤™Fž ìI9ωÊrýЇ>ôkoÊ2Ó8¶Gœ[=ìàv wÖ`Áž–­ÎGèNãƒ7Écº# _ʶª]”%èÍo?å)8°´´ëI’˜žcØ#x0¤up~1k$ÉcìK×?Y#2ÆÀ¬•" ~–Èóÿˆ~CÙÆñ£Ä›Â¤ÀÐ TsÑ ï_Ê9ÐE‹‚Ð}NØÞ##‡S¨4±¾ÈXù7 HJƒ$ÕÈRCy”¥âÉ$£¢T4¯}ãøÆ[³ÌôÌX‰àKãøü)æÌ˜:ÊsÎì¹~Áü./å Í ˜CX#pû²>‹e[U€É$Á%—h€?øŒçO&»à`…¬’>Ðø,ÍÖÃÛ3‘Bšæé}¢¬oì$Ððp(`]f+’”@ï° Ñp9Ž®rÚ÷úR¸|^ȸ&|Ý^ygð¢ÿ!¬Ð'u±2R`7vwh“1±ß@ªVàþ•ë˜&¥ ;ieóh¤0™ä”e ãñú•|à7Þ^}'Æï`O‡aË?°¾ î¬ôÑNö<àùÛOæp*e[U€K.±¬ÌÅãáûö÷ª,+Ð÷½3vfë+->Ü@Ï zdÙljÉókl‚öNëc[}DÐQ]1¼9ù5Kts…†è«p,CC†Æ ô –½²;±Í‡k ˜xa†.^Ã,ϸA¯q°ÇA§ëÉrƨTX^Î9MGëW>è!_K–rÍŒ ,|ÙÂŒÈ>3֬ůOd•žÎ#¬™7WwÞ™eÛøþË —röÙzîd²ãt'Îò^Z™pZÂÝàW"¾(M3ñPèæ9r–Á;wìñ<<ж—}8GR4€õù ‚#‰Îe"'9íùm¾œ7œK¶§è|9 dGXäÅõç¯'ÞFê¨ rb;²òeƒ$Ñ”¦š²¬Gžk* Cã±âÉRNYJjirâÊû?ðïÌRnÙ±³Ãðkõ…æ\w3§WÍ‘¸ „>:º5TâÆÝ¡eÛôÿý¿Û;~ñ‹qååON’ÌӘ⼒™Ó­1Kà‹†1=)¥¼®&vûöà ØfÃZJ¥d¥È úDÖHÌÌÄRòò -DŸã‡¾²ÕÔâl2‰†8‰†9£æ¨BùãÀðXÛYìµùLÒDdضú†”2œ$²LSž“ƒ=¥)0ž¬]y¿û_ù®¢Ð­ÖXR !^wŽ`øk°ðgVâàÙ×ê÷î‰Dƒ“Ð3Ý\ÿM•-ëžüäÍ×ïÝ{æó|r¾ÀŠñºÈy5ìàW$Ê=ìT= ŒáHæ»(=É4GhÁ’X Ô¢£(†‹)&Žšuë”DC¸„…c²?Ôù]$Ê‘oC6ˆ¶7þ81²Ž-¶Ž­T#M{äy¢ÔKKÀòJÆEA˜,­]õÀ]ñ΢ЕÖ¹³ij¶FÀqÇgÆ Ó…A¯¶ nTZ}q~ÛC g>ø‰Ÿ¸>ö1ûýÁ½ìeÏôÝýh´úïÓ´€h~œ÷–ƒ£#­ŽöN1×’;¤½æÇeióz˜°dŒ0JäãZõBxù/Ic,ëeàoƒaE¶e—­À-kôäu8v?ç™?¾;¶Èš½VM‘ŸC˜BÏe—!wÍ2Hw,1Y1›F’0%iï`OOeÁ4ž(^ZʨȡƓµ«ïw¿«Þ›¥è´ÆÄYMï<¹Sr€'àZÌ/N®.¯§`~¢¡“+ž!æÎ,wz(Ëxýë¿xêS‘¼ï}ŸN_õªç<ÿù8o2Ùõ—­¤–&Çý;Іh{€(aé1¤MÅ@ d iœœaÙá¯0/hi"bÜGöµÇ Œ?¦oË£e‡×9Æo¾}|œá²\[ˆÌdT£»!Ö?¸ša¹,,…=†“„‘¦š³Ls–kóxL¼4É(Ë£ÑúÕ÷¹ÏUï/ Ý‹ñ“BG„ Œ)ì`w À3ÖÝàwSÌ^Ü6Èè˘ïÆg³r§Ž~臀w¾ó8àéOGñ;¿ó™äž÷|hûô§“€}ûö>¾,—ÏØb5<1Ñn€"¿¬ãFA)rαÐ˲×áÀ'†*öáÛ¨1¥2¿}¬½‰xz_‚C ˆT°è³ñÁ/.á;E÷ÖÅE Z$‘ˆ˜'Ž&:vˆÚ’(8¥DYfÛÓ!Ï5ŠÂ ,&“ Y”£é·î}ï+ß—g\kY?'`5b¬p¬êŒ=¼þ™Ý‘Úž[Zî” ð?ìØ±„·½m xìc1~Ík>>¾ç=:#¢^®euõôLJÁ¯LNrûØïÚùÉô¬TDQNã'Xp õ3Æôäæ·8¡‚~îøp4«8T´ œe.Ù$±‘ñ‹„Ùíë©V‘Bûí«8#¢½b'šlï!“Ý‘„12Pʸè- {²Ì–i* ƒÑˆxºçxâ­ÅÛíÞ½ÿY6:€L6çiµÃ²°öÓæðgH*÷¨=t gȲÄðÆfh‘$¹ÛÖÀö Ãi€)»˜ r•e¡uc× @T– ÐÇ~ R`‡†Ž3Dç‰ï/f¥0€D$έ„¡È:·ÒTt==ÊÒ , “¥Ôžrý†óλæÏòÌÔZ£„KS¢ÖÙ¶öÇ™qÀq"/r«‰¼¢SËeÅz¹‡íbøRî° ðä'gœQâ-o±óE=úÑXúÿãOw>ðOÑaû@¼÷4[ZÚý”4Mòàé• BVgi½j|š¾ˆ•À$ô6@£Í&•°´Ö=ˆÙI³½œQË 8<“IÞHI¼µÆe†häÎã·ŒDŸïà“g„ÜÙ6§ˆ‰’Ê)í«°=ŽZd{I¢)Ë4²¬G–õT–e©xà]ï²ÆÿèG#}ÍkÞž^xá÷ψhf_ Æ÷ÿÈo¿{÷™ÉóÑ݃ãÇQ7Ãë¬ÒZ#I\¤¶ßˆaO8^줒An GD»c³Â%  TA7èuǰŸ1$Š]ùòò%³œÀûpafÅø:ãcÇÇ—Iµãßb§–KKˆ éÑHSv°§Gžkäy‡¢` {& ÒŒQkÏ=÷ºÿYØSÀ2=5€µo|ãÜGÝpÃÎGhýÕK/¸`öµñÇ´ÆK’4†n`ì^CÇdÐC¤Õ±bºà =EánÐqBõ}ãêc‡”g[Áéî¸n™"ÍOZÇ;‘ŸD¬…k“ivåÒ¢¹g]ñ!Â.)”Òä4=HSMyÞSQôÈóŽÊÒÐdOl–æÑxýƳϹþãYf:m0vu²S '®¼êî;tønßcÌÞ'NÜ÷™þ0 `=IÔºÖžíñ°Ñ`—ùöÍÜpG–;ä?úQûùÅ/2?îqÏ퉨à¬Vƒ_×¾óIËråI’KËÎq Aqî¬‘Ô…ØØ™äΡïg–ìqœÜ,:¾ŸgË;Åú¾acZÁ4g™Ý‡üu"ºÊa@=Eë!Á§^ô[Q|¿ˆœhCYìØ"ŸvŽ- ËðÎ2ƒ,ë9Ëz.òeix<&ÊsÆx´~ð¬3oøDœ\*Qh”ÂÚ7¯¼ûÃ<ã{û.C]wL´g_]_pñë_f=IŠyßZ~#r†ÛšªðÎ.wØe¾÷½¿åX’¤–ƒßÓÔV€½ûG£Õû9¾ZŸYÞBj>IáœdÆÃ8%b7Ød¯A~óü9äÚ6Â&»_×5кö°kq›¡³jx¬À\â0³ãÑç ÷±Ë¢Å×  vã'Eni¹eœŒ¹Gžw(Ë£Q‡ñÄ`i ˜,Š‚1™¬:ëìëþº,LÇÆNKD–ñ™~ã›g<êàÁÓŸÚ¶9æsæùœQ׌¢8ãÞÀ?û ¿€5u–¥’®ÜÞÝ6Åø7Wî° ðÌgþŒ_–YD†¡€=@žc/EÆî¡……>þ×çÿ )üØêkéjÁŽ Bœ',GQUnÙ³@Ô÷º®öšÇÞH"?=ª_`L€Ocâ}‡×C'|@#9¾Û$3[’ô”¦òtT=ʲ£ÑHÓhÌ<+Ês¦ÑhýðgüdžÁ°Õó ì™^}õ‡=rÚwa>×\UŒ¦Qh[Å}¯°¼|öÃÇãûýô3ž»õæ¯þ tôh¼ÜU`O\î°ËUÑ“P›<•ñx`ii÷™IRìŠ21DpÅË ½f)¥üzÏ "ÞÆÈÔ›Çøèöž]¶9‚ŒÑܶkÝŽ)§v_X–4pȱ,_'âë—Ý1€j²/ØЙmÔ–A’X=èj. ÍEÑ¡,5Æàr¤,ì¯9㌃_ä†Á3H)ÔJavõ5ûzäÈïêúu ®kPÓ]§¨ïõ=ȱ{÷y?xÆ{.<ñ‰0»w¯xÜW3~` åÐE± H’ü,7Û"bçN,÷]t| Ì:Æ$ÇÏÐ1´èDg”ü&0&@& dIý@¾¯,ê\`9¢k¦Áù΄uá~Df˜«}dÛp]yŸRVÏ“¦Âï[£oQ–-Æcñ„1žÊÒ`2Y?ræÿ¿<3½1(a+y  ºæ[.8|xߣš&A5×<Ÿ´mÂm«Ðu@×µiÝ¡(Ft晼òÅ/N\Âú5<ýéÀK^²U–tÛÊ–L”ý“? üÖo€äIO:íå“É® .œìÙPh7£=sbç¥ÊÈ 5…|:<`‡\¼+ࣴpÒe gÂúH±:†¬~ÈÊ$\ ß>¸"݃×ýÀ';tÛRÄÿ s€IJÙù¶làŠF’Ê2 {²¬§¢ÐT=ŠBSYjÉÞñxvøô»ùtšrï1?¡%ÂôºëwßïÈ‘ïÚ ³™áùœÐ4)u]J]—Q׌Qd½ÙMÅd©(ÆgîØñ­úêWqÃå—ŸûÜVXÒm/[R>õ)ûùìgãîgŸ}ÞÏäùd_ÈùãÜMÎ Î/í¼À>H†˜5'I†4Í}À;|ÐzœÙbmhi¼T“CÄ–°,pú'vH¹xÇ}'GrÉ1ÝÐ'rZ ç#ñžct‚“K‚ܬ‚“Ž­,Ól1¿¦,3\3~ã10+Ê3`<™¾Ûi‡?›¥l¤åW -æ7ܰ뾇컰ë T3æyÅT×)Ú6¡®KYë}O¤5ØUqêÑh´zzQ˜ñW¾røãó9Úç>xÆ3€O|b+,êÖ—-Em£Vˆh²¨k ¬H˜Ð˜Xê̸bK ¡âïñ„‰B€ü"‹c E×!Ç‚_–Oc4º®BÛÎÐ÷µgªÂïÃ{ Ë1+µYpº…=ŽÚô Åù6z«,5Š¢ÃhÔb4ê=ì™LeÁXZš=ýn‡þ)2~åÒ—Ô×_¿ç~‡ï¹ kÌgšgsƒª"nBÓÚ–Ñ÷™Rë²iV*Áþýç=ï™Ï\úax÷»/y+­éÖ•-­««ãÝDÙjdøÞÑÏä #¹bVDœb"Ç™8³Sï C†0#ì‹>Ñ`a’‰ÁuDÆhô}Cm;£¾¯ uK6µøbкqlW®.,Ç[ÂðØ@õ,‹ƒÖ{²Î­Àö”cæÑˆì™;°ÿÈÿM`cXã×D¨®¿qǽÙñÀ®W¨*æùÜ ®‰Ú–¨mu¡ïAZ²¹ø- zd&czäù˜Î8ãþ/ú¾ïÃ}àýï^ô¢­´¨[^¶´(¥VSØoNFÂÂ̈ÓJ# «w<Ù±3 ¬ñL‘ûÝÏot6ù¡h>/xz&D“ £‘ÇlÀØX¶ \îûm[qÛιëj7¯–ŸQ‘ܱY—Ýœ]¤,Ô1l±~ÏiÚKŽ.Šžm˯¹,{¶t§árÄ<ölÏôØþýGþY%LZcÌ ¥\ãWïuôÈŽô=¡ž®*CMCèZ…¦!n[æ¾´fÖšÙMFÁZç¹úÎw{ø}ï{îˉPÀ™g>x+Mê—-Hyä#ˬ¬ìÿ¡$ÉÓxN®a",&æ^â)°>Vñ©T‚,Ë)‚39¨¢ìlq67¦ðU$šÊm³qû8x&¬·c†X²Ä_>Å ŒÑdLûד8Ôd$,“I±Q]$¡èºü<™¡<ïQEÑ Ïbd¨,ã±Bž3MÆócû÷ýB’€Õö3þúƃ«÷:rdÇ}».A=gžÏù\¡®34mJumÐu Z+2F!JÚåïQî]dâišQQLοÇ=®»ê3Ÿé¾ð‰Oܰ•&u‹ËÞüò/‡å¥¥Ý»’$-\’+QR0d‘AcÁ ¶æö ŒýD˜–á$ö8Pô º¯ºôëƒÑ’w`¹AëÀé&ç…7Ëhõ}‹®›QÛΨmgèºi]s¥zr:¤©¦45”çynÈæèÑ(KMeiaÏhÔS9241'Dynh<žß³çè—Ýc;Q„æÐ¡ås]ùw}OhjÃóŠQU ¦èô=Qß3iÍÐÆ1’zRîQû{± †Æôf4Z]=pàÞÏèC±°)._úÒ;Û²n]ÙÒ  T¾bóô{Y3ì° v T@^±12)F€"ì'}MQ´›n=;ý‘@¯ØŸE~™C@ 9¾£5Y‚ßí5HÔ–=—u‘»¶Ì-S±Ö´ž²1SfžyÆD3VjŽ$©8ËÎóyÞ¸åYÞrQj.K¦,1æ'vï:þ•D´¥:•RVÖ|øðÒ¹G-ÿ»¾'T•áºfªkB]+´-qÛ‚-ßϬ5¸ï5Œfv“ˆÀíÞ ¦I ”ôާ=ö¡ýxÇ;‚ r»—-…@OxÂÞ‹F£Õ‡Úoþ ¸$Ë›°%V+Ñ£iZ…0‹ùîm²Úhb9)vñ!¸%Ž­ A,²« Žü䡎]ïgQwY—‰’NÃÃä24¸ìl½ VïÈÆí6Ȳ–ò¼EæéNÆxdgc/GÍÚÞ='þ%µq<%Z)´‡.ŸsìØò¹}Ÿ`>g®+F5W¨ªu­¨i´m‚¶eê:­ Áb›]4œ@‚ÓÊýeY‘äy±o4ºö—_ŽcÿüÏ[iY§^¶rœ0cYh'£0©CÌœØ.Øy^ÒbÛM÷~wlŸ{Gœb¢íšˆˆ)-°4²½ÝÆM(ç¡sÐ ÅÇ·;4B!PαeÈN/j\Њ!Ks*Kã`OO£‘¡ÑÈ ( FL£± `OsP9j×÷ìY»%3»`uËó³›vÔ ( R·œEÎ( BY’Ȳ¶Z]_m“¸Ã¦Ã#t¤ ×ÖŠÓ××&´&4æª&45qS“‡=nÐ [ }o5>}ß¹û!— )D§ÅIkí¤"ò,uþH.Ê n,0ʸ×sŸô¤ÏýùÅ¿u[{ƶ)•îv9}©7#GXÐÞÀå|Ša’8§˜ÙioØiWbø4œ =–LÃy9…½‰¡‘0BÂ&ù¾À/û¼Eƒ|9ŽèyÜܺ$Tg– ÛÃÈ2& } òÂPž[æ'Ï 9SQ…¢4%Êó®ZYžKŒß©LM¢Ð¯O‹ëëãZêÆpUMCÔ4Šš&!ÕEŽñA×´¶¬šÖ=i­Ý}…lÛAC¥}ð~ÌÎ  „$®ayyï…÷¾÷Òãä]ÿøo…yß|ÙjžÊE±øÁ'Í;(â~Ú$û±°.`eÉ‹F ü²s„íàØ$Éê,sµ,²=öl=j{L4£z`{”b¶t'aOn8/´ƒ@Ú1?Ìe .KEYJȲ®^Y®®M0€ÜkŒ"tÓY±:КжÌMÃÔ‰¶§%{ÖÙ%:°vÓ«õ}ÏZ§¡Ó-Eˆ½–I»Ö±ÖßaczÅhyÏž³ž ‡2®¹¦ØbSÛ¼lqЇB ;ä% f[â?ŽðËÀ§KpZÃÌÈ1œ ð)–# dñÎ/Ï™f!’KÎ;d–Â1ìlðA×Ãâè‚£:=Ôqøe)ß Š‚QŒQ ”e‚,ò¼­WWæßJffä°=š!B?û×§£ýbüumÐ6àFp£Ð¶„¦±ð§ï }$ä}ߣï{Ý’Ü“Fœ’Ò»[oçc“ç0Ôpy…®Óh-=â¹ÏÅyð¡5[kj')[Iƒ¢®×ö}‡\6Î¥3\1õçÕ­;2¦óÛã3)Ÿ4ø]m`n÷]”@ûÖ£Àö1láÀPb-,öd™!;Û¢ql!gð”v9Ï™ŠÂžgÈ2I:DL'j68ÔÄÈ˾ŸB6f {ÄÉåèNÊ2CY®‘g†²Œ)Ï­¶Ç p^(JRP–öÍòr}½ÅÝÈàb“•B?Ÿ»«ªØm ¡í˜ÛÖ²=]§È±<¨®¶%tÈ1>0†ÈËüt]MZ÷ š+ÏäL;Ø3㟃8ãô•·áôRÏ^c4šX]ÝûصëœmçÛÒ  5Ú¾o«¨•÷hƈóË­‹œ\ˆ a;¨ ²jc‚×ÿb— άØ¿M ‚þ.ÂËßFrv© áÒ²Óôo€=yngö…¢$!DÆÏÎøAc1±«ªóÝvÀ îX¯n§œ“K±5zÅm î{›Î¤ï™]`c˜Û¶FßkïÎsé$ýóŒa¡Ë4x!,¼ ÁýD1d²½…R)F£Õ@½æ5—óŽ[iqË–T€§?Ý~?ŽkÛ¶½6¤\LQ8,‚w‚Åhœ™`C{H¥²\w [†0*vÀÉ”V!/P|²Ý¿…@!¿0>ìv&@žR˜ (E®¦@ž÷Íòr}l$gæÄ&IÐWU¾c6/vk­ÐuÌmËhZ²y{œs«kºÎ«ï±Îøa £mkh‹û}&éúÈXÌ–ÅÉH6ꢆh¨çÿ‰R––v=æ9ÏÁ¹ð‡¸wò²%à‚ ìçŸþ)ŽuÝüjÁ‘¬ ó"ì±ËöáºL%QÎ¥@Ì=´î")²qÃI|C*ÂiØsè܉ç):W€Rò0‰®ßåâ:¹r˹VŸòŒ)ÏÁyî`OÖ×ËKõ µüÌ0JY㯪r3Ðw†¶§®SÔuD­c{ÚÖfrk[Ëö8¾Ÿ¬w–Ѷ5u]ãsÉó‰ï1@ ynfð £)¨œ4=~>qî&]y¾|îîÝ£ûËûßN0hK*À/ÿ2ðs?· mg_µz~8 OpZ-:ªì61«Ø¡à8#´m5`i°í ¤‹‡ Ó˜²—.‡œAÂD…©Z7ð2 Ÿ,î7œçÌY8i³‡@yn8Ë Šœ9Ï…ê´lÏÒ¤90ëö$ úºÎvTU±Û°¥:ÛÎRšmkaOÛ*î:…®%¶‘]„¾'î{⮳°Gkâ¶m`'þ ]Âýno„á‘AíâsXdçœn˳Cáäy¹4™œöyÿe¹V·yÙ²1Àúú@ÛN?×÷­GiàÛ¼l–…xQócL¾o¢‰)‚ÃÊvãUºeË„¸0EDÎ.Îq&ãŒPµÜTJ2‹ mҔɶøVÛ“¦ÿLîEn¨(€<¼YW/-Õ7Àöx{8I ë&ßQÕÅ.cmoÙž®µ\óî¶"_ZP×ºŽ 5“Ö­MSQ×õ°í\4mŸ–£è~E[åÇ9$³Òç! ܳÒPŠÈM-埿í] 1'"bdfEI’í_]Å*€·Êæ6µÃ-<7@]ã ÖõQY ñ=m쬕Àa0¦%Öºç¾oáàLíE’r‘%"Löõ‡q]·\CXk™¢XÞœ$ì&¤`ä9³‹æ²Î¯Âpžk9sQ`3ã‡?ì¼\}Ýä«Uï²-¿e{ÚŽÐtö´B×*n[qx)î:r:Å}o¸ªæèûž•Bi<¹6X¶ ˜YÐG‰ÆÉóE,rè2ÅLQ̶I‚!!’$[]^Îöo¡½mZ¶¬”¥ ýñãš¶­>/ávò{èNû#S€†–;”!sdkÛ a‚íØ!F>;óPÏ3¸ÏjˆÏøXØÃN¦Ì :ëø* ƒ2ŠR¡(d!Ï­‡×UÞÔžÇËùµBׂۖѶÄu“ qm“ ®lVÛê[øXŠT£®çÐZÃÂA‚Ÿ£c iZ¼ï˜e³ÛƬW`Š­j´b6O¶³8FYNö­®îÝvY³¶¬üöo‡å¾o>e1»ò ¥Ü$‘\™Èºñ‡¹zS4ÔíÓ¡ïÛ(ˆž=S!z ŸBYpô؃Úãk¸‰¹}T—8¼2ïØb乡4cÊ òœ(/iùÉÉ®wÃëÌãZ~{ºŽ¹im«¨éu-QgÅmQ0»"›Ç‡ µ¢®5M‹¦©]ôœ0-Χ„…h8ö÷.,Px<·°l¼.hæ'Ê¿ž³1ÆØZãûÈ;Îs¶Êò†e«åЀº^ûǶÏ {àƒë— Ë¢¦Ƚ5"ô}ÍQæhï¸P£µ —ž] À#Â’ÄʛӔÙÊš…íq°ÇA "g{ˆ‹‚(I¹Óö©¿"ôM“ï¨öX¯Ã÷ m£¸m¶©KÈ1? ÚܶÄ]GhšŽëºâ¦iaõ>äœ\‹Ð.0l5Ó!çÒPØ6@C÷áFí–¼/ËD%è©(–ïuÖY˜@ºe£ÏaÙààA|¹ï›/»¹ƒi3¦'¼/ÛÙ±, D!÷ ¿­1=ºn¾aŸØæz–#‘«°Œ¢ˆ®!Û#‘]ÊÂêzŠB!Ë€"ï«••êZ"fmr˜k¸¯ê|×¼*vi8alÖ'iú ylˆ#Üoóy…ù¼F×wì} 0û\†Ð%ünp F(†–!™€<›’ÒøãÆA2€¢(ïþÀâ40f[˜ÞÖVy¶Ÿÿ<ŽkÝ}Sp«úú@uŸ¼ˆ$ÓêÁrè¾@Q×5ä<  )=ât2ÁY&ÇܨóaÇüp$ofçðbÊsP^çEBYJ”e}µ¼\]gC7ƒ¶@W×ùŽº)v£œñ‹–'qYš“ö(t]BMÃT×=ªª¦ªª©ëz3Èi~dÞ4DÏʲ=‹pE2?ÇÎ,yn±CPœ’ÁáO ‘òlaêûE§`Œ)°ÅÉØË–Vi©/¿]]¯ÿ£KJBºvßA 䂆!2éó µmc˜¬y¨òLÃx?äÿ»à{â¨.†ÕøÎ FžƒËƈ4ë«•åFôü)Û9¹´Rèš&ßQ7Å.cºÖ8ØCh»Ä²=µâ¦V2U×5c>oy6«¸®[tack­Ð÷ä¤ÎÁƇé%ƒ×¹øâ øø9ÇpÈ:þüØD/ —‡RišKKX€,K¶…2tËû¡W¾Ò†ÊÍfíÇš¦ºRx̨uú[¼ÀŽÖS4·–ìc¡PÛVƒõÃý‡¹ˆãŸ óqÆï£¹l ‹ãú `TÊQ‚,S(òn¾ºR]£c,ÛC„^)tÖÉUìÒZ¡m˜këÐâ¶Shjå¹þ¦!T•ÆlÖ`6kPUÚ–½³«ë}È${” Aa–ù!s#Ï*ž²UîP/9›â}T’gÚu%ÖÆÅh÷x¼|,/o|A[^˜m¨Ü[Þ‚¯Ôõú?¸q\+u¯qîIEN ,P<…@&r¹€Z7Ôu­g–b‘›up™±Ý†8ÚÀvÌ u¬ØòTÄE™Rš(*òn¾ºZ}+±ñþ)àØ“`ü{Úè:¢®w’æ†ÐÖLUeh6k0Ÿ·dÓ›HöfEÎãKƸáº1>`_˜Éå À³1¢} ìPxž Qyn}öÇI ÔP¯êû8ù@äm#€ˆÒ,+öÀÒÒÞ­6=Û ¼á À+_iÛ¡®›ýy×5~0Í­µÒoæ}A Û K×uÛ˜BctÏ^êì lD—e{l®ÃEÉ( pY(.˔ҔçíluµºJY%BîfMï­ñ»æU¹Çh1~¦¦ê˜W†ç³žçó³yÇóyçüʱ=€sv±ãÖÙ8©³°·'Ë¡w_,Ç©ã]ù‡Ë’éZžy€OÆ;%µfvÀ½ß3#IRŒF+à OxéÉõ.wbÙò Àas`6ãO´íî³pQ\c…ñ8E–+”e7Ûµ«ú¦R0Žçg4úy•ï^Ÿå{ú.WgùÜð¬Ò˜Ï4ª¹A]1BþÎ…œCL¦,…ÖÖÁ¥µ°Wäõ;Ò.Iw ùdŸ¹ÚÞ}T7ÄÙI"¿/NY3B’Ý £ß¤AK¦ùÝ$O~òOoµÙØ&@Êÿ1¾Õ¶³OÓÇ¢À³@Âð„iLc$*»ìÍv9äü±pF‘›ØÎwñÁIË›­ÎÇ2;Vç“çLeÁ°9{@e™ð¸L)ËˆŠ¢›íÚ5ÿ¦R»––V±1¼³b·îØ%­2°Q5µ¢¶QŽíQ.‡´BäÍNú ÔN6¬ž¹’{§^x>‹i‡ŽB1!íyœ:†IFqt^»}×Åa±|=Öh1”R+ØB ÚbÙào Ëm[½³mgL¤Tƒ:ûG³°ÌØ.†<4’í ˆ´î¸ëjLÀ Û-×ÏQÆf¶Q]¥A1bRÊ2…¢è¦»wUW$ÖÚRzí¼Ê÷Ífùž¾ `o%[›ÍÛãffä¦P¡^ÞܶöH0»ìBÄV…è6×@ø Ûìýìk2'Š ã2Éq0€”™ YHÖu±|}#µ‹Æèël›)Û¢!sØ 7à/Ûvþ×Z÷N!L !MpXÇX`uBc'̈8Á¢É-Ð÷ ú¾v°'N[ ÎhÄ F#24O&ãyA(GÝtßÞù×ÓÔ§çãïgó|ÿt½ØÝ¶ MÍ\×@Uƒ«Š 3´Ô•B5W˜Ïª*h|¬È\tÃfv°‡¦I:Ê8ç‘|·e¨‘ò}ÇZY'ŸC¶'ÞNØ&9¶Â“ã›z1ý± Z÷DÐv«Ë¶©RþôO1«ëÙ{¬aF¼gœ}3È 7¬·xxAæ§f·ÁôžÍ i]C높D‘¶ ´=yÎT–Œ²Ù¿„Ç£ŒòLѨèf{÷Ì¿ž$>u Õ9›çû¦Óbw¯ ]klÞžnr EÍÝIv ‚Íß#°‡¨ëÈÁvšqÒ-NÆ!/è—ÞˆËȱ?:¥] ³p“‡C@px9VÌç¶µsŠ…LÚÀF6)L®—$Éð Ø-/Û¦¼á ¡¨kþ«¶­®«3^o"ãGX}Zx*J¦û„c,OgÛ©™¹á,£ˆíq Oi¸( Êx4Ny<Î(ËÊQ?Û»w~yšã’V‘›“«›ÎòýÓi¹W÷ mk¸’9¹BS)®+b×Ú;{l«vé ]0;\öæA?Ç»I+¢‡I:âŒÖ®‡ôÅB£üuSèâëÑÄ’ãËäò^kg¯'¾N@ȉ¤<í´ïŒ6-Ư|åˆÞúV\QUë—uïxsqÃ"ý€°?.Í9lØÇW#‚ËÜLN˜Uƒ¨FžE”%;Èà ãI‚¥¥eIûÙþ}³¯f™é±é ‡·_[+N[[+÷u­n6ªŠ¸ªU¥ü_]SyD÷C> Ý:»à ¤¤¤“Ú.) )n °½>H‚‚ãŠ<;4t|Ù㘄è<Ö¡¦5£m‡p+\S\·¢#2 T’.-}§lZ.¹`®McÞÒ4S4 Ù<”wý6ù®?ŒÄ¤Ô ¹¬ÍPÊ÷eH©IÒRY2F@Y2%ÑhœðdœQžÊ~¶ïì_ÒÄcì<¼Dv6ÆõõâÀúz¹¯ïê†y^3êT7DMMd½¼öÔö„ü> gød {m•‚ÙvpBqtïÁ©OüaïÙ€(Ná(¬ÚÐë>dІËÌìá-Ý4ð Òp{ÞA£ã€(ÉËrûè¶U€7¼Á¾Å·¼W¶mõæ¶­ag%”Ö;Dxt»ðÞòpÈøI]„’”à$!NS=ÄE¤iÇiÚrQŒídt<gTä £RÏöíŸÿK’­ f"tJ¡9~¢8íø‰ò@×êÚÎÀ^W@S)4•âÐ [è£óCn®.Àîfma­…ù!/oæPäâC'—<?_&~)ᆎ3‰äæ\r‡-ÅË\×Öá„ç.¢õ ]fÝN§Ø6“¨n» ?÷s#À‰úÍM3û:³–,ÇÝmì³/s±ƒïæeåúÉ1>pQZä a<J;;#Æ“ËËÊRa4îgL¿œeºeøì =Í‘£åÝŽÝ­ëæólΘωçs…yE˜Ïþc{(rv¦§ëŒìÒ:f°BŠÆP‚ÃÊÍ6¿ÀüŽó{]”øõ:²_Ìð õQD’rq3íP8÷‘Š~aƒ¾o¾þÍobºq¿­)Û²¬¯[ôÎwâÚõõù;æóq/*LN,{”©èyˆ·'_I í ¢ÙL ÑØÂžÑˆ(ˈʲŸíß?ûrš™Îh”n¨Ñ“B}ôxyúÚZyºÑÒòkÔ¨©‰ÚF‘ _TT׊ªÊB «ü´UHqr‰$Û2Z¢óñóGÒåáåXïÌbŽáÍPCµ9«#Ûß>À¡°}Û2ú”$›Á¤Ì„g& `F W\ñÉ­63Û´¼ñÀ+_i—4¸¶¶þùªbt‘0;vZ= uAäaO˜ä‚Ø|‰³ŒØ z¹,Á£0O–2ž,”ç)Fe?;p`öå,Õ½Ñ9ƒéI¡=zttúÚñò Ý+T•áye¨ªu¥PWŠçsŵôrU‘ vaÏöØ>à®chmaÀ<+‘iIáf‘ÀK¸)‚7þ¹ýó ybÇÖP;4BâXdÿ »Îël”šfÉî«à j|LDlž€¯}íã[mf¶i€cÇ€‹.RêÃÆUUÿFU͹m U®k;X ÁÞR(‚E2wUX¶ÐúÛŒFÀhD˜L&Ë9-/• ––Ìôn§Ï¿gºa wíX >|x|Ʊcå™]§0« Oçóñ|¦0›)Lg ³a6#Ì-ä´=ÊézBÖf+m ÇòÄy‹˵˜«gèo:´‚X½ý}ˆ8†“„,–EXdç¶×c³Cjå×r,û?\“žÀW¾òémá Û¶àÝïÚÖšøe—áým;û€Íð  µñÐNû©¨ëŒ0@Žá Œ™Ýæ´ISP–õö‚ÆcEãIÆK“y‘Ш4ÓÓN›}!ÏtkìlŒ£S„æÈÑÑ™k'Š»kM˜×†ç3ƒjªæDUEä0~ÄöXÇV€=Dn‚:rÎ-1‘MÒrohëâ ™dÜi!!—~Q“³‡pÒeF}TUÐVa«ØçL@¦pÍ–ÄкCÓÌOÀtzýV›€m$JÚ¬¼ç=a¹®Ûצéì{Ëre¤±‹e­ItéL‡ím·¯”»/Ù +€<'* pž;Ì?R<™¤XZ*¨(ŒK3;ýî³Ï¹îû#P„–ýá#ã³Oœ(Ïì{ËóWS]­¶G±Ü’ ZW‚ó]Î"ëÔò^]fVbð"7&"ŸÀ¯wÆÏnöË8¡‘ ±ô/¿°>Ž.æP¹äD!Û,Vœ«ï¦±ë“DŽ<)r& ³¸kö7ÌFw]}ÖÖnµyØÆ=€”ýQûyÙeøûù|ý7»®F¤¨%l^˺X“•km™›…Yœ\ÀdBXZJ°´œai¹D9J±´d¦gž5ý\Yè&’7tDhšœsìhyfÛ*Ìf†g3ƒù Œß®ªõ£A¤¢nXˆÈµø®E²Î#c,ÓbÓ÷Y’€ŠBÑhœñÒRAe™ÐÒX¯ŸyÆìŸòL×®åWDè“ÍáÃã³/Ïè´BUiž9Ø3w°ÇV:Dy{¬c«ïÉMNANÆ:6´ˆ¡ EPg™‚ñÔ±!Óµl#Œ™ë-"Q7‹ÓÈj{ª*Žª‹§”öÉ~Ã#ɽ6Ǽ…ša¸íz]Ï@ßkl‡²í+À¥—ïxãG~ôj6kÿK]Ϧ€¸2ƒcKR88»A/g8Ï,§)Øqÿ<e<”¦Æc^;óÌégó¼o=ìQè¬ñOÎ9v|tvת¹æùÜP5gÌçÀ|N\UŠÝÉuMn¢ ›¥ÙBÃ}/yu¬¶ÇÞ÷k9† ŒŠÈÁ‰>'ÖíÈ zýñ+æöeÏ’Åqq,Êñµ×µu Â;Úb©s4%NÐÙ›$"p¤„»fƒµµ#ÿô®wá+P×õ¶plû åÒKíÃý“?Á?ÍçÓ_oš D „ù‰bÂö8¼ï]Àx L&Œåe…;r¬î(1¥XYÑ'îqŸÊs]÷%lËÙ)…æà¡¥sݶ„ÙTótÝ`ºÎ ÁyxsK "ȳq.±:Cx³Ê ƒøC'™œ#¬²<1ä‰Ï-uI)«=šÍlšE™€$@zhƒbyy8fxq!"£Ñ÷í7àbôöèî:žÿ|+!¹öZüÎ|¾þÁ®ëH)EÊŽ#lRðòf;ý¨ÕöŒÇ -/g¼²ZP9ÊheEŸ¸ç¹kŸÊRÓjƒ&Ûò·‡/sähyV×sÛòc>U•¢ª"²"6Eu- û©Hm–f‘+‡ÈÛÚ–¢“3LW(00&H—~„\@q°¿,}Î0˜]X²¾gÌfpˆ ŒnÀA²aÄ™çÛ$Ö‹òlcôuò.ßjk²å.UÞþvþaàoÿÝ|®_U×k_´i÷”`v1½êX'WQˆ“+á•åŒWVK*G9V–ûãçž»öy®;c0A% ºD¡=txéGŽ–÷èZ`6Ó<›ªÜàÖêy„ñ¡cËAÄ!¹äÄùy¼ ™]0É‚+¤”hD©F®%vËä&þð#8Å\õ9D†ëšy6³)µ±à–Ä X€:’ÅãdI ˆRêûZÏçǯ–wùþÏV[“-w™ ]ùÎÀ³Ÿ úàqmUµ/˜Ï×23”"¶­>y¶§(ÄÁEXZRرšaÇΓI†;úãçŸüo‹\ׯXØ£€Ž€öÆWÎ;t¨¼GÓ5þ©Ál ¶N­E=OVwÓEÝ{<©†…ZÄÑ[´YT ŒMX˜y6‹q¶½Î¢TYXËôÌçv;¥â}‡ Q¬ }ÀP–볬ôÙ²@V’mŽw]såVÛÑb¹ËTÁ«—\´­m]Þÿ~|®ëêŸišlDqš‚óœ¬tyÓx¬he%ç•Õ’FãŒVWû£÷:ïøßd©é™1†ÃüDhZ¾×á#Ù9m缨栺&};C#9箳ÌIÈ8Í0Ƹ,ÛvZ)òÞˆ¾O´7¢™±X™…F1 G„É6‡÷‡=±M®¡ª¬ ["æä˜r eœ×Ë}à诜6 Êv)hÛêÊC‡¦Wnµ-–»L5#3ð¾÷'NØué¥x›1ó_mÛ¹èúá\\ŒñXñÊJÆ«+%FVVú#÷º×±¿)ìq‘\-š.ßëðáâÜ®SÖøgšª90w°Ç­Ûè­®³ÙmÀºe`ؤ;)CÄðë"Œ—øèªˆúŸa°yð7Éyí²ƒDQԘȤE/´fžÏÁmk4T‹LÎ ŸÈß‹DØÉúø»ëµnüÆ7pl«íh±lë ]º1¶{–ïi šL€o~)óH½ímøÕ$™¾Á˜eITÌ£ÓÒR‚;rìØ1Âx)ÅŽÝ¡{ßûðÇ‹\7Æ` Çóh¯»aõ>•÷¬kÂtjx}Mc}y:…×ôX=¹™ØÅ±eœ†'Df T±åàè2A1Ó"÷»ö´ýüñƒvhÑA„9¼lÙt×Ìf윃ò[€1U’ÄìŽ3çp xðûÆ,À ¾ï°¶vôs_ü"f@}Ýe[W‘ÑU ;‘$HÎ<É|^ox~m2™þ`+ÁdâZþÕ‚FcE++íÁ{ßûè_åkfLÌB„ö†Vî}øp~¶æóžg³³9Ó|N4Ÿ=“2£ëˆ´f&k„Æž;hãØã´4A'#F` û™ë½´cÀØÔaj‡ÜcÈ›cd£áìXµª˜ÇïY£Àâëß)qìC5å%X1\œÛv@Uõ<›jTÈfk¢…núQ¸ u؃]<æ–È* ˆR~{»Þkl …ÏÉ)Úœh?Ÿ—'HzB›±¤N"½@&̦=G¬G$-T Äê-"‰è‚£[\Oà¬ÚV«ëa¢—ã‹ÊÄIEÌÄV 2Æê¡œÈN`ޝ5"Žp;˸C)ÅÛÓÂ6Ý‹\[´=DUµþµý×î Ð÷Óm¡ÿ‰Ë¶¨‹Xß’÷²¸–?AhùGîo`•«DX°“U£±2Ù…ZcBEvªÇþÊ«V/<|8?¿ï›Ž\ÛpÆypl…LÌvŠÙ^禎R{' Ì pÇÞ+yØÙ¢s ¼ LQ¼h‚ä™ÅŽ4Àæé±Ž¹!4’ÞU&ÿËãäIÓ¡ªÁ±KÉÉåXƒßH©„û¾Á|¾ö‰¿û;¬£p»•-­Òò Â¥eìÓO$l3.gÌ(á`3–¬HÀŒ0–ŒÞÏ€ýÕ×ì¸àð¡üü®#TUdziyÅ$ ª¢ü<’‡“‚ñS?Bw EëÃ@A —y)4˜%Å @{òë×K—¡”Í]× ËëgÆÒD>‚=ÎüC/74¤ˆÒôÛ³$Yò’lvk´™H©¶¶Ó鱿•}/½t+­mó²å=@¤r´úZ畇å耄)ÛÕK"Œ,¹¿e"ì€í–™±ÄŒ’)*IÐÐW]½ú°C‡òûXoÏóyy۰‡¸i¶QÔuv2 Gsr”Ê7óW }¿×Õc"¼-Z·ÆÅ,ŠPÍDóqGL’pAˆ“瘙PULMcá™GpTéüñÃrGl±»/åŽãƒÚ‰(À!¥T{bf¡‘ÕeITÿëñã'>¿Õ6vSeËX EçàáBhõSfä®ÕÃ_aX¸Â.;˜±ÊŒfŒ ‘9L  ý×oîzøõ×îWׄézÇkkÖ×­Cȳ=µ0=ì³1;šsƒT98‹†0$ê "&K •-cgíAØ—Øié1ûž1Ìç:-:Õµl¦ëÁ(àPì2?â¶'îü`M)&J¨ëZÌfÇ?ú¾÷áJxñ‹·ÊÒnºlI°8èjT Ü³Ì_¸¿%XÌ¿`Œ"ßêO³e‰ÈÅð6W|c×#ÞXÜ·ëuÝót½Ç|n(öèJž;õ\$™"jå(2&м¦´“~1Ô­8¼|šó¼Y Ø…v$fFÓ0šÆ¦NŒÙ˜ òXžB% L~ñ×ExÉø$†4ì¡‘è}Ü%xßÚ¶Z?qâÆÉ¶ÓéVXÚÍ—-©Bu.¨c¦G!°=€w‰-Æ_a‰áŒŸ­ñƒ‚ÁI‚9€öŠoîzô 7êÆlÖq5ï0¯ ª 2Ó:Ú6­Û¹m}Fd Ìl¸ëuì&‚6\Œ<Òã8æÄOTç¶—e©A¼†ˆúÜ4Œ¾gG3zL.Ž5|a‡äšå8Àð˜,Ê;ò²}H}’{”c†ë‘m„’íÉy­ ªjís_úÒôSð?¼þõ[ai7_îô Ð òîÁø3R‡ùsØÁnÉŒ%7Ð]|Ë¿lXØ“ƒX¦ €æk—ïþ®n(Ôu³i˳Yº6¨j œ[’“G8~ë¼±Œ‡u>m>ÙÄ0'ÑÀV"VËx'Wè1(‚7a Ã'»^z rì{'¡ì·Y‘Áv¬ ,’AT_Ìöu>òi¯[8¿õ`O¬”¢ºžáС«Þúÿˆ¯~õ¹tÝuWl;úSÊVbÆ'zð @ ögP%€±û[‚ký‰°×êƒQ,Þ‡•7ÔDh¿þõݽþúâAm誎××;ïªSå]­Ë£ö&0'’¢$Il%ƒ%²>ÂÓÑö"…¶›È8aè, ðÃùDÈFZÛkµ>ˆ˜YŠ—iá˜~9éâk‹!9¯—6»c*~ì:åÝ~|àΫ$ÿRÄü´Ö8~üú¾ýíÓ÷Àu×]Á—]vgYÙ-/wZˆaO„­í†g{27ð-ˆ0rƒ_}ˆ° `É0ÆŽíI$öLèùÚž'Üp}vAÛ2f³–çóŽæsãæÞ²N.—­£ôã V.鎋”|>¢å‰žÀÒxSÇ7@Ž@oŠsJœbŒ¾'瀳4,‰ŒnC>Ÿ•9ºæ@JÅÛ؃Øa½ÉŒ?vœÑ ‹SJA)E€g‡àÆ4?zðà5ÿ³ëlìïv6~àNbäõ˜á4R±ñ§ÌÈ×?Š9~¶ƒÝUÀrý†1f;&È`™"£jõ¿´÷ñߺ&¿ ®µµ–×ÖZ¬¯['×|´îf\ôY“-×/˜cO83ÛàöP‚n'ÖØÄŒJ¬¥2-C§–1âL§Àtj§D• -"ÿÁซš¢ÀJ!º®¡>gèL“ID¤‰·•Ö>°>‹Ž1ÿü9ˆú^c:=ö‘w¿Ûü5üØMî óºMåïâ@`ÀúHH`[¡;ǰØì¼ËÌþ{ÁvRж•5J¡!¢ú‹_Þó¤ë®Íªµmù§ÓÖ±=@Û­›fÔM2 fEâ M±_£"ª1Òù,:È‚ÆfÈÌD1iž¨iìÜÀ2yE¤–@p–EW°p®ØY&½É"²ïÁc7D—»~#‚6a[ëC !#äï—‰êz:=vìzßæ¯­m#ÝóIÊZè·Èø…ëOˆ<ÛS0ÛÀ’ë Äë[2[¦Ç+M’`P÷¥/í}òõ×eìz`:mx>k1›iÇë·6-Ù ÌäB=pˆG%ƒe‘÷:ìí!DÐülæx¼VŸ­¸Î²:6‰°Lá\Bck òçÀäk´¨ó û²K±Žo –)뉈ԂæѲ'K‹ïöõôÑtzüÿ¼ãÝ_6»÷ïÿþ`Á·±Üa`3ãG{„m¥3ü±£5—„éqÆ?2l[~g‰F%¨ª>ÿùýO»þúô‘]ÇX_¯y:mQU:ŠÚŠçÚ"Ϥ,²0CFÄË" ü6t ÅÅMX‡À…ßûžýµØÙUÂùÅÙ´Åe"ª)v\m„ïØ‡ -JŸe_ËxMOì ú Ñû„ßœ'Z^§«Q M§Çg7Þxåk´Ïþ„®ºj¶m™Ÿ¸Üîà$lOœ‡FÁ)‘Õô»í°•ã 3P2PÀÁ0:IЪþÂö}ÏUWÓ#u¯QÍ{^[ïPUšºÎÎdÒ÷6zËâ|›¢0¢$cºò0°õ‚µ#,fT"=ÿ‚ÈÝs°LNP“gYäAõµ ù?huü¬îºßþ>„aá"¨F!=ETÙ(? ®gÁ¸Ë¬§7°ID`¥RÖº§õõüì2|Žñ‡?|ÇííYn÷ °ÀöØçDÞ9$ÐGŒ¿€Õ÷ˆÑËàw γ ¶°GµŸÿü§]}5?®©{L§5Ïf-5â¾W‘[DZ³d’^€=µ1ð:ÑϬÕ3?AŸC¤TÌ®‡Z×IÀ:ûsçš\/h~àÏåœ?opxùgꙟ˜Õ‰¢ÄüõÛk 2æ·”’gb})D€¤™TN $׿aˆ‘šN>~üðˆ ÜUŒ¸+€è{"¶GœŠþRGwD~Ð;‚¥:—Lü€È H”B¥0TýOÿt·¸æóئ鱾^ñt½Æ¼ê¹mcr0§ÐZùè­Í'œàL:³à—·}Ž  ]8?§—œ+H Ø.LK,…0jÈm„1Áa¶ùöC ƒãÇúžˆó–c§—Ü`” IŒ‚R µmcÇ®ýÝK/Õÿ<ç9.»¬¹ý-õ*·Kˆa}8¾õ¯g|ت:s"/q(ÙµˆáD(’³^¨N5ÿìgïöW_Í­ë³YÃëë5ªº§¶ØÓ‚¹' Qꔜr]Á93$ˆ„»’gol 9 }*S˜:™´7l¿½R·†Çq=¡TÂôÚc쑬øâ6QŒÉdè8dÏevð¬Î)ŠhÐvË®7Æ`}ýðg®¹fúVxéKÏÃ|þõ;ߊoC¹Í@Z}û`ýj1zD°'¼¦_Œ_0ÿ6º«t#s¢8f # HúOú´g\{-¾»®{¬OkžM+ªªžëÐαesrfn@Ô‘R9+•ƒQ£¤Jz¡gûžV¤µac”˜×‰HX¥B$×"¤±†³@´© 7 {Ø;æd{ÉD! \<÷!³C è«C.‚Ld ‘oB$RL©”f³æÈ‘kícÃu/|á„Þüæ¯ß%¾q¹U &?™žMx~ïè‚¥9Åøc™Ã¶2Œ`{ Eà C„H«¿ýäç]{-×µ=Nœ¨x:­Ð4ö8Ã÷SŒbÔH’IâœÆæâöÅ^¿À™ Í ú ÖøÄö+Z÷‹ë ‡Ñ\±Ÿ òȶñïá¹Ø´¹,ÞN®m#ì Ø=ü.¬N€?Ò3ö' €­LB©u=ljßp饸0`¾wžÙÞ~å”*À+—cióài#dp`Fáþ±ãô…öÁ †d®òhg5Õ÷w§=çÊošÇ5ÆÚZÅ'ÖjTUO}4-ÁXŠ“æ'²žT¸[ƒH»nÝV»ìñýÀ±%ɬ(dY@ cLä݇ wDβ(ßÏÀ±3H‹ëv‰µI†¢ù‚Ž­¨’QpðÇz¡…ÁßÚ:¿„ù‘Ê ‘l•À†b6˜ÍŽ|öª«Ö^ñ¿‰§<åçï,›½] ÝÜ‹|þ½é·üÎð·9/o0à-aU„1@ÉV J®åŸIÿÉOÞý9W^Éÿ~^uX_¯x}½ÂlÚ¢m­#ÉFoÙ ÉEl` ×lÌj’¤’»’##ŒaŽ ë#*•?c‰g^V=df´ìMìÀ :Ÿ k ZtÀ¹ŠÆ¶Ähò°G¶ž_Y¶ÚÑù¥¬T›~>a¥RJ’Œ-ô96=räºg½ûÝú£ð„'ûöï}ïV›ó-/›ö¡û?»Ð=ƒãOBpr Ý)-–Û·"·‘ûþ?w-¾´üÔý×g¼àÊ+Íw7Mµ5 {æóMƒHËCµd”J¡Û:‚äw­{Ó»Ö.{ñnÿ$;›âg#ç“íb‡ÚÆçfŽÜÍZqþŸá»‘V9(Jåü1¬ #|ÑùÄßåÏ^«Šà޼Fmgý–v| ¨if8qâÆ_ãÿÑMðÎwn“Ù.nEÙP6ñàÆ|>G]éàÓµþÌ’¹eñò–Ô²,Ò‡Ä>ت#ñŸõ¼o~Ó|wÓjL×k^_¯PU=µ-£ë }'ÒccIÝIC{x®Õ³CÖz²ƒyË~„Š@UˆS,2z tgÈ$ÛG­:ž›ë5Lo¼ÑorÍÆÄ°Mõ¡¾‹Û¶'@ÝcÏ ¹9’$¥¶mpâÄ¡÷¿ýííoÀ³Ÿ Ôõ]×øM*ÀB‹ïY’ð‚áË÷šS`OìèÁR›J²úåºu ÀaHº¿ú볟Õ•æ{kçäZ_¯0Ÿ[mO¯‰–ÐE ¤¿‘—9‚Cèo Ønc¥ÎšÑ€s ¹T N' E€‹N% àÍÕ1ƒm‚CmãuÆ× S¸U¸ÀyaPÃøÅÏ™æäÍÊo˜¢;;¿ˆ¬ÔYRœ§¤uµµÃŸ?~|íçà×ýø¥_úüVÛïm.ƒ ­¿8²0€ 5ÎIo378#²4‹Uv–nà+Áì…kýGD(#¤œAjg/Îÿâ/ÎyÉÕW›§µÆÚ ‹ùëºG”ªvn`an6§Çc•˜±‰¡”M2ÑÅOÄK í zx̘q‘™ÀúPtÜÍáÌbq½—ßxÝãpÛEgWxr*‚e1¼ ìP€Bò)=€‚ºûæÑ£Ç.þÀðMøÊW¾´Õ¶{»_bãwÅi#ïÇ=çEŠS$Í{$wOîXŸ¶õ/ágDN×ÃИˆç@VìÿÜãEW]¥ŸÖ4=¦ÓŠ×Ö*ÔuOV^@BuFi …Kôt!ã°b<÷Ææá°9LB€Ü‡š°.±¦(<£!#s³å0.¡èøØø{Œ}dæçT{¨¨Ýqè¼)¢F#ØCâ ãP¡ˆf³µéñã‡~òÀç˜]”ÂvÚwý’Áè#J“¢ÖNžDAå˜å0¼§:)H›s„–?'BâÇ€/H`ÏPšþï{¼âš«ûÿXU³YÍÓYMu­…åa§æŒàD`?˜c½ HRå¾(5„1Ä6‡O²Í"dZÜþæ–ã}‡Ç!Ï8‰ïK®Ñ™åzy;°g¨¢Ï(ÚW‘Èž…’|žÎ)FV”€ˆ¨ªÖy:=ò3ïyåûÿã$|èC[m¶·_IYè»?Ðu­xæŒ_ô=’³³pcédpÌ:l ±R<&Ý?xöÏ\½þÞ¶í±¶>çù¬B]÷òóˆƒj˜™98¦„2*rO›Q¹‹ß¥,~?ÙºÅõ§²|Ó%fu†lÖpÿ k ò.û] }…±Ók¸³ IˆªªiwâÄ?}é¥x\tÑïxGu{Úß–—Š–=³Ón€ëÿ\«/°§ˆz‚4ÞÎío´Ìv¶8kü£îzΫ¯¿Î|oÛiL§5O×k´mOÖø'h Î el `ü›eZŽQ‚—"ˆ`Œ6¹ßÁz,À•!º½–}åÝ¢–Ágæ+‰¨—ûŠ8ÿèú½¡S0ôÁ&ö(×[(¥T×S?~è.» ¿_Êl"ç‘P…±ÊÊ€ecÑä ÜyH³9D ì'ƒ1·nY®^Ï#× ×˜ït º ¹¯P†ÁìÁà7ÓÿX'—Œ ¬/$¡º^ÇtzôW/»Ìü6<ó™À[ßúígü€5X¸"%6øÆá‹ƒ¼pŠÏˆ"æ¨‡Åø·ÃèÉÎÉuâmoËž·¶fžÕ÷ 'N̸ª*´mïŒ?qzƒ¬O<ÄÐ À€ðgc ÉÍÁ%)§cnI±çY+w6ÿ[ ul¯'÷:¼—Î,BaubÖGZz‹ó…–Ÿh>_3kkGþ=ïÁoÀóŸ_âío¯oï›ß6%†B6ûà,æw/F„"ñÓ•ÖݳBî¥hØ7hÜ Ù†ý ·ƒ­MÝÜðéöÿ¨hòˆ¦i©iz¶ i•—Gõ)b0bff(Žî+r…É&†°g1Ú+ úï¨/û ‹=.¢–’“'vr-Bšp»–"'МAç(ÏöÄÆ?›­a:=þïyÞÏ~ަùö5~N?èBËïÙ`þø#ìNꎥ"CcŒûÔÌè‰Ð˜¾æ58ïÊ+Ó7)5~D’$°SŒgS=e×›H¸ßbºB†´ãöe‡óŸÂýFcHÃsG@YÞÈöDϱŒS6¶àP ëIæìr ÜOŽæòì±æÇV ¥–H/¥2Æ ®×góùúO^vÞXØóþ÷oµyÞñEE­Ó€B#ŽZ(FhÍÎtΘfTÛ£0gÆÀ:€)€)¦nݬëPÀ/þ"¾°woÿ´étíCuÝ ÂªÑ`Ï^š´jŽÿ_èÎã)?}+ ‰Â ߃¶#<Œ;ê„{ó†œý±Ù± ^Eì´Þ3¢õ²,P'ñߦ?:V¬íIHëóù‰™Nן.ÆÿÜç&ÿ&Œ°-ø¢¸M¾û‰š‰ éB^ï@köãHkçö1Ñ>vÌij{GŸùoÿ Ϻüòùoc^çeä›õɦ ‰D`E­¨×Þ,ø1óG Ž&¡à;öÅÚ¡p# H¬7€7Ã{!̈ú@ ¨‘ðllïÆäzvð‡Ú¶F]O?Q×õKßû^|.º(C×u[k•wb‰_|ìô’‡'>E‚ëUôÝAݱ‚+4µøÞC(QW±øÒKAÏ}. üÈà¢4Í~7Ï‹D‰ÝÌB!‘O*ÁBİ'Ž¢â 0‰‚ÊCR,¾¡Î"ìÇÖfŒ“8æBd/nHœz‹XÄðɋ٢tŽÃÀvqn@Û6hšÙ%ëëíÿóÁâÈ+^1ÆßøíÉôÜTñÆm_ì{ƒ•­etzXZ³‡жÌv²if4DÃ?­ƒH½ÃþÚío\Áßó=0ï{Ÿ=ÿ¥—âUÕ=ª®gÙ÷-…YQâ.?À…d>t dˆ_‹òè!z–¸¡‘Þ@`ÙâücCXÏij9þ.0dz¬:E)rò›·õ½Æ|>ýÖlvüyïxGûŠ~GàĉoïÁîÉ _L°,Ð¡Ñ 1örFŒŒ‡?_+ð ‹ëää±íÏ |àì–QŽFøE¥ò_Êó¶õ4ÅA\æÙaÈŽ0 \Šî1íx<Þ°´@±ÆéÏÃX€ü¶CŠ“± r‘×í26@(•3Ðu5š¦þ³ºnþŸ÷¿_€ßýÝóðÙÏ~Çë»T:“Û«ø7$IÈÄ)0µ[M÷[7 Z·á»Ü¢ú’Ùʲ̮{Þó@ïz—ÝÿG~OSJýZ–å²aŒnZsEQÁC a„†9|x³drË™¹}\r^auà\"Ø“†!8ò¼ÁØC~Ž0ÚàäÚÓNQ²ù{"RкC×ÕGÚ¶ù¯ëëú?ô!t]´‡úþ0oljëîÌâù~¥ì_ðBºæ–-»Ø:£A$X˜dB`eÕ´þø1Ü ²ëÄøà]ï?ïyö.½¹ñFó𺮥mëÊέe‡² a!=~ÌðËpâÄÛ³øÁû&LO`£„ÍŠ¡@9Gã œÝ~˜ª0†>2n RƶúM3o«jýí³Ùüï~·~ã‡>„úþ=z'XØ6/ƒ neÝÉhÁEÁ°qÿŲÙ96+]d·}׻ºç=ð_“${J’$°:uáùí¸Q<Ç‘†éÄ Œˆ`n/<»¶Ò…çä%ÙÕi¡Mvô=¾W)ú"z dwÛzÓŽX£¥ùöíÂÌéBÛÞ€½Ôží 0)nõå„áàR;Š7—¼ácö}÷ÞéÔüþŸý.€ûß?Mo¸¡7‡Ýl-Þì÷;ŠÞ¶å.Y`ÿ~ ÏA×\^Ø…";ï<<%Ip1M¤öù%Ò®7 Ø+¼¹Aß2J4¨HiÃ6Nf1˜lÏ_YŠsú(҄ݸ€‰È@^ë~MküsÛš?¿òJý§Ÿù þÕÝâX)(c¼ô$.¼ÉòÉÖÝÔï§úý.SîJ`Ñý9îqH¯ÂèiOÃ}—–ð”4ÅÓ”ÂcÉj+¢âõöq®#ÿÛ)]Åj8)^ð> ü’éGƒ·1»EÆ/3¯’PKkSwùR]›O;†ÿÍßð?1c 6ü4E” ›ñ¢1‹6k³õòÛâþæ$Ç瓜û–=Ì-*Û±ЧŠ>ãØdùë•TQ é{Ö¨t»wÓÞ?˜/رOÎszR¸R¼,ž×(‘"þoªÕ†òiA6"QŽ·Æ ˆÙ¹‡xL*Ž1 ­ùDÛš/Íf泇ãÓßøýë¡C|Ö ÆiŠDkïsn¾ÛÌÈ¡pØXYÌ&Ç0Ѿ–.¥°æIDATf“õŒ“WÌ-/Û¡П:É_r’u‹û z8ÍlKYÞë^8㬳páê*=²(p?¥ø4"Af -6Ì&×iüC¾?ô çÏa?«Xóã&ñè´æ#}¦SþÇ/¾újÜ 5˜8ÉÀftªÐåæ YX¿™Ñ›|ü§O²~q¿Í®óN+[YÄHŰ“›ù[4þ“}lüñrü[Éi§án÷¸‡:¿(°k2ásÊ’ÏÍ2:+MqšRàÞÍ‚nêæo0@úh¡ïqBkl[s¤ïé[u­®›ÍøÚn0W^q®0ƒ5ötáNVøÖñM,Ç‚(-Ç¿ßÜßÉ ]/|önY/ü¶|ºÃËVU†Aõ ßOfüqkïÝ Ñw`sã,' ”UaÚðLØ—æ9–P;÷ì1ûwì » ²ä}£‘:3I°·(’Óˆ°D„‚™+€`r€@‰\'- Žƒ¶ï¹b6µ1¨µÆ±¦éÏç¸æškè Ó©Z;vLÏê3X£ ¸V^)›22F妌„Oaý© ˆOe€|Sa±BĆÞGŸ‹› àïÐB1ºî“ïË#n¥—WZ}ÀI×¾ ì✬¥ß òlfô'û/Ç/ÈD¿ûÞiuU-íÚÅ«ã1–ŽDZrÍ‹÷½x­7WâsÅßoj»Sí5n 5*Ÿ'[·Yp²JC ¸õo*ÁVÒØÀýsxèqkï*†Ì¡u[z‚Ň³ø¥EV­‹ZX—Xò¸ÈëN¥)Ÿqo@¥ 7wóG?UÃÆI~¿©Špª½Ã©ðü| ¿Œå¹¹Ê±Y%¹ÓÇ>-Ê¢î‘шo>zÁ¯[ ú¸‰— akû">¼)üSc€ÅåSC›}..oº.¾ÿS0öÛcœu*†qkZþøûMnjÝ"üYüŒÙ “µþ[6Hã¨.Wü·£æ¸µ“˜^¿ÓBŹésÜÈYÄÿR6£@¡Ðf°'ùŽ…ß°ÉoÀ-¬·à·ÛZn)þ¿-­þÉœ\'kÑoŠ Š _c#ûî´"¹A/l“ ÁÀ†¬¾õç >UÍ¿ÛW2JhlôÄ=@‚Í}‹½ÁM6«›ý©TŠÍ`ÔV0j›]ÃÍùnj@»¸þæðýÉXŸE?€ÞdÝâþ[RˆÙˈbyíS°fðp7‰YKô×-lf¨ê$Ÿ›9Ínnû–ÇMWŒ›‚G7WQdßÄï·¶Üœìæ¨ÍEææ¦ÖßœÁdž2L¿x`ókÜ’’F­zìé¤ÍÇ€‡>2dÀËn »q²²Ù ëdx~3£>Ù8aÑàOÖ[$ØXiÏ­N² €“ö·gYdª6£"ãVõd°eÑHe_Í wq³°ßÉzŽø¶]O#ÇÑò†.ÒFô¾m” ܦrªñT1ü­Yfœ¼'Ø.ådÏéæzŒ[zŒSe¤îåÿ¦7àþËIEND®B`‚Slic3r-1.2.9/var/Slic3r_192px_transparent.png000066400000000000000000000633321254023100400207010ustar00rootroot00000000000000‰PNG  IHDRÀÀRÜlgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs  šœf>IDATxÚí½[¬¥ÙUúÍù_×ZûRµëÚÝîn·ÝmÛØÆ—6Æv #;DI:ÌCò‡€Â9B‘"åÁR^’%yˆD$Øæb!œ„‹Îá`‡ƒ‰ã6®ÚuÙ{]þëœó<Ì9þ9æü×®®ê®ª½·ÝSZZ·ÿ¶þ5Ƙßãc ¼>ŽäxôQÈoÿv¤gÏžY–£i–ƘÄHY˜—^ºŠÿþßažy€9ìk=ÎCö|£Žoù$ï1Ͳ2=yòÑ<ËŠDë.ŒBˆÉäD™$y™¦y"e c´¤‘Ò¨¶mtÓT*I¤®ë½N©¶B6óùÕ®ï+,—óæË_Fóû¿…×ä¶ãuxããGöÖ·NË'-•mnžžN&'¦Y–O$IÓ"‘R 02…Æhø¿‰|ûÖ= ­éûV@×5]×­š¦YVËåµE×­šº^õ.¬ªßø ´‡}?ŽÒx]îÃÈ2ÈŸú©ÇOœ>ýÆ“Zë<Ïg“<ŸNÒt’J H™@J BÀÀŒží°ß !!% µUúÞ!­ „€Û×î§µ†1”RFk­êú檮÷VUµØßÛ»8á…¶ùÜçÐãx–x]îÑøÐ‡¾ç=bröìÛ·f³ííís'Ër3„°‚­AnŒ„5I$I)! =["…” ì.ö8ZÛ@ˆÄÍ”Rк‡R½S š5ì±´VPªGÓìW]W7«ÕÍÅÕ«ÏÜøÊWêåoýÖ7Þì𺼆ñÁ"}úé|vþüÛOœ8ñÈÉ$IË¢˜iZ8øb!¸µÄV…B"I2$I)3$IŠ$É‘$‰;²p®$ r8h&ðï­rÙ}•êÑ÷-”jÑ÷-ŒQN!íq´VhÛUW×{Ë›7_¸yóæ {_ýj·ú­ßBwØ÷÷AŒ×à.GžCþý¿j¶³óøÎlvò䯯É4&I’ ÐÄ[x ­¤L¦²¬@’Nð'ˆ`8Ÿ„™f `=:10Æ*H<„l?ÿ÷gý;Ó£ë(Õ³U’ºßß¿6ßÛ{éúÅ‹oüÂ/ :ì{~?Çë pã'~bçäÞðÍmlœÜ.ŠYnUÁ6´¾I’"MK”å&Ò´a`}ß .…‚ït%pŠÚ†GNµýÞZ~û•€Öú¾A×ÕN1èØ}_c±¸6ßß¿týÚµ ·~åWúùË/¯Ñ¸c>^W€;ß÷}˜¼ç=ïøÌ™ÇÏ—åvj­¬t.ôRJ$IŽ<Ÿ9‹Ÿ¹m¸c;¶ê ³„˜obcún½BøÏ ¯ȱƒÛÙ¡AÛ® Tçf­[4ͪ»qã…ÝK—¾tí_ÿks _GNóë p›ñ“?yzóüùÿåüÆÆÎÎdr¢Ì²Æ(çXß+£‘$ò|Š<Ÿ"Irp ïÓC€[uû,%½ö3"x?8þe’C ~^|Ï!)v×ÕhÛÊ…X1„cëzÞߺuùúóÏÿéå_ø3_­ŽÿŒðº¬Ÿü$¦o}ë‡ÞÞ>¦,·ò$‘•炟¦ŠbY6u¸n;:š̱…^÷ž&n³(Ì>‡ ŽG³†UN€Ï6â$Ý¡ë*ô} @À&ä –ËÍþþ•›.ü—ÿÍ¿Áþaÿ_¯e¼®lüµ¿†üƒ|×¹3gžxh6;9IÓÔY|ÜB¢(6Q3žä1|>¸õÀ+Q²üöµˆbýë;v~×£‚T|Æ +hÕ÷ Úv9@#)Sh­±\Þ¨.]ú³Kò'—¯þÞïÏêë `‡øéŸ~ÓÙsçÞòÈÖÖÙMë°*j€0^øò|†Éä¤Láãû@ˆµCk”ؒ4BˆûÏaD)†Zë•BÛÐo± j;ö}å ‘uð…èº 7o¾póÊ•¯\ùOÿiïú3Ï@î_ywã^~ìÇ0{ë[¿õÑS§?W–N8T`탾ï!¥ÄtzE1€afˆ#7>Jg ýrjcÁ¤ïã×ü¸¡‚‰@9¼§‡ŽdýîÐk1:fø[ÄA²yƒ%ú¾…ºÞW»»®~éKþÒ/ý–‡ý¿Þéø†V€ôÞüÐC½ýÑÙìÔ$IRcy5±Õ׺C’ä˜ÍN#M­#LQ( !ò\€¶yÁ)Í ¡cL¯é½`ÐÇ—+ËXqü±V)òûÈ@Élø´r‘¯R¦PJáæÍKó‹¿ðâÏüÌòêaÿ¿w2¾!à‡“oþæo{âäÉGÎäù –L¦àC›¤ÖÑÍóf³“"aþ€ÇîÞZÆ‚³“^¯`JŒý?ëø0èø·rË¿nùdœuŒùÌ$ µŸ xA ­{4ÍZ÷.ÀÎ{êòå/_þÿãk—>÷¹£HK^û!Ž×øÉŸ<úíoÿÈS§N=v2M hÝ‚n© ”Áµ±éô&“í!Vnt¢8h r‚íkJz ŸºEŠ4B«Lßq(sð#Î1ÐõÐqù5¬?†ŽÎ;žY¸¿#eâòþ~@žOåææÙ­­ÙSO½Ô|á ¨û?h|#)€ø'ÿä==þø7¿ysóliÿ`ÎÕ±¯=}Ab6;…¢Øp‚oÿ\þ 3¸BÐ1HXb§8H/´\XÛƒMœý ŒgŽõÑ'¾ˆr\йҘµç ¡P¨xB¤iÀ¸l2\<ÁlvªÜÜ<³õÄÏw_ø‚Y¶¬ŠÃ¾€1þú_Gñ<ýÆóçŸ:_–3(Õ»?]3ëï…?M3L§;HÓbÀûëÜîx>ÏúP(ÇÝžºLÐ"L˜ÙÁ¡È8âžß+‰Xsë`ÕúÜÀºs„޲߆ “¥v t]®k‹5” –ËÝ‹/~ñ¥/ù¥ËwS utItEòÞd£¿îàãGùßñ¡'Ï{ê”®-ÉÑ }¿FøsÌf§\¬;„<ž’ ƒÏ}ˆÒ ;†q{ û…Nïˇüº¬n¸¿œ`„ÐF¾KèOpg™®G"|R.ÚÞ¿'ÿ ï[—8ƒ£tÛ¼A×­ÌK/}éÊüÁ3Ïÿ×ÿŠæ ÿKk{ðuN|Èuº7Jðu­óobúÁ~äÉsçž:)¥„ÖƒzÈäÒŸ­u4-0™œD’¤°Ž1Ïþº&¼•瑟qrŠ03L–“‡9ùl@ŸÅ±ý0lžg]$(ãíŽHÉ5!R®t\轓oî–UÚ¹ßj•(I2ô}ƒK—þâåÏþËÏÿçÿìccüIèPþ~Û›)@ð^ˆW¯_· ð£?Š­w¼ã;žKLBO KYq[ƒà•ÀÖ@Ø{{õêWo|íkúüÏþ,öÉDÛv"Ï­"¸ó‹¸ƽ6üç¾–™ }Õv„Ç÷?fïz×w>µ³óø ¡”îX[êÔºCš(ËMH)#gP–›(ŠM$I措P× ,»°êzM(dš rLQðû¸WÃvëcë(cjô:Nк­±_Â9Bë ]‹dQ"Ï.µ%›Vì Úç·áR‰óçß²“$BþÝ¿÷ßž¢›#¥ÖZ8kOVtHèÅ=4Û_w3ÀüfO?ýo9}ú±-0ÒšÍî†f {r”å¤Ì@ÛÏf§†Ð'# !±\îbµº1@! ħaH¸-–I†¥Ù#r€íç÷'È3Îêb8·ÿ|ì›ðíâ}ãëñ ûö!¥¯3 ‹¯T7Ü!h&’ÄÖ7ß¼ùܪþü…ÿýCí~±XŒ»N@;eÔ¤ |›W3¾®àû¿³§Ÿþö·œ>ý„~*ò §~¥:'üÛó (Õ¡(fØÜŸÎ×Çäýë0c‹È’Žý€oa8”8Džrmðü,ÆüÙÙŒBsâ0j^Afíã³ÂVh•»>:žqPHB›B($ g Š"Až%(‹ñÎwBÃæ¤üaäûØß|îÜ'0á×à“ð !PCÇžáäÕ^4Hˆ¤LÐ4•³j$„c°×à鱯pCy®Wz…PÅ Hßyê´¿Ÿèâx8ù“ûß$à©1ÓÔÿžÐ ˆ3dý+§°FCJƒ$Ò(Ëij åÕý§Ÿþž?}Ƙ@×…ÔâÈÏk‚>4޽|üãÈ>úÑô¶Ùl'¥•§0‡ÔfÏìÜ ]iŸpÑÀCÿ:I2Çý7èºjð⌫œ“#¸ã…r|»×[y̰t‘þ2sÀqÈ'û$ô™/è?(tË}úÜ÷â¿Ñ¼à` ¤²L M ò<ÁtV"•@š__|äÃx¡,¡µFBY^&ðÚ(!`ãÖö½qïgë^àà˜ÎŸøŠGù¦Çf³“)õ³!ˆC^ªêÒº‡Ö e¹=Ħ !RæÜè>÷}ƒ¾¯AÄð1,ò–<Žó‡Û¤”ȳ÷t<›XØhn¶’Â8è#‘ç)&“ Yje×ï{ßÿ{±,a`J9Š÷!Nçfº¢{eùiKøÐ‡Þõè‰m{(ƒQØ“_¥zäùÄqQ¸Æùí1(rBŽ‘9émwZðáBÂÓÀÇð"Äù q§š ªÝ>Nœqçx}6Ø;¼z8>‡Ulˆ”™Š{ì=¤ÐƒõÏ ²”È2<¿¾xÿû¿ðÂæfgŒA*„…=îX$à$ôšC &üæ^ ?p à§~êá3gμùá4Íܯ‡=÷[Øbq?§@Û¥!~?/¬©,àÃ’a]-Ov1NöÖÔcýðÿ‹}ˆPñÈzsÿÁCœ0ÊÄ„;LͰ½fÊÃß_¯6£ó„×N á?Mí£(R̦)òÈóëËoýÖ?~¡,¡y¨“ýt +ôš^»€àÏí3}¯q+øøÇQ<òÈÛ+Ë a9ýÞò‡t嬇ÉdMãTÕåüv±|/ø¡2aâˆ[ä6À…Ü ïãNn´ W.Ì>µÞ9GáŒågŠð¸±¢Úg¯  Ãz~^JtiH©­ƒ› ”EŠÒ QÜX½÷½_|±,akùZv‚9Ú˜AL î‡å§q¬à#y×#“É©8ÂÓ¨ ,u]­¿bøœ×oñ,÷ÙϹpH)Á“Ocû€¼xž¯¨º}¿Ï˜—+*‡Y\©¼à‡…ïf8κ뱯éØáöt=öµvXß ž[ÈSNRd©@QÜ\½÷½öâÖV«9ì!6'<æ„?š îìáãØ(ÀOüÄ©'β}ß!ŒñsEPƒ3œç[X—ð‰(þ§“ïRìàÛxaw[¬` ˜HQ¸å'zÅú˜6%ßD$°cѺˆÏèhŒF–8Ô‰a_³ÏÂRfW»HP)&³YLÊ›Õ>ðÅËR©(à ‚5æ(ÀF}ø ŠrßDZP€“'!zèm”åFBÖÜÓ8cÓr€´Vn±‰ ~{Þî$ÄË¡ ™RÈ‘¯³Ðv[K·‚*LâÛs–¬3Yñ‡¾7‘U÷ÇôЋ‡ûa2ÎÏRÖùcšh ÛAIA‹÷“D!I4²\ (L&™S„›«÷½ïË/•¥Ò²ˆË?X}þ Ì0Ôyß­?pLàïþÝÇÎnmݱ…-a-¯…- ÿ·>BšnÀc|_|n÷á°Ï„¡ã^9qB+ì¯ „‚[x1=ãú¸ì/>.Ûû /VY—Í #Eëf1(¸-ÿö59¼ó$‰Bž“2Åtš;ŽÏ­Õûß÷åKÓioŒ„Ÿ‡Ú4·üà òó „8 ð‰O ?uꉇӴ„R}пÇ;²öÓÓ7°¢ž?)Š’½Æ $crWXÂÆøC8„Š`w½‚uxlŸÎ[õãǰeØr4Ëļ ð;3ò#è¸aˆ×¸Ezïkd™…=“I‚Ù,A–L§7«§?ð—²tˆö.ʘAð ö(†ûØôG^>ð§ÎnlœÚô'fxöðÅíVسlQ÷l´gÑXd ©ÆÃ§(H88¿'dVÒ{)—Ï4ÇÞ‡cý~ãˆYöx&â3D܈Žç}æÔH’i¦‘ç“i‚é$C–ÓÙ^õÞ÷<3?›Í Ãödí)ëØçœïÏ“]·GZ~ìÇPž<ùØù,+œ0ÇU]{<Ô±GË “Uó `g›Ù|+ôD`eÇÝ×(š„áaL_À'°âœAȽ‡#¹£‡UéØaîÁæ1øw¡BÙãÐûðZxÐÀæAxž€o§˜³kCœI¢f yF°'uaÏyõþ÷åR–icÌÐd-Àû°pÇv$pŠ@˜ß˜ ëûÀ…8 ðñ#ߨØ9c; ðHŽfÖ]»,°fƒ$™‚'¸xc+=t\¢ê.íjZ=5š;ɪ¬kÅ!mÃ!Î&ÅíFÆq~Œ¶;ˆ+çÃ’žÂ@üv…Ãcþ¤kã!\ áð~’*d©¶žRb6ËP”Óé~ýîw={…„ŸUl‘S;à|zí‰Þ {ø8² ðž÷õH–èûžõÝTL¸‰õIŠÐ#Ël™#Í>JDp”Ïjèdòt†q7¼¬Ò×ÃÒ1ý±…š°#³å&Ñ‘×ÉÄôꄎÆá¢ãWHÃ'¸”þÜ`RJlnæHS‰éd^ó{¿z%K6Æó¤(!ìƒ0?9½€ç÷°dØ¡ ?pDàoü泳ى >…:yôÇ Íišû Ü2rJƒ 嬾/l÷Â"ì ¡[Yì1ý3EwìàŽ³A™ð8<û;¾'¾µJ8CŒ) c÷3 §e{$â1¿rÙÝÞ±85ÊI‚é4G– Ìf{õ;Þyáå,5Ú¸"VÁEV>~ N/›%ÝòÓ8r ðã?ŽrsóäÉ$Ɇ0¦·æ|ÕEŠéSìŸBŠa–˜'ÃBÈ ¡ÜÂoÚ YiŽÉÇŽ¦Òq"ÌÛ˜îNõ´îh†C•x1Šx;*EôçŽéÜ!ÔŠ•Þ ?Y{ÞÖh Öéíe y.ìɦÀt¶_Ó;/¼\Ê(…DJD‘þ dƒ<ÔÓÇ8ªÈ¡ ?pàÌ™ÇNæùlêXÏôôJ \·7¯6öŸ¸èA$€Ãù±}}l(Óãq‚r°ÚÜò¯/ŒË ÝYY<>î÷é‹ÊCzÃ8k<žq¼ƒË:ä6qå°!bï§ï#†Â4ÑI4µÑž"7˜L¦³ Y*PNöëw¿ûÙ—³Z)$‚¢6€6€^ð{`­Ó{èÎîAãÈ)Àd²Í®à4Ý9÷Ÿ¬YrŠyXãg ÌŽÚhPïÖ¹òNo )bGx}Ô†Ó• ;†ë VÆ´çÏ64bìî?çü°ý©+…†wÒu$üÊZÿ´w°G!Ï &S‰ÙÔ&¹ÊÉ~óö·?-Ka”ra3 C8Ÿ9ºq¬~²úGMøíO9Äñ‰O@|õ«2HÅü&³ÙΖïÖvh¦Ø?)ïßc;7xE‰áýù±•ääº:ØûÜ·‘¯1ŽÈk_Ïø5][ØÓÔî„ÊMçâÂï^Îá'gW¡( ¦SY†,˜LæÍÛßþüµÒÁÒÕè(0¡'eˆé ÆñzŽ ä‰Ç¡Íßó=ÿìŸ}O>ùþáÆœ={æDYnœÖÀÁZ=~,¬‘R õ¿ób‘±C ӻŽlØž;¯÷íäÅá@ˆ÷é|¼F·±ê‚ýNÿàðrH^ºH3‡v¼;„wpíL`ñ¾ Y¦]´§Cž+…âüÖò/š·½íÂÕ<3Z)$ÐÂF{”z(a• ‡ŸFĶ£hõù8ø+òSŸúÝäÉ'߯„ ‹ííGNxç×/FçÞ€¯÷<üÉÜw&š!)XH.8VhzPCWž„Gmà¬fðü6q1<]‹€4q ÷ xÈ’ûa?dƒÆµÊuì=¢‡B’d™þ,³Â?™L§‰ {NÍ“O½¸›gÆh+üF oí!Ðã…¬¨8š'\>ò$ÿò_þnúMßô½°Õ€O~åtzr“bèœÄ¶®Ã³‹¬ÁÇÒcÚs)ø,3*­ÕlaÈöÂéO‚iÇ: „oÂg†°. ùcŽgšxÖñqýñùIÁu´=‡<ÄéÑ.Ôi-^hL'Àt’"Í$&“yûä“/\Ÿš`MZÙÊwbpZ¬ÑšqÌ„xÀ ðíߎä_ü‹ßÊÞõ®ïTÂ’x†qêÔ¹,›”†Ä—Ö&Hfëxí³íáO¡Ñ0;Ê1sŒ»¹ÐÙ2ÊI’»m)B.Ä dx¤‡Îég°ëð}sÆJdŸÃ%ÂÎ á¹øqã¨H$(¹•Ha“[éɳe©Q–³ [ÉU–óö©§^ØÍ3­•‚írh)lV×Xkß (b?2!Î;L>üa$ÿôŸþZöîwB !ºèk±±qêdš&‚ÓȰÄ]áB$tqø/|Ö-*aá‹R ÙÖ‡¦Œk|ÃÁ8á%ßÈÓ$Âd‹zŸ$Þv|â¼ý°& ™Ík¥²»–ÖÐ#Ëz”¥BYJL§)Š(Êyû¦7]º‘gÚh+üFHééË=\˜“=bnϱ~à)À‡>ùÏÿù¯Ë§Ÿþjðãïý=9›ÍNï$I†®«±.‚ãcù|¹QÍ´·î!«“ ?à¡OY¬Þ÷­žqlßÃ~þØ!%Â~öð¡óŽ–ãßñsj3È´oüÐ.Ì©¦–דçò¼GžkL§“I‚,('ûíO\¾á¢=’Ä&·®¼Œt{ õdâ0Do€‡>ÇöðqßàÆøÔ§~^<ýôßÐ1ì¡qêÔcy>)øz¼ëëx}8ÐZl…$@X8®i ¹ú¼~—>%îŽí —$V ¼Û^¢¡†…ñ"*†'f]æØ+@˜ àÇæÇ'‚ÿŽç:|„ßvaVHÓÞâý¼CQX>ÿl– Í Êb¿{ó›/ÝÌ<ì¡V„ý³Ï¾yëÊ•“[J}ùêûÞ·¬¦SôJA%‰øåÿŒûžøÔ§þ>úÑÿÕ$ü „œX£˜°{ëïûYê¡ø…bcl<®°£\ûüBÿ ï—#;ΡSá¸$ä¡_Îþ3~]a”Ê ³=žo·²[}nƒ~·rQ÷ó¼GQôÈóe©1›Ó™íÒ<™ÎÛ7>qÙ ¿F‡ß¥Dáù7l^Û}xGë3éÞÞ;Îüæob€J©” Z—;ØÃÇ}W€oû¶6<Ú³fˆ²ÜÚH’|°ì‘ÙÂVçz úr'9œ5B‡Q$d\°ãö!V ìbaƒ+Ú'.Y £Bô:O†” NŽkÆ ²0«K >å[DfÓÈ2=àý"·ït*0™&ÈsƒédÞ=þØ•½‚%¹i…ÿ¹ oؼvõÑS}—¡®;q:«ë÷ÿ™ŸÁ Ð*I Ý÷ÇÛòÓ8ÔL0|ò“È'“í™_¿7îòægZ¾Ô'É|7Þ%b ;?kmÃhÏt]¥j¦œá6aÞ#+íg/ïøÚÄ”/D køl² Ú™hÌžÅI}z¬£k-~‡É¤Ãt¦±±Ì6ŠÂ`6Ûïã¥[e¡µÑŽÕéÂhÏ>÷èÖÕ«ì´mŽÕÊ`µ2¨kƒ¢xt ¼ïÑŸþiÌ„htžÝìîÝŒCW€qy¦ƒƒõ'³Íìj—ÙUÈ2‹ù'S (%òÌ`:w<²»ŸgÚhm“\RZçáâ ç6¯_?¿Õõ ê¨k i€®“PJB)ÈqêÔSg}ôÊؽÈ.üØŽCŸ’$/øj‹ÜÁ‹Ëi6 ˜&Æ8ý9LNñ$ïÔÀ…ŠS&(‡à×ø"+¯Ð÷µƒE+3<ûój-\?]»÷YBèã\º.‚<þ!¥åóÐg™µüeÙ¢,[L§ Ó™Át&œó;ï{ô* ?/fÑ/¼x~sw÷ìVÓ$¨V «•FÛ&h[‰®ºP nÙ {ì}ÿø'g[vî‰üòùÅÇ>öÐóÙ΀` ^ó;ÆÀ>m-rßÍðÑn鹕ůãzÜué¾õ:5Ò [¬P4(Ì ðs„½EãøèÔ1®%¡¯Üʲ~ÀüE¡Pö¹,&S1ÀžétÙ=òðõyšã0¿–ߣ.]>5»~ýüV×fX.5V+¦IÑu)º.C× h-a³Ù6"U³¤(¦å‰/οüe´‡,C¯iê ðýß|6ÛÙð+.úˆ X1G\ÓæbÜó~üù<_æ ™›gnc'u¥éûm»–M G\…Å£Gz¸¶û¤\dÇžÃ27{Ö²‡”^èm|¿wlNÂý “)0™ {–ÝÃíγÔÂ@Jÿregvýú™­¾OQWumÐ4m ´­€RöÑ÷JiGM±uÙ;;m¾ímo{ÃéÓ‡nD_Ó8T˜L !’˜×âáÿ‡TgbrÆ¥ŽfÍû1ߟò 1d≪8£<ŽçÛkêº m»Dß×C¤Êo"…åþJXš§+‡ñ#°)—в‚_–Vð'““I?ÀžÙL , 66ý#_›g©ƒ=`._>=½¶{z³k¬– Ë•FU 4pJ`Ð÷Öâkmœxf®” Î{êÜ÷}߯ÙÔ¡×:U{¿íÛ¦Ó'Þp>M3Éo.ïßÃaƒ’ƒœ$ ²¬„ïĵ˜úÀŸý×ëâ\ÿÐ]縊áú´î˜PÇÜ<ï‡?‡Q 0]\š„ßR´«Ü¢ˆ…>“‰F1&¥DQÓé²?îÆú!¡FEö´˜´íÁIŽ®ñý÷3bqêÀâSœ¿˜h”¥ÁÔ9¼³éª?wîÆ"I­ìäÂýú‰Y×%¨W«°ZIÔu†¦MQ×z{ZëÏ›v™à·S¨7M3Ålò¦7]j>ÿùny˜òôjÆ¡*Àw}×ÃÛ³ÙÉÓ!™?T ì~fðŸÙEðÊh†ˆÉj¤÷„ü{>ø4Â".Á{¾PHâJ1ò´nYmsëø*'üÆÕéjWµe¥A!Ïõ(ÒS”“6Ú“L§«þô©›ËDŽØ&„ }íÚæäúíißKÔ•ÁrE°'AÛ¦h[‰¶úÞF}´€!(‡{ßÀ Ë&Rˆ$™Ï/߸t ·£½¹q¨‰0)ó”EaúŸg‚×e[í°C ´ˆ8‰´Ž6ìçЇ8=Î<86¦k÷¥‘<ùF–Þ³:©¾Ùçø9i{®y®ÍA#Ë5ŠR ,$ÒDc2©ûS;{«DÊ&¹ %´”л»“77'}/PUu-P×u-Ѷ6êcãýJQËjC$»Ä]¯í¨fËNœxèÄûß?Ùùª—S¦îvê ðßqæüd²½iß…ëx……ï!,ò‰*+ôiZ8+÷»ç´ƒõ#dp®çÜ[K®øÂ(Ûòóúv‰>‹ké Â9¸fx¤)19{W²Ø¹ºÝYÖ"Ï[dEçfƒéÄ®Æ^NuæôÞ*MH[ f ÒwolNnÞÜœô}by=•Aµ’¨ªuM3@‚¶5è: ¥D}¨¦!lÉW†·ÛfY!ò¼È&“—n=ó úÔ«»‡é¸cÀrÊó˜²LN-dïè*Õ#Æî1eÙÏ(ü{.RÅί_6”÷ÜÁP€Æ-}µAzØèŽFYêÁÑL´ò”“‰Ádj ØÓ('­:}z•$ÆÀökƒÅý7nn”{7geßKÔµv±~º!‡×>ºh[åâýj`Ú’Ã;n4æ?çí+·¶Îo½õ­«°èa*€Bq‹Ï^ Ã·\9¨„Öþ é¡C „¥„˜Ð{k¶Žz±®ó‚gc†)ëøJ¶í ØÃ°0'á|í`ŽAQh‡ù½"ä…uvËR`R [Ã[´êÔ©y•&6ÎOÑ) oÞœ{{Ó²ïêZ9èÔ5Åú%ºN ï-ü¡d—RÊ)_^ŠÖ^ó-W¨9Yز&ÏKqúôÏþí¿Ù!ÊÕ]CõŒ1z\¯ Ý…?8ÆðçpÖ§F°f ‚÷0ʉà<ž‡?|Âðop5™°‚î•Ëž‹œ]¢5d™Aš’7 øG–k…õÊR (%R)ç:ujQ%ÉàðZn€¹qcZîíM‹®XÕ u4î4ˆnM—éUè{åZÍØÅ¯­`àpfó±Öv[ëØm77OÍ{ìáSÀ¥c:ÔØ­1½ [˜a Ìëh_÷çÙWN,ãP†Çøy_N;x83ŽäøV&q± ïòÀ“EvØëå…éÞêSbË °ÇZå"=Ei­~QX˜8‡7K¼hÔÉ“‹ÚRµ!©þ^H˜›·ÊbZ(ÁžZÖ¿m-»³ï¾7CŒßõ`õcâžÿ/øjc{dY‰íí‡O}ìcǃ2}¨  µB(@‚ö÷·#†$€€RÝð¹§8x¾Íú$–?çéûýžijµ¡°c°ð$KMP‡°>=(¬Y8'Ü_Yny©QºöÉD"Íò¼ÓÛÛ«ZŠá !™ÀìÏ‹b¾?ËÉòWÐÔu%Q׉³þ‰‹÷ ‡ÿ…› ºÁðÈVøÛ<“•ßûYØ!/Ms!e’&Éå›Ï>‹kÁÂ8d*DšQOŸ˜i9öEfÂŽjJµnàXŸïïaKLñ¼ ü?cøÙÁ× ð9ÔGz4~Šö„ñý|p|]Ò+7( ¢HS+ü[›«FÚ{ ¨U"aæ‹"ŸÏ§¹Ru£QÕ]Âý^ð=¿ßZÿÞ­³FÎ-·úcëÎggN[áe¨BlnžÙ|ÛÛ6¶S¾îHùü†?yœm?S™Ç„\ë¸/и¿&sîyQ‹Ÿi|ü>Žö¸£ÀûqÁJˆû¥¤p§ aO®‘ŽÜVP“Zƒ²´‹Òe©@–uzk³jלœ 0‹e‘-“\)ËãiƒŽ¸=­`áN›è²<Ëõ1èûJ…Œ[N5?³¯íoû^~â˜$§O?¾ƒuÉ’#4YTçoî˜ÌÆ-ëØg[¡vFŽ<ì¨Æñ»O~y&*§6è`Ÿ4Ç#?"âõ˜Àò“µpþæôήux²L¬Ã›·z{kÕ$‰q1~7˘ŲÈçLøëZ£m€†ð~c­~ÓX~ßÛ°'Yï¾ïÑ÷=Äé à×…j‰Þ` XHð%¹ òÜ8á·¯³\ïK'üe!‘¤YÖë­Íº•ÂåYzÅ„Ÿ`OÛtDÛI´…>ïSU½Ù*lßwЪ r!|Ôàü¸I/”áí`zäùLlmßI’ÃFC½°®«[c” £,1…9žÂbsž”êû>C‰Æ¬¯ÊòEì>Ëoë=±r’²xŒAø}s*ëì0§t˜¿`Ð'w˜¿6Ô™wzk«nêœÞåªÈ–Ë2SJ i¬ð×çÇVŸ`ÍôZÁ·ø_¡m›¨Zmìy:÷¸GÓ˜²Î÷¥è0›ílýèbã0åìvãg£aøÍ¹;œÁ‡wVãíÖ±ïÈ!]9NdqÁç ªøg<¬÷õ «¸ôä¢Ìn–id¹B>ÌV, òB"I,íõæ¦~­­åw}{ÌjU¤UUXËß´-œ¥÷QûXñCÜ¿ëj·ÉÆx®OœX_Í»sxÂû“É,ßÞ>³y˜rv»q¨  Lß·:´òÀ8$G€ÆïTS`::ásRˆ-¿ÝŸ”(^¬:„c¡À{¿„V`¡Ö„4ä æpØ“çyæCI"n'ü¨ª"­ê<³/Ð5Vø['üMCB/ÌO–ß+€AÛÖè{>KrèÂg¿õÔ”¸÷¨ãŸ ¤L1™lOSÎn7UnÝBÓ¶mÃ׆- ÃÁSO>ã¡NáJ=;ÔÆº9l aW4ZìÄÓ­ù¹yDŠfß—3Œø˜ØF¸?gÈF~`…?—HS ÏCáâü LUåérUdJIt±¸ßYúŽ­D×I×ÌŠ">b$üÊá~"Žg_ÏÊåÖßϦc!örã"e†­¿ówŽf4èPà×~ }×­Uà#ÎÚÆt¿ }g Nz(µY0$ÌùsyJôº?>ìç•&ÎòR/þ É•ûª®<3ö‘yî`OÖëͺÌòcaOUåiU•©1@ßéÛwes¶ YŒ">JÁYy+ü]× \•fŒÝÇÖ~ü=uégÍA×<ßœœ:59’ ÑC÷ÎÛv¹´|~ Æß±sEß…¾BñhÛ ±BÙ™À;º|Ñh±|9¤·Š|fŠK"mÈ“gxÁf€ˆÙ™ä9…:m´gcÖt€b°'I`ê:³˜ßØPgÛ‘s+‡G×ùYÀÇú-þ×ÚF€Ú¶q „Aš×Á›óëÑ}ˆ£sã嫼bäy™Ìfm¶¬­G@ ëqlÝXש-æühݣËÃyD!ñ‹þ,_°îøÄGâ¸Øc~3Âýv! ¼0È ŠñÖ1N$rÇíÙÜhZaã쑨ë<«jço\”‡¿iÚÆ=Fc¤³üú¾îÏZ{ˆÈƒ cê)ÅŠCôˆ14R2EQœÜú«õè1D]”2Ö½cöoLåëfÁ¾ ±?Ï @ÛVÑBÑ žº­¿ ’øÙ€ŠZ|U—%ºYjƒÕ!/ôðl™@‘3‡7ëôƆÅü<Ã+%P7yZÕEªµ@Û[Ìß9!'g·m˜"8r›ùÃut3hš ]×¹¿Ùß¿¦Ð=s±õçEF`¡QD3®dc`ŒD’dùööÑë"wè P×h•ª]è&ÄñM#Bñlà•B@©}ß‚ 0áü¸ù-í^Ï‚ò?YôfÂý”è¢j®,ÓÈ ÇñÏõ ?áìiò¤ªóÔZ~íi;¦óÖ¿m$º–hÞ°¸_¢ï5ªj…¾ï!¥ÿMüÞ…÷‘Ï †)?‡9>äZþ0+Ìé#€B$I–lnf¯ÏñøwÿMÛV _l‡·Þ÷{ ψ,šý®m+ø¶¹ ‹¡È#Ì ãÄÇþ">öؤwx=§ÇÒ›Ëܶ'/ » ]žÛ /ÕÊÛó8Ë_çó+é,¾AÛ ÔMâX Ú&Š[êÚZ} àð¾B]¯ ”-¥ø¿»«£p'oÎÀ\°ïŒ£.Ce·³ 8ƒ²œåÛÛgŽ\BìÐú¾™[ÌN«ªÇ ËÔ£2lIÎ-wÈÛ±K•vn°Ÿá‡Oã,¨ÛË~UP3ªìŠiif¬å/œ³[$Ìò;á·Ç¨õÎò°§ë šÖ³8'·k(âã“]>ÔiÛ˜7M‹¦©•ñ=Wb=g¾9ò÷Í¿«ÂâÈOØK•¢oÚ5К¹HБP€ºÞßoÛ•b\ði˜ ÿú¶å\!l´¦ïëàOñ¢ýƒl!¸_…¯Á&¾<Ó¬¿öP”Ç ÔÏçH|~Í„_ ˜¦ÉÓš`ËðZ|oáN;´.q±ÿ.aáP¦éPך¦u|¾„öpÆ ë#ø}ŠÃœc¼û üÿ²‘(8ƒ– (6'?~´ü€#¡W¯bÙ÷ÍÊöø·wŽ}ÓZ¾J‹¯Vâ|}­{tÝj´·~4³„“Ÿ7¬ìŠ£=VÌÊÂòzŠB"Ë€"ïõÖVÕaŒÒ–Õé®ÓTuž®ª"U*qÄ6WÌâ(Ížßc!ÍúRÁ‹ÂjUaµªÑu: ;pVkH?Ö%ÃiŒ×^C¤6ùŽÏ EQï~÷ÑŠ øâÑ+ÕÕ„[à ·TqÊu¸4œéVtiÁèzᬢ H–Ñ1Ç<Ã"?:Š÷»Wa¡OæX››Uc»Ø…-Êë:Oë¦Hµ–Nø Þ$ÞÒ7¾¦·ëK~«{TUªªÑu½»n1Tyqª¶»sBí£bÜñǰ-qráæ4ïý½µ ‚Ç ¤µÃQG` x昺žï‡ MŒ“]ñô½ÿóúU>lX”„ìµxa(ÔÑpècK}“a(¬ð—¬Œ1Íz½µÙ4œÏ/íR¤ºi¼ðw­Ïð¶«Û­%šZÊP׫U‹å²B]·ÎâKõúèÀ§ ŠtOúÞu!ÑК‡4ŸD¤ãùB똳€Ašæbcãu´v,—í¦©êu™] ¬ˆóaî@˜–Bë>ÈóÞã?Ð=Üx«45¬[ƒ„¿(`[—Ld™D‘wz{«j¤³:m’«H•’h‰ÎìˆmMí‰mM#PU Ëeƒå²AUu®…!\ÒË–8R€€„ßC9Þæ1ì¸1vŠCg9ì­ :ß'4ÆØÒ˘b­µFQL²ét³žíñ%Ž~±pZ2¶÷‰œTLðî}.ôÁïŸvÛi7}ØÓó¦À`•ý^Êišçˆ£‡8ŽÔ `³ÂË=»éúÖãÜ/BG˜·6'ëãá=ä°°Ýsz³åùÌ¡dWY˜ÚP– ¦eŠ,(ŠNíì¬jKɶÂÀ8á_™ê šF¡ªµëÏ)¬£ÛÈÖÒ½óƒ7öJÃä<û7ýåŽ.}Öu¼"ŒŸ›+ qËbHБRhÛêå¶]‚fq C<ßµÍã÷qVØ‚OǰǔPªC×ÕÃ1Éé¥Âvâ÷øŽÍÎò—ÅÄ`2M0™¤Öá-:uj§ªa£=î" áϳ¾*`w¤6ß©™Ú–{Š3ÈÆúúf2Ävõ¿ß &õòûÇûç8¹È~¨L´ï­õçÆdýÐÐZµ׸Ú8R®\Á­²\Ýʲé ß5n u<ï$\Ð"„?!á-\üB ”é´ªT ¥$‰dÄ6$·|§6{&òLbRtêÌéU•$&hQ.¥þþº1hj¸Å)¬à7ŽÐV7¾y=½¹ï khe"ÈãaHXßÌ+Û0üîõÑ?£†]¸×G…<Æ·¶µlÏØð¬‡øI’$xÝ~Ôµ¹Õ¶UE\˜öä8?Æ>¿ûÐ!l+% u cd™`ÑÂù®ŽwL¦)¦Ó Y&QNzuæÌªJSmx’+I Ë<_,ÊLõm«QÕÆ z%QWUÅ!Øj-bèä@¼yb´ŒV1ÌtÏû0dH‹ðŽ0;Òè2.(ò '„œr:G˜§‰›Š‘¢%⡇^W€ÛŽûoQUÕüj×ñY€ã|^¶È\-Š`Î!¢}¤R M †5òNð-ì™N ¦Sé,ÁÆFв˜N{uîìr•eÚ½ÁexÍþ~Qìï—Y×ÚéìjŒÂ=äð¨k¯v­.ùñ•]`^ŠöˆÀò~ù¦¸f™ )u®ö¾9¸a탼ì<6’¤”µþþ¼q(ˆgk$±±ñº¼âh}¥i®hDV‹ ;j®|?š H ,¯G 4‡4µKIÙ"IZ'üV ŠR`2M0›fÈsIÙ«sg–«4Ñ·'I çó"ŸÏˬï%êÆ`U»,Q#äñ¤6kýÉòS ‚O5Ì”á%h&¡Ö7ˆ3æÔÀË·p¤ç0ë~»¼‹­ ÷ÿ”«‰£@á"‘eùº¼âøÙŸEݶÕå¶­]¼^–|ó“·þ^p¸ÒÐ 'ü–»_@švHÓE¡­åwÂ_ä“R©³çV«$Õ V§´˜_ßÚ+Š[{eÞuÂõç×vi¢J¢©$›HøÇVŸZú^>Ö ¿ûµX‡ÓíoäaÍÐàpÆß7ÀSÅÙ_¶Õƒº¶ /{Ž8;,Øçc97FéÅâhä€#âcoO]NÓåÙ$I'cs˜#Ki‡¶ý[Ï+œå‡«Ò"È#lÔ§P(K`:+±¹™¡($&“^?¿X¥ ö¸5¹ôõe±¿_æZ[á·/«ØjDù!Zá}JjYÁ÷xÞÏ`¾„3Tx_ÜÎ×íå„ÿX¡÷‘£DH÷I²}é;?³ØYËFÌ€u¾C‘bß¾oªçž;:‹fÙþý¿G3Ÿ¯^^­¨¶—F }0̱À·ç«¼S¢ËµÀÕìÚhÏd*\œ_ ËʲWçÎ-Wi¦µVVø…„¾q«,ö÷ËB+²ü´(­à¢©ëZº•Ù©‹ƒ‡;qçfŠãS5yƤ=žám^ÂRÇ1 ‚^œŒÉsã¼€ü¾Ç üáX]²ÇóÇ1æèä€#>ÀÕ«ú²1óÓ@¾a—ö Pû§OßóÒâ0ôi†ýl7 ¤6ëôÂÅùffò<Á¤ìÕùó«U–*£j]bWcœû{…[N¡j4êÊulhªZºŠ-ŠñÓ"ÂunÎꇖچ,yuÖXU¿.AX³ÛãyÅà‰­8"ä£n$´RÚå¨RMÊÖÄ!X QÃÙ÷È0 †q¤gøÍßD[UõŪZ¡mª ¨k Ð!žÑIQîúxáç‚?™Ìf³Í››&e‚ ­~dµÌ3¥ à¸=ôîî´¸y³,ºNbYi,V«¥Àj)±\J,–Ë¥Àr)°r‘ p©"ßµ™Z™X…æ}‹¼¿÷ꉩ 1í¢FÊšŽœåqM5û6R4»Ö°½{\cþ±_Á9JêÈÀà(|æ3¸Ö¶Ëk¶Ãƒ-¡hŠ]ðYºVèÔ¿'t’ù"Õ$üÄó) ã,¿Ät–ac–#/LJ­zh¹Ì3¥µ[Z èë7&¥µü«ZcµÔ¨V@µ ]í!b.¿_ž4ì^ͳ¹±Ó»®\”;·û·íKâDV<+Ü~4ê{ ªBfg¨P!ãŠè¯ÙÎ JuhšUGð€Æ‘‡@4êº}1M—§ÊrKJéÛ}X§ÑOïdá1,WD¡OáŠZ0D|r×—2‘˜ÍRll(ŠÓR«GÞ°\¹2}o+˜¤€f÷ú´ÜÛ+‹¾·qþª2óxß;º¶Œ‘÷êŒK/c$Ãôž”ÇË29)Íö6õ‚Ì™œ1‘Úx%u}•ø¹úžÂ6\R p,føÌg°¿ZÍ/ @d©¦ÀjEÖ¨kû¹Æq|0Äùg3›66K”“Z=öøbQÊ8zƒVøõÕk³òæ²h[‰åRc¹ÔX-¥ƒ=Ë¥Äj%ÈãWd÷´¥ÈòSžÂËìà1ùЂNJ2îgÄ;?„-QBÇY0¥A0ƒØï-4«*ZžHG3Ž­$þ'ô:.FkŸ;> ãØÌ°»‹Òt¾$éIKk&¡‘ v…ôgP€RöBˆ!zAQ ¢˜L3llä(ËS¥{t9Ï3où©QíµkÓòÖ­²è•@]),—Mî u½­/^!lO¡N^î…9LH…Ÿyhä‡wúC¶,ïðà·áŠÖSð,®Ý^J«¤U%\ævlõ½S¢-¼~Ã;!¡T«êzu¤àØÌðÛ¿ ½\¶ÏÕõR¡ãE”s|Èé¥hÅþû˜L2L§Ò4ÃtjÔc-æyÞÇê*¹vwgåÍ[“²ëª•Âj¥Q­ V+`µ"Jƒ‡@|!j«}O…æ¼S[زº4ðø;Ÿ b¸ÛjØÇw†ÀpOBÿ€'ýþ\¡”²AëCйýµòp²WÔõI/¾­1ûû×çÿá?`uØrÄDZRøÕ_Å|µZ<ß4„°ÄBn‘È ö‚o…½,-•y63ØÜ”8q"Çö‰“IŠ­-Õ¿éM{ûy®t²:õÕkåîõIÙ¶Ë…Âb®±˜yâ ¯§5xÈ3vn¹PáÍÊ„áK?¨ 3Ÿ]ìça”g[“+ˆ”ó/—Â͘±oAtšÃa–ž† Y+ô}[ãÕÇPॗðâj5ßíºRJ—¢A´GJ ôfjM^–Ói‚ÍÍ [ÛÊI†­-Õ?ùæýy–j­(ÃëX×v7&×o”e×+gù-¾—NàåPØÂ¢&¸C TðUæ}©bHE gÝ[j_sâuLßËßÏ W*:n ­ÂYNø –Kr²E¤lqÙi8;ŒKPÃ|ÍZZ«æ°e'ÇRþà`V+õ—u½¿´" Ã6ÃKI®[›¶¶K”“[›}ÿæ7ïïç¹ÒZ#€HèD ÂßË¥ÅücvúˆOyÖ—*®ããpKE×é=ÇÚ¼Ýb}R4¿??Îð)ÖA!4êÚ ?`aãúPé:œOÇð¹—uCˆ}_›ÕêÖë p¯Æ¯ÿ:šªjÿçj5ïm­©pV_ Ñ+øÂE{$Nlg8q²Äl–áĉ¾ë[oíVø¥»Zæå—·&×®•eÓ+ü å.©%°ZIyB'ÕëÚÁ[ˆ{¡a#bd©}Ä&Î ø{1. µ#,JçTe­*`µ²ÛIÉ÷ #D~?îC„´ŸU&š²ÂZë¾ëšGl[€_ù,º®þZÓ,]E ?9¹–ß3JlmåØÚ.1™fØÞîû·ZÖ‡mP0Âñt¬¾æsÁò=BÃ$ܘÿ²=ýuë³Ã)ójµ·ÿ»¿kޤ©VÕw2Ü*ê<ü …@b ²àmoGuõå®(ŠiqâÄÛÛuûÎwîÞÌm%W«8€yñ¥ík»å¤m„µú 5dw½Õ—Œº,àW`‰—Vò5žxG’ yLmöîØŸ/ å÷ç‘™1Í–/Ú(ÑBÂs‘ †–›Gv„Hœp'c+Dâž%¤L‘$ ¤LÙgö½Ö®]»på÷~oq$àXqÖ ?€D$­‘ž<|÷÷˜kú§{r:ù;Þqëf–Âo-?`^¼´½±»[LÚ¨*+ü¶Ö@¸èˆ Z•øv„¼dÑ[ÏБwZ×àòÂŽù½Bq%âç£ã† _‡E?–Ìf‹X(7¹<öšb+Î[JÆ ©¾!†cX¾®Ã¾·ŠRU•šÏ¯Ï[vÇBŒAlžˆf( •©1HgSÈ÷¾¯Ý¸š9ŒRH5ZÀÅOlܸžOÛ¨VV+…ª²˜ß/?W»Kݘ-ä€pEËÐù´×~¶Ž&Ì1?íÃgr¦=iN°Ù…ïçÏCÄ7Ë‚µ""³yXÂï+—Òí5LøÇx>N¦ù °ŸDp¨ªæÕ_þe·8l:hy0Æv_`–_²G uJH j…d6±ÿ¦R¶Q-Éæ…ç·7wwóißõ#W¨*ÀߎܰŽl¼#³8í#h2æàÇÂOBhX€ñ}8V÷ŠågZf©ë °©šÀêÇ'²åBÎ["˜=|wmšÅüwR&èû«Õþ­?ú££þ¤q¤€VV Œí¸,Œ±Âïp Ÿƒf€C¤$ Þ‹/œØÜ½–O»N ª:,=V•Tñþ<ï›öø1†"¡ðskÍT›7Áv¶Å ÷!D èþ¼ž!¥%¯Õµqq}žPâÞ©ñˆ3¾ä¸oG0É+QÈÿç$Úva‹›GûÓ8² àð>àARˆÁò'ÒÁò«NQ$ „]•€yþâöæµkùÔfx{ñ`°k™ÃKaNÞ“‡/D ãå‡Üâ’ÀÒvFùþa)doRüÝâõª²‹lø¸¾‰  /|ÏF~øÎÙã,´‡CääÆñ~Ÿ+lFª«[·öŽ,üލ8ØCˉxNð  …ðh~ ÂOÑý—Ïílí^Íg}o°ZvX.{Û¸ÊUŒù5wášÑú6äÜ¥Áë_CFjb&溰#%Ðx3¯ð|ëÎÁÉõ‹RÄI©Xøù±Å Å….þ>¬I 0RP®(´/Eˆº®ÅryëÆ/ÿ2Žý#§lA9`ð»G çüÂo•Bë+ I®¯=»³uõåbÖuvyÑżÇj¥£¦´äðòü± dz&œø,à1wì/x?‚¯V)Øì FûÄÑ™¦±¥ÂhL|}|q M`×(áçÐj qÂð(ýŠöÄJÕ¶•ÚÛ{ùÆaËÓ+#£Q¤‡ž¹Ók­¿ÃøVè™å0¶O'ýµçv¶¯¼\ltÁrÙÙˆO¥QUÊ}sª8üÜ»+%š¨i,ÿÌ'®bÁöûxÁäáÓ0?@·ÂãŠÔ©-¡ZÆEéœCDÇ o­?&·ü~'óÆTè¸‰Ž £QUûó/}iqdß4Ž„h=Dz@ÿ‡SˆDˆ0Ôé>KA¢ ìC‹r À|å™SÛW®]§±\´X.{Û¹­ökrµ-†Œ.Åøí"žO]ì0üšÙcû´½Öz”Ñå‚xa1ºw†…‹î˜ÀO8˜{ï­ø¸kƒfx~¼Â#mËy><ûëcüÃÖ£×RJÔõ×®=åÿøèFh ¼ðãËgí…°Bo ¤~A²E¬Î¯~õÔöåËÅFÛTU‡ù¼’Bê¤ÕW|­wú€0iE™Ø$á<ߺ$Ž”ðµ²¼ƒ[jN…ö¡Qrpmm®V„ô‘%þZŒ¬?3$£°'Íúë¡Ïé˜Ü ÃûÙl¯fk<nݺ|ãç~qí°eêNÆ¡+svËO‘ y‚ ?¬å§°(0ÀÀüϯœ>qår¶Ù¶Ëe‹Õª0ÿ8ÌéPN7fWÇe¾ŸOS_—ôâVÝŽXø9MÚg€û^ Ís),'Ó¸óæøHc€o¿n)#Ÿ‘æ4 ÿž†-C¥¿Æ#V)%‹[ýÕ«/¼ÐuG«ö÷ q¨ ÀqætÂOI.ã·Â/ »ó öè?ÿÒ™“/_É6ûÞ`±h±\¶Ži^.y¼¥ô‚ ¬‹ãS¨2 chÛv¬¼Ð¯ÐÚ°úoáí‚bíqý= g!î@Ç¥Ša2Í/"»l‡”f‰±óg‚=9®ï‹›×ñõ­Ã”«»‡¦<É—{-Ù÷!7ˆ®m±¸uó~¡»y2õjÇW‚=1·‡ zÂa}¦£5À ¿L ¡¿øÅs§._N·ºÎ`>¯±X´¨*Ū¶øZ["€=î:˜³:\¥»VYWº ßóA Öqa§A 6š…Bø4ŽÜøã¹³ÊÀî»–˜v1Îüú}mÄ #«ïÛ)'·…~Ít ‹[êå—/¼ˆÐšùqh3ܽ‡çùµy˜ ,Þ7ÖÑ%‘2–Þ õŸýÙÙç/Š-Õ+T«ûóU¥Ü:»`‘JnWQñÑŸçÒàžÇÂÏ…Ž,«=®äxØÅÛÞ‰ÅðÙí±=¯%It*ùkÔ¼ á›Ïì†P‡;¾!æç "e¸­”)”ê1Ÿïî~æ38Ò¼Ÿuã)‡=ðÂOÄ6†°§ðŸ €ÕÂi¾øÅó;/ší¦î±XÔX.[4DßKV¹%"ËÏaϘc¯Ó}{’'·(.NÇ&÷ëŒC˜z­Ë2W¸ÆçÖ;Ûq–š¯©r}økÞT°Ùz˜DÅ2‹Å­îÖ­ÝËJ–îåx ey)ÜɉmC¨û ‚ÆR¸µ„¤þoÿíáÓ/¼ ·›¦Ç|^a1¯±ªz´- ucR(%¡m݂㠯¿D ¯ãíH¸H¡Â.pžBá)€-l«ÆB!T®1ŒñaÓõÛóÉ8LºŽÞì“S »W\…¨HÆV}µm›7_zéÓŸVÇÎú@¢h‡?ÕbÀýì`0`scCR}á Ÿ¾xÑl×uå²Á|^£ª{éi¾oaL ƒi€ïyV6~¶äøÝC Zp‚7¿ò9.ØŽÁ'9‹4 ™údÕÁÂoG,м‡P¸_OÀû a{¨tqg‡14²Ϙ‡i­1ŸïÎ_xaq,­?ðà ý §ó'`Ž0|t0B@‰ù“?yèÌK/áD]÷˜/j,ªªGݪ§²E۪ĘBt2‡”qé³Ç󈎾PÒ˶<‘ƒ€ÅHQ|[öx¦-yc?àF{xlßêDòwâèAÖ~ÜTØC:¢5Œ#>D|“2År¹‡ë×_zþw~í’£{>î›ì!‹O‰.âø€A„¾€àQh Õð‡çϽô¶»¶ÇÞ^…Å¢BÓôŒÏcX?~Þs³F’$H’@¿ètˆÇ=œñ™SÏŸ¸²pÁ Ë©ëBXÍgƒé|q\?†4îþÃ&uÖ‘ßB'6srKFu<ü‘l…Ì“2A]¯°·wõ¥O×ï— =ˆq_€ ?ÑØsB1}yÜ#¥[áÏôýÑCg/<§·›Fa¿ÂÞ~ªêÑw@Ó hE Â…tfË–ŒQB¹?Õ*ƒ}½Îà-Lè³rXÓLâd·ðcáõ÷éàìnØvƒB{‹¶/€À‡‘l_Îò.ìqhwŒd¸Æh,—×çÏ?¿ñ~Èσ÷\(ÚÃBœƒÓ Œ~Hr‘ãK‡±ÂŸ˜?üÇÏ^¸`NVµÂ|^a>¯±ZvÚµ6Á%X+r61$™üÐRCëRZE Þ•¡Ói·öqsûy8–áë ÷%nG“ð÷Ž¥¬[0ŸcÌå ÓÇ\ °ž—³>9¾§ã{§7.ˆ_,nª½½ë~÷w/ô¡qÏ€•0ò$9»{†dY{žDÿ—ÿòèù ô‰¦é±¿oaÏjÕ;^oSBlɘË ‡ Jõкõ¼ñ¯ØqŒÃ¤Ãј¥÷QïHúí×·?;Èô™_uLY³Í¼ ‡5a듸`sÿ}˜“ç8“ޤ”hš%öö^~þQùb—;¯Y˜ÕƱ¶8ËË­þàà‡ÂÿøÙçžÓ'šVa1¯1Ÿ[‡·m º^ ïˆ Æ-,ñb|ÊÝàÂj©ZˆØð^˜ý £EÃ/…&½ßÀïÏÁ×a·Óƒ’$ôþØ~¿Ø “c<Þ'ò8Ô © áû0Q–$)Ú¶ÁÞÞµk?ÿóí ¯UnŽÊxÕ @áMögä0P•ƒDW嵯ùŸ=D{þ¯ÿòÆsÏ_Чj—äšÏ+¬V–ÛÓ+Âü8ÖÀGˆ—ÃmxHÒþ&!´¦®‚qÞÉR®²õP†oG–ov…¯“_¿‡L< Ì)Õü3žëìš¹2‡ÂÎ*qËo³½ûû»‹[·öŸ½gÒwÆ])ËæÒLˆØcÖÜ`Þ"4®&V©úíß~â¡‹õ©¶SØß«0ŸW¨ë>ÈìÚµ)r¶ñSû88΢†‹BŒ-¯mUb÷Q£còˆ ÈpaôÇ=ز‡73ððw„T ¾íºV%Ü™õE-ôzõ î3¿ô¹%ºÝ¬oܸù•Ï}îh¹ßí¸c`Î-î 'ì<ÛËaÏ€Œ±æÏ ¦çÿ|ÓùçŸW§š¦ÇbQaß ¿¥x¥ÇÇÂÆ#.$žùûîÖŽ>ðøè,©t-w2üŒÄ! 2o‡NÛs˜ÇgÊ •<ü›Â¿,VŸ óÛ,—ûêÖ­kù¹ÏáH·8y5㎀[ì!A>Ñfõ ã“ÿg8,ØSšßú?Þôð ûÓU¥°\ÖX,kÔµbQ3×<=!tDZ [ÓBŽÛä¤FÛÄénGH©àÇá=}Dpýq2ËÏ0ÖÄ\ ïäò}x‘K8 P­/ÏKiy>U5ÇbqýkŸýìñŽ÷4^QrrÝý–\øÉ±X¹"FBFk)fæ×ýo¸|YjÛûóVK²üfqÚ0'ïÉ;w!Kr¸`±^ðÖ âz?Hèèƒý¶wVñhV¸¿ç5ÅáÔ¸z‹/È énÇq?TÕÂìí½üµOÇ–êðJã¶ Áž€Í ã‚Ö…"þ˜Pi8៘ÏýÚo¸|ÉbþÅ¢Æb^£mIø“¡=¡»Ø}yÇ> pþ  zo/?ä÷‡–þNf„0ÊóZ_såõ#¶üò„¿#æç¯[·+tÌ>a·7 !RÔõ·n]{î3ŸÁK¯B®ŽÍ8Pxƒ*f5áÇXÈi&àÑŠí!ê<…Ï}î‰7\¾Üž±Ä¶ K{ˆ?ï“[`VoœA cÞa*¬{µŸ…ô„Pè_if¸[«û×<ºxAõJË®lxæÿØá¡UˆõC%Ž'eßÛ\H‚ºžc±¸ñüg>£¿nÂ;@`V^Œoò°]l;IØûž0¿ÐÿÜÏeçö÷õ™¾—ØÛ[¢ªªÁòk8>--$ „]f4V{ì±P¯»F÷i: ·¿ôÚ†ÏU¬£cÄP‡³K¹€{ʼn\}âŠ/.ü¡åX­ö±¿ãÙÏ~_÷ }˜ðóÁãý±B Ÿ\`è„?êæÊüå«ý¾³­¦iÑ4½ƒ;’õÊŒ¾à\LÖ sI[t‹ ÿý:üµ8PÙBË/£ÐèpûÛÝâø~…É… é[¤œn%IâZ‘‡«)’%´õ±„©4 —ª $é­§_mÅ^~œ(‹ûí»ë½P‡^£=1g?qòðçð§ Îâ ã ¼`ÅÏ!ô!ËO³€Öu=W«Õü/?ó\9 A<¬1Z#Œ‡<Ýs0·òY9$ØA×Nе°‹PkclÛ¾‡Ià£Eû‰Oèë×®5S@N-eÙ·ç^tœàŠ© ÜOàN°ÝÞ+…þ†ß9²È÷ö5‡<ÜrÓ}ä¿·ÇEë!¡×î&Ã{Îé'ª}Ÿ$6Ã[Uû«årñ??ûYì>0É;"ãng€Åðˆ)N`š™P6VQ Ì?þÇÏ<ƒ'Š¢|Cž— ì&_[7äÀód·¦a«A> ø™C#û³BNÿýºÍ¡uw÷ !ƒS` òð&ÌB‡…*¡Ã+‡ÙƒÖï¢g«)„èºu½¸U×õ3¿ôK¨îç8ªã@׎³; ?bÖ·yè·½aw$r?øƒ8—¦Ù“y^¤B$ð Ìà»#Êyl¬!LŠ‹ÊãŽÌ÷ö„Ô~<¬óBèü¬/`†3¹³ë;½yš%· m4ÍòÒ|Þ^øõ_G÷`ÄíèÛÆ6H b Dì,àÐn­Ñ+ó¾ïû0- ñd–•'Ó4 ¿WÀÏ ö4ÞÑ¥á•g/|a(ò~)@hùÉ!­!NVqÇÙCþYødÇൽú(Õ£m«¦i–Ï~úÓæXuq»㎂{|6¸Xgåïd|ï÷BN&xLÊüñ<Ïa…”œ`^$6€ r¨> ¯ÄpŸŸ·7V0ÞþÌú†|ÿ7…Qû™cq­èûJuK­Õ•«WÕ¥ßùã±BËac©4~è‡ ‡¥Äi!’ ÉëY‹óPè;‰W‡Æ ¯b»Â­ý8$f†}ã*clw‹®kë¾ï/v¹ö«¿zô¦;JãX+ù¤})Å£Bˆ _Ýä­?½ö³@¨÷fð!V! húxqj1\W¢,­_kÝõ}wu±Ð/ýÆoX÷ïLÅŸÿyÿz”çÇ×…Ðxúiˆ§žÂN’à¼ØBfÃÂcD\<¿^ ï."ÄáO¼ å(8n½t‰°ô‘ö£¬­0–zbHð•RX´­¾~á‚Úýüç‡ÈN"å°ÔÒÝ*À+mÿu«PÇY¼(­¡a|÷wc¶±“iŠSRb[Œšóx.Иtgÿw,ü1/¤:H¦a½nÈ褴äØJiÝuzY×z~ó&nþþ1P i#üïF˜ÍlóZ÷;Òã8(€¸‹×¢(€¾‡PÊ®yê”Èßó³qâvò\̤ÄLJ“Pü=^4Ã'ȶú@ü>¤Xðm8 "$°ÙýBB›ÝÇ®.cú¶ÕËåR/vw±ÿì³¢ºvÍg'ISßÈ×;¾W#Äæ^¿Ò÷ws}|%X+ÐìYÜæ»Ûƒ8€|Ë[P<þ86··ÅVQ`&¥É…€ô4ïx¡‘2ÄD7ÿׯÌÂa‘x@kc”2]ߣ],Ìþ_ü®_¼ˆ”Ôð6^ oãÕ@;p³æÙÜæ»;½žû>Ž‚ÄÂýJïãë>èuü¿Ù€xè!äoz“œÒÙÌLÊÒ”Y&Ê4E.%øp':|~Åázû™@ߣW ]Ûê®ïESײ].Ms劮¿ö5´ÀðænÆÝ×+¢^Éš¿’°›5¯×}÷ÀÇa*€¸Í8Xø×]÷íôy’X!ÖÚìÐçyŽäüy™ž>­ó'Dei²ÉD–I‚¬(’\ZÂÉh{ -üë\'½ÖÐ}o´1Zk ­ú¦éûÕ Í /ˆÅb!ÕÍ›JÕµ]ážÿ^§|æNíÆÝ*ÃÝ|w7Êp"Äß=°qX À…[â•…wøún~[üýAi¸Žím™ìì˜t:Erëú<‡˜LL’¦ynóRU%ú+WD[×0UeLßóãSÊ:xw/Æk¢W º[hÄï±Æ!*Áa*@,øÍñuÞ)乿÷NÿœW«”÷k¼¡z-3ÁÝ(h<`8”u‚ï`pLÊŸq›×¸ÍgwòÝ —AÁI×µ»g­0(þüNðþ¡ÜµäµâPÇ]aÿ×p̯§q/üÛ)Á«µþßP>@|þ»q‚_­S|·¿÷°ïÍýw*`÷#t;'øn®ížŽWü“opõ ®ã Øÿ«ÍÜî·Þ­'¥¸ÛÄØ|§‰°ƒòG.`“õNÈ(‚¿ï¥·»¶;|-îà»;9öí>»›ïs¼¡~¥÷w£¯´ÿ‘ÃÉ{¹÷Xçì=`%¸í5¯yÿj”ævŸ¿å9¬ñJ‚öJ‚zÐçwûúvŸ¹‘dõ`tc¸ŒWx¿n|=CW3î5[ôØŽÿÓm¸ÙIEND®B`‚Slic3r-1.2.9/var/add.png000077500000000000000000000015511254023100400147040ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<°PLTEÿÿÿ‹»tˆ¹q„¶n€³j|±gw®cs«`n¨\‹»s‡¸qi¥Xe¡T‡¸p_žP[›LŠºs‡¸pV˜HQ”D‡¸pL‘@ƒµmGŽ<~²iB‹8z¯e=ˆ4u¬a9…0p©]4‚,k¦Yf£V4‚,0)aŸQ]œM4,0)X™IS–E4,N’AI=DŒ:@‰6<‡27„/3,0(¤Î”ËæÃÔëÏÎéɸݯ¹m»Ü¯àòܽ䶥ۛ Ù–¯ß¥ÅèÀŠÂy·ÚªÜñØØ’“͈ŽÌ„–Ô‹•ÔŠŽÒƒ·à¯}¹k—Ç…×îÒ Ù•”ÓŠ‰Èÿÿÿ…Íy|Ãk~Çoµß¬WžE¶Ý¬¯ß¦ŒË†É|y½nj¬[w¼`sº\™Ñ‹’Å€¾â¶—Õ‹†Ézp¸Y~ÁiªÔ³Ý©ŠÏ}Ìu}¾g«Ôœ˜ÌŠ–Ó‰zÆkpÁce«Ur¹[—Ì…¾{h«S´ÝªyÁdq¾_nµXt·\´Ú¦C8}¹j²Û¨oµYoµXt·[³Ù¥lªZx´dµÛ¨˜Ì‡}¼f|ºd˜Ë†´Ù¥kªXP™AÂ}¦Ð–¦Ï–‰¾xB6nAsK3tRNS#}ÛóóÛ}#SææSôôS"åå"~~ÛÛööööÛÛ~~"åå"SôôSSææ#}ÛóóÛ}#w¤öcbKGDˆHÇIDATÓc`À™˜YXÙØ9`|N.cS3s n(Ÿ×ÒÊÚÆÖÎÞÄttrvqus÷ðô ˆxûøúù‡„ŠÄÂÂ#"£¢cbãâÅ ‰I`œ’* JKÏ€ dfId²sróòÜä‚Â"Y €\qIiY P ¼¢²J(  X]›\[WßШ¤ rˆŠjSsKk[{G§š:Ä©š]Ý=½}ýZê0Ïhëèêéaõ9#Æ1]{hotEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/application_view_tile.png000077500000000000000000000007211254023100400205240ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<cIDAT8Ë¥“ÝKÂ`Æý%)‰èƒÂ‚¤•CaS )ˆBDZÚ‡QRÌ4ZaQvÑI’•÷A7]x§óôžóæfí"š<¼0öüÎÃÙ^8:±Ã9¥Ž/hÕZpç ämæô H›ÏØ(ƒý ĵø’E˜L<€°tÞø ÈÉ«æÉVõXþþk!z©'Û±gþ §_ÁކgÏ8@ڪЃpÁ4JÐ<†QËEÓ¨ÁÈÉ7€-¬H•~Å(Íf“‚¹Š èsnû¯º®[ôMq~ªß‰-c¸ÝFƒÞé p€¸úh™ˆAP\†1øY5ŒrËû (÷ URŒÊΈtâT ~¬—?Ëñ[KªÙÖ ^¯[t‰ðÆ®mýNŸ ­» Ž-èÏ™;‡¡È) ÌäÙ–iQî =Òtr¬ö.›¬Ë Ut|;| .’ûc^IEND®B`‚Slic3r-1.2.9/var/arrow_out.png000077500000000000000000000013131254023100400161710ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<JPLTEÿÿÿd´[nÀcd³Zb±Yk¼`a±YoÁdm¿ci¹__®WY§Rl½aj»`h¹^[©S_®W]¬UPœJN™HL—GY§RO›JJ•EH“DW¤PM™HW¤PU¢NI”ET¡NtÇhsÆgrÅfqÃef¶\d´[b²Y`¯X^­V©Ø¡§Öžg´_”ȕɎ\ªT¥Ö›Ñ’‡Ê~~½v†À~ÅŠZ¨RrÄfuÅkŒÌƒ¤Ó›„Ç{}½u“ÇŒs¶mY§RW¥PpÃe‰É€‚Äyu¸nsµlU¢Nc³Zs¶lq´jd§_g¨bG‘Ca°X_®WŠÀ„tµna¥]y³uc¤_L”GEA_­VŠÀƒz·ss´l^ Zd¦^p­lD@\«T‰¿ƒƒ»}[¦TFBq­ms®nBŒ?Z¨SX¦QV£OT¡NRŸLEŽAC@B‹?AŠ>ÿÿÿJ¡{YtRNS®ñ®ìñ¸³®쳸®¸³ì³¸ñì®ñ®`XçôbKGDˆH£IDATÓc—WPTbeU5uy M(ŸQK[G—AAO߀ Ìgf1426a053·°d °YYÛØÚ1سs88r‚¸œœ¹\€ ^>ˆüÜ Ô‚ZHXHº2¸¹‹‚øbžâ^ Þ>n¾~’ )ÿ€À `†Ð°pi°وȨh†˜Ø¸x9¨a ‰IÉ )©iéP~fVvÅ•@;~DtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/arrow_rotate_anticlockwise.png000077500000000000000000000013141254023100400216000ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<PPLTEÿÿÿnÀcl¼`c±X_­UpÂdn¿bX£OTŸKm¿bP™Gm¾bG@jº_]ªSY¦PV¡MRœJC‰„8i¹^fµ[‡È|¡Ó–ªØ §Öž™ÎxºpŒÊ‚«Ø¡¢Ô™¦Õœ¤Ô››Ð’žÑ–y¸q…Ç{©×  Ñ—Àw~¼u—ÌŽ›Ï“˜Îi«aÑ” Ò–dž”ËŒ„À}f¶\¤Ôœ¢Ó™~¾vJ’BF?B‰;>„8c±X¡Ó˜{ºrI‘B’ɉ“Ë‹’Ê‹EŒ>A‡:rµi–̎ʈe§]D‹=^ XZT9}3q²iɇ„Ä}‰Æ‚‡Ä„Ã~‚Â{S–M1s,c¥[{»u…Ã~‚Â|€Áy~¿wO’J-n);57{1Q”LN‘I-n(/q+,m(,l'ÿÿÿSï1tRNSkØØkËËËËkkØý‚‚ýØ‚w‚Øý¦kËËkÓÔ"„|bKGDˆHžIDATÓc` 02É+0³À¹¬lŠJÊ*ªjìPNu M-m]=.Ÿ[ß@ÅÐÈØÄÔÌœ,Àka©ËÇ/ hem#°µ³wf`qtrv ¸ºéº‹20xÀ-óôò—`ðñ… Húù{‡„ÂD¤ÂÂ#"£¢cbã Ò2ñ ‰IÉ)©ipcdåÒ32³²Qý““‹æÁ<¢Oò“ttEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/arrow_rotate_clockwise.png000077500000000000000000000013021254023100400207210ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<GPLTEÿÿÿnÀcl¼`c±X_­UpÂdn¿bX£OTŸKm¿bP™Gm¾bG@jº_]ªSY¦PV¡MRœJC‰6y02t-1s,-n)3v.0r+i¹^fµ[‡È|¡Ó–ªØ §Öž™ÎxºpŒÊ‚«Ø¡¢Ô™¦Õœ¤Ô››Ð’žÑ–y¸q…Ç{©×  Ñ—Àw~¼u—ÌŽ›Ï“˜Îi«aÑ”¦ÕɉŒÈ…„À}f¶\c²Y`®V\©So¯gɈ>„8U Lj«cŠÇƒˆÅ‚:~4TŸKPšH„Á|~Áww·qSžKl®chª`H@a£Z‡Å…Ä~‚Â|T–NSJj­cɇ‹Ç…‰Æ‚‡Ä|¿u‚Â{[UN˜Ff©_‹Æƒ…Ã~tµmR•MF?^¡WZžU;57{1>ƒ76y0ÿÿÿù6¼tRNSkØØkËËËËkkØý‚‚ýØw‚‚¦ýØkËÓk’Æ/bKGDˆHžIDATÓc` 02ÉÉ3³À¹¬l ŠJÊ*ªìPN5u M-m].Ÿ[O_ÙÀÐÈØÄÔŒ,ÀknaÂÇ/ hie-°±µ³f`qp´r‚›ëÌÀ êâêæã{x2ˆ‰{yûH@ù¾~þAÁ!¡’~XxDdTtLlœTA|BbPRHrŠ”4̈ԴôŒLY$¿d¹3 l8 ]U+Ý:ÝtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/arrow_up.png000077500000000000000000000007671254023100400160220ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ÃPLTEÿÿÿsÆgpÂerÅfh¸]tÇhrÄf^¬UY¦PqÃen¿bX¤OSžJRœIˆÊ~…È{‡Ê|§Öž¥Ôœ}ÀsƒÆy£Ôš¡Ó˜†Ã}e´[ŸÒ–œÐ”e¬]_­VšÏ“˜ÎPšHY¦P•ÌŽ“Ë‹J’BSžJ‘ÊŠŽÉ‡CŠÐz<Ît8Ìq7Ën4Û›v{Oå·ƒßUÎv:Ír7Ën5Êk3Éh2Ú˜uzN㲀ܔQÌp6Êm4Éi2Èg1Èe0Ú˜tyMà­~ä¯â«ߦ|Þ¢zÝ yÜžwÚ™uxMˆX†V…UƒT€S}PyNÿÿÿ)<™tRNS ¤ñóóóóóóóóç|îƒÛ¯ððððððððð·ÀÂ#7bKGDˆHÞIDATÓc` ™˜YXÙØ98¹¸yx|>~9yE%eU5u MA-m]=}C#cS3s!a K+k[;{G'gW7wO/o_?ÿ€À àPѰðˆÈ¨è˜Ø¸ø„Ĥä±Ô´ôŒÌ¬ìœÜ¼ü‚¢bñ’Ò²òŠÊªêšÚºú†Æ&‰æˆêÖ¶öŽÎ®nÉž^ˆê¾þ 'MžÂ 5uDõô3gÍž3—AzÞüÉ -^Òµt,cY¾bå*·Õ)Åkº§¬»Lï[Im` ŽtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/brick.png000077500000000000000000000010151254023100400152410ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿eIDAT(Ïm‘1H[Q†¿ûòbhÒPˆÔª[¡…ºfJÇN.ŽR©«d¯àâÞBQ,¡ÉÔ½ƒâT–RR%†T(ãC£é{¹÷w0Q¿³}œs8‡ßˆ>ÛËÁ|k ÒÇ©|ñF«W•L)TR¸9Ö÷Fl/s­I¼YrˆŸ€K7RŸóED©s=WWYUUõEõþ¦Žˆ½X‹ÍˆGˆ_t©ó—$–Ç8~ø;9SŽžûm 1j¼îÝU!KDËA×Sû ã¤!C ‡£Æ(´I£¶/`8gÄ Ø"Nèï}è Ž\àøÇ%#ø,áÛ„‡ˆ°D]bpØAO[» Ã!À!Ä’x ]Ͻj¾ùö{C°„ 1Lý£“wzi¬g´¨ÂÔçXšŽiœë£Vþ€é‡õ陊š›6§49¯èýÛï×Þܦ fµ¤­¾Þº; ÷qÓ¹%4£vÆtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/brick_add.png000077500000000000000000000015031254023100400160530ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<kPLTEÿÿÿ³³³¢¢¢­­­©©©¦¦¦³³³¢¢¢©©©®®®¬¬¬›››«««šššªªª™™™©©©˜˜˜¦¦¦•••¥¥¥[{H£££¢¢¢¡¡¡7kŸŸŸžžžœœœ›››ššš˜˜˜———–––Lt47k7k7kÿÿÿ¢¢¢±±±ÖÖÖýýýüüüÓÓÓààବ¬ÑÑÑ«««®®®ÕÕÕÇÇǵµµ£££ûûûÒÒÒØØØÍÍÍÎÎÎÌÌÌÁÁÁ¾¾¾óóóéééÝÝÝÞÞÞÂÂÂÚÚÚÃÃÃ×××»»»ÏÏÏñññîîîáááËËËÀÀÀðððìììêêêäääÄÄÄõõõòòòçççßßßÛÛÛš¬r–]fŽO^‡F‹¢~ãã㔨‰«a´Ô•Ðæº»jW-èèèëëë`‰H±Ó±Ö’Œ¼e¼gÅÅÅL{1¶Ôœ²Ò•7k½½½~;{G'gCW7]w €€‡§—­“·¯Ÿ¿.HE@`PpˆKhXx„®¤ 0CTtLl\|‚ŽUbRrJªCZzFfVvNn^~AaQq‰(ƒXiYyEeUuMmqqQ]}ƒ¸DcSspKk[{GgW}wƒ¤”to_Cÿ„‰“&×Oén»TFVnê´é3fvO™%u¼‚âì9sçÍ_ ¤ 󰤊êÂEjê aE> ]ïîÒtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/building.png000077500000000000000000000013301254023100400157440ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<\PLTEÿÿÿtÇhrÅfL˜GqÄfH’DEAnÀck¼aDŽ@B‹?ç99šššÖÖÖÓÓÓÏÏÏÌÌÌÈÈÈÄÄ᝝tttŽŽŽ±±±ÑÑÑóóóòòòñññððð©©©ÍÍͲÌõ¯Ëõ¬ÉõªÈó§Åóïï¢ÇÇÇäääãããâââáááàààßßß›››ÁÁÁ¬ÈõªÇó¦Åó£ÃóŸÀó¾òîîî“““»»»ÞÞÞÝÝÝÜÜÜíí파Œ´´´¢ÁóŸÀò›¾ò™»ò–ºò•¸ñììì„„„¥±¤ÛÛÛÚÚÚÙÙÙëëëxƒwzÁpƒƒƒ†††×××ÜäÛZœT|ÆsÅßÁ………µµµ···ŠŠŠÕÕÕËÛÊ`§ZrÂgˇ€Ãx‰‰‰¹¹¹‹‹‹ÌÑËTžN»yj¶`u°n’§‡Ž‡¼¼¼½½½ƒš‚X–Tm­ekkkjljZyY`s_gmgÿÿÿ±+p tRNS ARÞ-wþø±hD…7bKGDˆH²IDATÓc`€n4Àgñòñ  ‹ˆŠAÄ%$$%%¥¤¤e ²rrò ŠJÊ*PUI5u M-me¨€®”ž¾¡‘±‰)TÀL (mnaie °‘¶µ³wptrv ¸*¥ÝÜ=<½ |FoK_?ÿ€@ˆSP°[HhXxDd3ˆÏ럘”ÌÊ`çHIMKÏÈÌÊÎÉåä äÁA~Aa;â!†é tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/bullet_black.png000077500000000000000000000004371254023100400166010ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿wIDAT(ÏcüÏ€01 ¼SIŠ!í_è_†ß«Íz÷ ‹†4Ïz†? wë—304`±âo¨Ã]†ë r ?C±ºá7Ã?†ß ?þ2üÅîÈŸ«o0(0h0Üd`X¤â?rK±50\e¸ÊÐÀ …e A ð÷*’|ˆÝtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/bullet_blue.png000077500000000000000000000005761254023100400164600ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<oPLTEÿÿÿ¥ÿ¥ÿ£û õœí—ã×¢ùŠÊžðƒ¼—åv¡’ٌ·À¶sœh…€Òý¹âû±Þú}Éð¸áû~ÇønÂõ‘Ñõ©ÛøpÂõgÀô†Íó|ÈîÐõ…ËóxÁäÿÿÿåçÀ;tRNS€ÕÕ€€ÕÕÕÕ€€€ÕÕ€øãPbKGDˆH2µénpÇtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/cog.png000077500000000000000000000011171254023100400147220ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿§IDAT(ÏM‘?hQÆïqi4i/˜ÜÕ€±ƒC&¡8(vPpvîd+8‰“І:ÚIzèÁ.. ‘ÚAK ¡ÿBB jî’»÷ÞCƒíoüø†ï'RŽù4§g ª1óì8G…fÕ˜¾nŸ/¦lvsžÎ«òí€húfWï)W™ˆˆ1£Üx/Þmø£ÂNÍž¯HÇÓûÚ2 qÕ~Å›‘gç×À‚X¶SD„kKP‘ñ÷éPŽ6l,/1üæ;¦°P„4WÖ@l~Ö%m{N@Á£,NýòÕ›õž tG|0å!ÉÐâëÍÚ;ðoÞz„äqy’HE„Æ’g…&ÁbýÜÞ.9ö%g@™Ãú* UŸ$FÒèõÕ)°¶<¹¨±üÀpÀ€õ•§5ðj)^˜ÀbŒ.`“åSdî-Qâ‰Óìð%¹ë"áE*Â+v‘‚h&£êþ¯;A§0}fë–§ñÛÃéd+,©çoîþ—õ ª&úÙöµbÀËnêÉüŸòëÖ ›GÜŸëÎFˆÆÚ Ýÿ”óºªià ¤tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/cog_go.png000077500000000000000000000020321254023100400154040ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÿÿÿËËËÇÇÇÄÄÄ¿¿¿ÑÑÑÎÎÎËËËÇÇǬ¬¬¶¶¶²²²­­­ÑÑÑÎÎÎÊÊÊÆÆÆÂ²²²±±±¬¬¬§§§£££ÐÐо¾¾°°°¬¬¬‹‹‹ÊÊÊÄÄÄyyyqqqÅÅʼn‰‰ÈÈÈÄÄÄÀÀÀ‚‚‚www~~~ŒŒŒ‡‡‡ƒƒƒÄÄÄ„„„ššškkk¿¿¿“““•••aaa£££   m£7g¡3b0XXXSSS¬¬¬5‡-ƒ«««¦¦¦%¤¤¤ƒƒƒ%yyooodddvvv$w yooojjjzzz[[[+ƒ!yymmmXXXQQQRRR!}yåååäääÞÞÞÝÝÝÜÜÜíííÛÛÛÖÖÖÔÔÔËËËççç···ÙÙÙÒÒÒÈÈÈÌÌÌÉÉɺººœœœ¡¡¡ÂÂÂÆÆÆÁÁÁØØØÍÍͼ¼¼ÃÃÃéééÎÎÎ¥¥¥p¤@eŸ1âââ±±±h :³ZW˜(ÀÀÀÏÏÏo¥=|¯UªË‘t«OEŽÅÅÅk£7°Î–­Í”¨Ë¼t¡ÇŠh¥FÕÕÕcŸ1Žºo‰¸k…¶f€³a‚µg˜Âƒ\ <¦¦¦[›)©Ê¥È¡ÆŠžÅˆ…¶j—‚f¥HQ—"J‘D;‹^Ÿ:–Á€b£F½½½²²²_¡C{ÿÿÿ“RsÆ[tRNSc¿¿c)êê)›ýççççý›{ôþþô{}þþ}……ÍãîÆDDÆîãÍýDDýýDDýÍãîüüþãÍ…û}þù{ôê ›ýçí)êêëÔc¿¿cë²×GbKGDˆHùIDATÓc`F&f8`ec爎áäâæðyùøcã„„EDÅ@|ñø„D ɤd)é”Ô4 €¬\|rzbRrzFJ¦¼H‰brVvNn^~Aa𫬢ZT\¢¦®¡Yš_¬¥­Ã [–”]^¡ÇÀ _Y•SgÀ`XQP\cÄÀ`\[WßaÂ`jfÞXÕdaiÕÜÒÚfm4Ŷ½°½£³«¥»§·ÏΞÁÁ±¿=eB×ÄI“§L6݉Á¹$v†ËÌY³çÌ7«P‹»‡§×ÂE‹—,]¶ÜÛì_?ÿ€+ƒV‡À=¹: È;wF'Šœ„ÄtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/cross.png000077500000000000000000000015161254023100400153060ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<•PLTEÿÿÿø`cÿtwÿtwø_b÷[]õVYýorø^aóQTòMOûil÷]`ðGJîBEÿsvþqtùac÷\_ðFIîADýnqüknïEHîACûgiùcfïDGî@Bø^aöZ]ïCFí?AöY\ë9<öX[è02õWZîACì<>å'*õVYî@Bì;>ë79é25ã "óQTí?Aì:=ç-0æ*,âðGJí>@ë9<å%'ä"$í=?ë8;ã!âÿwzþvyøadùhjÿ~þ~ýqtø_bûmoÿ|~þz}ÿ‡Šýy|ûilø^aúlnÿz}÷_aýtwÿ‚†üsvøbd÷]`újmÿy{ûmpÿ~€ÿ{~ÿy|ÿwy÷\^ÿy}ÿ[^ÿX[ÿtv÷[]ÿvyÿVYÿTWÿprðFHöZ\údgÿrtÿpsÿnpÿln÷UWî=?öY[úcfÿqtöXZôMPÿghõNPë46öX[úbeÿpqöVYòEGÿbcôHJé-/õVXúacöUXñ?AöJLê/1òLOÿÿÿÞ5¥>tRNS ++o>++¨)++oý++üüüüüýý+++++üý++üü+AüüAAýüAAh,1|bKGDˆH¼IDATÓc`@ŒPš‰B³°²Aøvöì šÃÁ‘“ Ìwrváf`àquswååc`àððôòòñõó)   —€&)#-³NV.6.>A^n¿bbRrJjšŒ¯œž‘™•“›§á«æ©©—”–i€øšå•UZÚ:ºÕ5µuz@ýú†FC#ã¦æÓV3sma á30XYCh[,%—$cŸÌøÑtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/delete.png000077500000000000000000000015141254023100400154150ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<›PLTEÿÿÿâmmájkÝjhÜdcÚdaÙc]Öc[ÔbXãlmßjjÑ^SÎ]PàijÌ[LÊXHállàijÇWEÅVAàijÂT>ÞjgÀS:Ûid¾O6Úc_¼P2×b\ºK.Ô`X¸J*Ò_TÏaS¸J*µK)ÍZMËYI·J+µK)ÈXFÆUB·J+ÃT?ÂP:¾S8½Q5¼K0¸N.·L+µH'퓎øÃ¾ùÓÌùËÄô´ªâpfò­ªýÜØúº®ú£‘ú‹û©œüǺé{pó§£ýÛÔúš‡ð‘ñŽzø”ù’~øŒvø¶¨ãncêƒ}üÔÍ÷“~îŠuö„lóycø¯¤ÐO>õ¯¥ú«ðŒwì_TòwcôŽéxù¹¯ú“ð…pÿÿÿéYMîj^ñ–÷«¡ø†pö‚hêf\ñœ–îŒ÷‘~ós]é[Oðƒ{â{uÛVKö«¢ðdVîfRæXLæZRõ£ŸÄP4ãkaõ¬¡ê\PæYNæVLæVPô¢žÖ`Tàe\õ¦¡ï†~éc[ç]Yî„}ô ž×]QÌR<èzuî’äxqÁM3&¯‘3tRNS#}ÛóóÛ}#SææSôôS"åå"~~ÛÛööööÛÛ~~"åå"SôôSSææ#}ÛóóÛ}#w¤öcbKGDˆH¿IDATÓc`À™˜YXÙØ9`|N.cS3s n(Ÿ×ÒÊÚÆÖÎÞÄttrvqus÷ðô ˆxûØúúùA@`(P@,8$4 Â#"ÅQÑ1±`Ÿ JLJ† ¤¤JdÒÒ3âÀ 3+[( —“›—âËKJÃÊâÊ+*«ª•”AQQ­©­«ohljVS‡8UC³¥µ­­½CKæm]=}C#¬>ìÏ0°§—ÃtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/disk.png000077500000000000000000000017161254023100400151110ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ûPLTEÿÿÿ6k¼6k¼6k¼6k¼6k¼6k»8k»>p»5i¶6k¼ßéø½Ðì^‰É5iµ6k¼>p»6k¼5i·7kº1`§5jº1`§6jº5i¹5i¸1`¦1a¨6k»6j»9l¼;n½:m»ÑàöÑà÷øûþ÷ûþöùýðõüêðúíòû÷úýëñûÑßö€ªéöúþöúýdŒÈîóûêñûòöüñöüâìùÛçøºÐî½ÐìÐßö~¨èéñúîôûèðúÝèøÛæ÷z£áÃÕïÌÝõ~¨çfÉéðúóøýøúþïôüßéùÛç÷Ùå÷x¢à©Âç5h¶ÉÜô}§çáìùãíùîôüó÷ýåíúØåöw Þ¤¾ä4g´ÇÙô}¦æeÉgŽÉl’Ëm’ËiÊeŒÈtœÚŸºá4f³ÅØò{¤ãz£ãz¤ã{¤â{£â{£áy¢áw ßvŸÞtžÝrœÛtÜšµÝ4e±ÂÕòx¡àužÞs›Ús›Ù•°Ú3d¯¾Òðz£âwŸÞvŸÝr›Ùq™Øp™ÖŽ«Õ3c­6jº»Ðïz¢âm–ÓŠ§Ò2b«8k»¸Îï÷úþˆÀbj“Ï„£Î2aª8l»¶Ìîz¢áÂÜ¿hÍžÌ2a¨³Êíz¢àeÊ|›É­Æë­Åê|šÈy˜Ç5h·5hµ4f²3e°3d®2c¬2bª2a©1`¨ÿÿÿ€\WtRNSqÌîúþïËT›þûìcØøðþþþÞíµîñÄéÑî3bKGDˆHøIDATÓc`dbfae“‘••“W`çàd`àRTRVQUS×PÕÔâæáåcà×ÖÑÕÓ704R66153`´°46Ö·²ÖTµ±µ³wb`ut²±qvqus÷ðôòöñe`óó Ò ñô `‰ŒÒމ‹OÐ××OLJfMIMKÏÈÌÊÎÉÍË/(,b-NKËÌÌÈ. +-(+¯¨d­ª®¶ÏÌ*©©-¨«ohlbhniU†ƒ¶ö†Î®ìî(Pííëg˜0qR÷d(P2uƒðô3g‰0ˆÎžƒ0cî<1q Éù ".Z¼dé²å"RÒ[QÌ‹´ tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/error.png000077500000000000000000000014501254023100400153030ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ŒPLTEÿÿÿì¿=ë¼<êº:è¸9ë¼<çµ7ë¼<éº:æ²5ä¯3é¹:ã¬1é¹:è·8á©/à¦-è·8Þ£+è¶8Ûœ&è¶8æ´6Ù™$Ø–"æ³6ÔŽæ³6å±4ЇÍ€å°4Éyå°4Ãmã­1Áhâª/¿dñÔ…ðÑ‚ýúñúïÕòØ”þüóþúçðÓÿýùùé”úëžþúìôß©ýùíÖ£>üõÔñ×¢íÈqÿþùôÜ^Õ¢>ôÙ\þûíå»h÷êÈýúæôÚ]Õ¡=ò×WüóÇôãÀðÒŒþûêøæ”ôÚ\ݱGò×VõÛ\ýøÞçÀ}þûóûòÃöÜ\ößdëËWòÖUõÙTøç”ûôãñ×ýùçøå‹öÛZôÚ[òÖTõØRôÖPüöØå¿ˆè¿bþüôúïµõÚXóØWò×XòÖXôÙWõØQô×NöÚbÒ“Dõã¾þûïþûîþüïþûìþüòëΫà§-ߤ+Ý¡)Üž'Ú›%Ù˜#Ö“ ӌυÌ~Éw Æq ÃlÁhÿÿÿ àq'tRNSÔÕiòí´–0ýü0ÔÃZZ ìä ª‡$ûù$Ê´KKãØ¢¢ì½sabKGDˆH¶IDATÓc`À™˜YPXÕ5ØùìšZœ\HÜÚ:ºz<>/Ÿ¾¡¿\@ÐØÄÔÔÌ\ƶ°´²¶¶±µðEÅ윜]\Å%À’nîž^NÞ>¾~R ¾´Œ@ gPpHhX¸¬P@>"2*:ÆÉ)6.>!QA1)9%55-=#3+;Ç=W‰A9/9¿  °0¿ÀÖ¶¨¸D…Aµ´¬¼¢²ªº¦¶®¾¡±I Ãß7+&Nµ8M„tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/funnel.png000066400000000000000000000012011254023100400154300ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ÞPLTEÿÿÿŸŸŸ›››ÝÝÝÌÌÌ———ÖÖÖ“““ÐÐÐŽŽŽŠŠŠîî†‚‚‚äää|||yyywwwªªªªªªªªªªªªªªªªªªªªª¢¢¢ŸŸŸ———“““ŠŠŠyyywwwwww¨¨¨¸¸¸ÇÇÇÔÔÔßßßçççìì쥥¥ÜÜÜÁÁÁ¤¤¤‰‰‰www¢¢¢êêê°°°ää䪪ª´´´ÂÂÂÌÌÌ›››ÝÝÝÀÀÀ———ÖÖÖÐÐПŸŸŽŽŽ¼¼¼ŠŠŠ†††îîî‚‚‚|||yyy···£££ÿÿÿ}¬ôµ"tRNS#W†¯Ðêú@€@€@€¿€*‰$bKGDˆHÂIDATÓ=È×ÁPEÑÁè½÷è-Hp!DtÿÿEÎõà¼ÌìET,•+ÕZ½^«VÊ¥"õÃÑx2Nƣ᠘™æ|±´¬åbnš3@Ã^ýg7æzƒ©íNT3†öâ8à°ç i]×[Dc±¶}:©í¹ÍęٻˆRž8 A”4|Q¾@P ¼ÞäŠf¥™ûFî”D®<4 (à§ÈS‚ò^ïOG‚ zV·÷»ú‹ð6ƒbž_tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/hourglass.png000077500000000000000000000016131254023100400161620ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ÅPLTEÿÿÿןqןqןqןqןqןq¾†N³{?¨p/žf!•]W ןqW Ö£z]ÅÒᔥ·¾Ûú”¶ß¹Öö¹Öõ—¹â“µß°Ïñ±Ïð–¹á’µÞ¦Æê•¸á¨Æé’´Þ¦Åé°Ëꈬ؂§Ô£Ãè¬Èé~¢ÑyŸÏ Âçq˜Ê§»Óq‰§Ñ™iÈ\žf!•]W W W Ñ™iÈ\à·‡æÆ•êÏìÓ¡ëÑ èÊ›å–âºÝ±‡Ô£yÄa®x>éÍ›éÍœçÇ™äÁ•ฎݰ‰×§Ï›sÂŒ]ÍÅÀÜ·šÕ¦}Ê•c¾‡O³{?¨p/Ÿg#˜cšk,§ƒSš”ˆÍãùìóû÷úþøûþíõüÕæ÷ÌáößìùÝëù´Îëàìùôøýæðû×çøÞëùÅÚòÉÝóâîú¶ÐíÎáôÌßóÐãôÍßóÒâóíôüçñûµÌéÞê÷ùüþò÷ýâíú·Ðë»ÓíåðúõùýúüþüýþôùýÏãöÓå÷Ñãöš¹Ý¥ÃæÀÖïêòûýþÿðöüÙèøÎâö­Èæ{ ÎÔ¹˜ÄÇÅÇØìãëöñõúÞçó¦¿ß€¤ÐtšËq—ÈŒ“£œbÿÿÿÛÖú¿4tRNSn³ÝôýýôݳnÄÄÑÑ**¸¸¯þþ®8ÝÛ8ÃÂÇÃ:áÜ8°þþ®¹¸**ÝôýôݳnK–RbKGDˆHÓIDATÓc``dbfae31eçàäâæa``à53·°´²¶±µ³wpä ð›™;9»¸º¹{xz9 ½}|ýüƒ‚CBÄ€ Âá‘QÑ1±qñ " ` *–˜”œ’š&.Á’Ré©™Ò2  ›•-Ç€ äsr¸ŠJyù…Ê*0¾ªZQdqIi™º„¯Y^QYU]S[Wß âk765WµT·¶Õ·wtê€üÒÕÝÓÛ×?aâ¤ÉS¦òA<‡ð Hè}]=ö€@}C#c‰z2œâ{ËtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/joystick.png000077500000000000000000000010571254023100400160140ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ÁIDAT8Ë•S=hÂPþž4At±¸”¢âà ´d)tµ n ¥àصÐEpº•ή"B77±[D§`‡HÑ‚à¿ñ/}÷ Al=8Ž—ûî{w—ï1Ó4qhÅb1´ÝnÕÕj^¯×X,?Óé4™Ëå´CìÌ0Œ@ öx< ]׃Nç§î±.'‚ÉdrÑh4Ðl6ÛíÆh4 9a 8Ùl–õz=A°\.…M ˲ˆŒ1A`‘MÀw€t:mú|>ðE §oNæ¸D¾q(ŠI’Ðjµà÷ûO뀃MºQÓ4(ó~ûPoo®ñúòÅýÙ±ƒr¹æAÛív2w10ĹÁÅ㘱d+½›ü.&ßR©ËR©”· ¨˜ J‹F£rµZÅ`0@¡PsŸ+WPj50¯R¯"‘H.CUÕ<#%r6#‹ÉœãñXrõÙ>ŸÏíèõzár¹ÉdÐn·™è€'e.]T*‘X˜UD>›ÍÄ™¾Ó9‘Hˆh@ ºu8Ú «p¿ Gº _kP’|6‘°ûq³Ùüs› Ûí¢^¯Û¢9tKTD£î¿`æôœO±?Òc¶ ™¶IEND®B`‚Slic3r-1.2.9/var/layers.png000077500000000000000000000021441254023100400154520ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<(PLTEÿÿÿtÇhsÆhqÃen¿bj»_f¶\b°X]ªSX¤OSžJN—FI‘AsÆgpÂem¾bi¹^e´[a¯W\©RW¢NRœIL•EG@Bˆ;oÁdl½atÇhsÆhqÂen¾bjº_eµ\a¯X\©SW£OM—FN—FI‘Ak¼`g·]sÆgm¿cjº`fµ\c±Y_«UZ¦PU LPšGB‰=G@Bˆ;f¶\b°XnÀci¹^sÆgrÅgpÁdl½ah¹]d´Z`®VY¦PV¢MPšGN—FI‘Aa¯W\©Rj»_d³Zfµ\d²Y`®W]«TZ§QV¢MP™GN—FGŽ@G@[§QV¡Meµ[_¬Ve´Za¯W^«TZ¦QV¡LQ›IJ“CG@A‡;A‡:TŸLO™G`®VX¥Pb°X^«TY¦QU LPšHK”CE‹=A‡:;5:4N—FI‘AX£NM•E[¨RW£NRJM—EHACŠ<>ƒ7;55x/4w/TŸLO™GY¥OTžKN˜FH‘BCŠ=>„89}34w//p).o)N—FI‘AQ›IK”DFŽ?<67z12u-.o))i%)i%O™GJ’BEŒ=?…9:45x00r+,m'(h$$c sÆgBˆ;oÁd;€5k¼`5x0f¶\/q*a¯W*j%[§Q%d!TŸL!_N—FI‘ACŠ<>ƒ89}34w/+k&'f##b ^[ÿÿÿâá‡ëžtRNS777777777777EEE ‚‚‚‚‚‚‚‚‚ ~~E ¢5:?BFJLPzJžE £=ØÙÚÛÜÝÝåÜëËËE £DzŠ–¦²¼ÑÆß±E£Jœ©¶ÀÇÌÛÐâ³E¤R¸ÂÊÐÓÖàÕâ±EE¸}ÖÚÝààáá×ᯞRÏÓÕÖØØÖÕ૞žãäâáààßÞ¦³²±¯®«¨¥¢¡¹çãßbKGDˆHßIDATÓc`@ŒLÌ,¬lìœ\Ü<`^>~A!aQ1q °€¤”´Œ¬œ¼‚¢’²ŠªP@]CSK[GWOßÀÐÈØ(`jfnaiemckgïàèäìÂàêæî1ÏÓËÛÇ×Ï? 0h>CpHhØ‚ðˆÈ¨è˜Ø¸ø„… ‰IÉ)‹RÓÒ32³²sró3ä-).)-+¯¨¬ª®Y 4¶¶nY}CcSsKk[{Çr @g׊îžÞø¾þ 'M^ vܪ)S§MŸ1sÖì9sWƒÖ¬]·~ÃÆ¥›6oÙº ’eBüÄÜGtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/lorry_add.png000077500000000000000000000012611254023100400161310ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<CIDAT8Ë¥“KHÔQÆÔR‡4J|…„fEeEb»ÊBªmЦˆˆ ÜD7AHÑ¢2z¬ŠB%m&PÙpÑ[\ØC!z‰(Þ{ν-æŸ  žÍw¿Åù8çûî ¼÷,¤²X`E†ïžSÕrçUAÕ"b1³h­AÄÄö_èÝû—€ª–¯ØÑ„÷ŠW‡w‚sŠWÁ«Å©àUxÕu{Ï?'±8'ÌL¤ð*8±8µƒCn´”›…mÜ8ßãUU±r2~i_GDÄPR{à¿»jo7çÕ¡Î3öu*ûjçË‹@Fàì“ §&Q㣨QdFgyMY>b#žu•El^]Ä•ûýÅkg8±<ÎϬÑpd‹†£ÿæK–Ur|lÑÅ£ŸÇ©®(Âˬ€³5é°ÑÎ6:1#ÅbÅŠ#96EÚèc¦Q1ä–d—Ðy'aŠKÙÒA:{1•§ï…!Z°8Bpp÷&ï½Ã9‡÷¢ó>ý'Z[BõÞ¶¬ÝJEñz†Ò?ØÇ§‘o—³<} ¦ìÇ¢ªzÊÖïdøk®¬k"ºj;#?rº»ïƒìªüéM5Ñ,eci#Xê74œŠ„)N$m@¸‹ÅšÃ÷I€ñ©ïy‹‚šÖàÌ®ºÚÇy€d2æÄ~oîø>ñez(õ<ïm*NKãZcGÉËΘž×19ï.÷¿{NºÚÉ "Äß<¸Ì÷œë›ËZÕ I ½ÿZªå<0€y=XöIEND®B`‚Slic3r-1.2.9/var/lorry_go.png000077500000000000000000000012731254023100400160110ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<MIDAT8Ë¥“KHÕaŵËèš©i))QjP‰QQ-ÒDE‹ U»‚hSë1° …‹P´‹­¢K“k¡µ 3{ˆ¦·H ­ìþï÷ÍÌ×⪛Š†Ã™ÅáÌ& !0—ÊbŽ•Óëôˆª–š)ª‚ªGÄ#âfÑ{‡ˆk;ÐÔ¹÷7U--ܶ” F0ÁL *õ˜ A…÷nîù£™žLT0ñ˜ú ŠÃı`ñr®åµpåü“ ¢¨¨Š—“]—ö_ÏqUüç®ÚÙAãÑZÔ#c?²[ïö^2çî ?ùu†8E"iåkK"^p¨)O°qU‚Ë·ŸçäxŸæÄ².~f M[öè´õž»´œÖüvîôLqlÞjÊŠñÎgÎè}óu)dºÕ¥P£.Æ|Œ‰'ÇŠ‚*Z;wÒóntV ǹÇü¼¢Lâ’Iþê·6B¶Ç™à&ú)Í_MUñf¾ÇS\ìØ„êY¢#»6„ 3##Lcéñ»kŽ¡ÁPSŒÀçÉ yöá1½CO‰5½$ëΣ—Ñ«¤LTn¡d]=ýcQª¼v±84CãoàýX©q†¾¾gÃʬ)©#–0…¨®®Þ´)àpªö [­?˜"¦h0>M“¿¨˜—»yôúáW§4D{¦ÃW+,vgŽX•5Ñ–Š=ô wÓ>ÐöÙ ÉæÐýï7®kÊšª+«ÏíhuFC²9¼øoŠÆhJ-;7eZñ¥9 ÎÌn¡½þÈèIEND®B`‚Slic3r-1.2.9/var/note.png000077500000000000000000000013441254023100400151210ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<tPLTEÿÿÿþß¶úÒ¢ùÑœøÍ—÷Ê’öÆŒõÂ…ô¿~ôºvñ¶nð²eð­]ýܲÿþúþýúÿýúî¨SöÌ—ÿýùþûðþüðýûîýûïýûðþý÷í£JõÈ“ý÷áùêªùè£øçøå–÷ã÷áˆö߀õÝwûñÈëž@ôÅŒý÷Ýøè¢øå•÷ã÷á‡ößôÛmûðÄê˜7ð¹yüöÛøçœ÷á†õß~õÝvôÙdûðÀé”,î¶qôÑžðÊxðÇrðÆní¾`ôÛlóÖ[ûï¾è#ë­^ðÃüóÓüóÑúí²í¼[ôÚlôÙbóÖZóÔQûîºåŠì¬]ë­Uë¯Gùé¬úê­ë»VóÔPóÒIùí·ä…ë«]øã´æŸ2é«;øé¨ëºNòÒHòÑBùí´ãë§Uùæ¶ñÑ•è§Kë¯ZîÂ}ûî»ùîºùí³ùí²â}ê£MèžFçš>æ•5âŒ⌠ä!ã‰ãƒá à}ßyÿÿÿ’í¡›tRNS@æØfbKGDˆH°IDATÓc` 021³°²±sprqó€xù@€_€O@, $,""*&..!!)–‘•“WPTRVQU ¨khÊkiëèªèé€ Œµ´MLÍôÌ-,ÁVÖ6¶vöfæŽNÎ`W7wO/o_?°@@`PpH¨·OXxD$X *:&6.Þ',!1),’š–ž‘™•‘“›È/(,*.)-+¯¨¬"Æ«ì »ÕnMátEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/package.png000077500000000000000000000021321254023100400155430ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<gPLTEÿÿÿØ´MزK×°JÖ®HÙ¶NØ´MײKÕ¬EÔªDÓ§BÚ·OÙ¶NØ´LѤ?Т=Ï ;Ù·OÙµNÎ9Íš6Ù·OÙµMË•2Ø´MÉ’/ײKÈ-Ö°IÇ+Õ­GÆŠ(ÔªDŇ&Ó¨BÄ…$Ò¥@Ã"Т=Ï ;Î9Ä#‚"€ Íš6̘4Ë•2Ć%Ä#‚!Ê“0É.ÈŽ,Ä…$Ã#Æ‹*ʼn(Ň&Ä…$êÕœç̈åÏ’üøæþöãòݯ÷ä²áÀràÆ~÷îÑÿÿöÿþðûíËÚ­P÷׋ùáªòÖ•Û²bíÙ¥õëÒíÞ¾ÞÉ•ÛÉָpݹiõ×õÏøÛúãªìʄզQþøáùëÍíÒ’ëÌ„å¿oÛ±VÈ”;úìÎÿõÙÿê¹þß•ÿÕwÿä¥ûÜ„ýøâÿóÔúä°ñφõÐõÐxä±Lÿé¶ÿá›þÖxõ½@éµ=ñÕýöâÿôÚÿóÕÿë½÷Õ‰õÉié´LûÚÿÜŒùÄHì¶8è¿Hè»OðÐŒüöâÿô×ÿòÎÿïÈÿëºûÛ’ñÁVùÂHð½;ìÅGé½EæµBæ±GîʈÿðÉÿí¾ÿê³ÿç­þÙ|ïÇHêÄCè¾Cæµ?ã«:á§@ìŃþûçÿöÝÿïÁÿë·ÿè«ÿä¤þá–ëÆHé½@æ´=â©8ÞŸ2æ¶kîăܽwöå¿ÿöÛÿîÁÿå¥ÿãŸþá”êÁFæµ:âª7å¯VíÆ‡ä³tÊJàÀ}üîÇÿòÌÿè¨þà”çºAä±EïÊŠëÁ‚ЗSåÄ‚ÿôÌÿïÄñÒ‹ðΊפ_ëÍâ·oÈŽ@ÿÿÿŸ™pÜ;tRNS+¯¼1„ïóŒeÛàl FÂÈMŸþ¦ûûððððððððùø»ÁeÛàm …ïó+¥þ¬1F¾ÄMñ*NbKGDˆHùIDATÓc`F&f`ec·¶áàä‚r¹yxmíìøø@|A!gW7wO/oaQ1_?ÿ€À àаðˆHq‰¨è˜Ø¸ø„Ĥä”Ô´tI©ŒÌ¬ìœÜ¼¬ü‚¢âi™Ò²òŠÊªêšÚºú†Æ&Y¹æ–Ö¶öŽÎ®îžÞ¾þ ò Í™'Mž2uÚô3gÍž£È 4wÞü -^²tÙò+W)3¨¬^³vÝú 7mÞ²uÛöª jê;wíÞ³wßþijiª£«wøÈÑcÇOèB}cdlròÔiS3$ÿš[XZAXç2R95uºÒtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/package_green.png000077500000000000000000000021351254023100400167260ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<jPLTEÿÿÿØ´MزK×°JÖ®HÙ¶NØ´MײKÕ¬EÔªDÓ§BÚ·OÙ¶NØ´LѤ?Т=Ï ;Ù·OÙµNÎ9Íš6Ù·OÙµMË•2Ø´MÉ’/ײKÈ-Ö°IÇ+Õ­GÔªDŇ&Ó¨BÄ…$Ò¥@Ã"Т=Ï ;Î9Ä#‚"€ Íš6̘4Ë•2Ć%Ä#‚!Ê“0É.ÈŽ,Ä…$Ã#Æ‹*ʼn(Ň&Ä…$êÕœç̈åÏ’üøæþöãòݯ÷ä²áÀràÆ~÷îÑÿÿöÿþðûíËÚ­P÷׋ùáªòÖ•Û²bíÙ¥õëÒíÞ¾ÞÉ•ÛÉָpÞºjõ×õÏøÛúãªìʄզQþøáùëÍíÒ’ëÌ„å¿oÛ±VÈ”;úìÎÿõÙÿê¹þß•ÿÕwÿä¥ûÜ„ýøâÿóÔúä°ñφõÐõÐxä±Lÿé¶ÿá›þÖxõ½@éµ=ñÕøöÛÿóÙÿóÕÿë½÷Õ‰õÉié´LûÚÿÜŒùÄHì¶8è¿Hç¸HñÓÔç’Øå€ýñÈÿïÈÿëºûÛ’ñÁVùÂHð½;ìÅGé½EæµCÔ­9Ì҈Ɗ(âí¨³ÖAºØIääŒÿê³ÿç­þÙ|ïÇHêÄCè¾Cܳ=©®-ˆ±)»Ó…þûçùóи×F®Ô9ÃÙPòâŽþá–ëÆHã¼?±¯0„¬$­$¿gìăܽwöå¿ÿöÛë螬Ó7¬Ò7ÑØU¼¸7…ª$|ª"©·MëÆ†ä³tÊJàÀ}üîÇþòËÐÝe¨Ñ3‘°5èˉëÁ‚ЗSåÄ‚ÿôÌúî¼äшðΊפ_ëÍâ·oÈŽ@ÿÿÿPñÜŒ:tRNS+¯¼1„ïóŒeÛàl FÂÈMŸþ¦ûûððøúû÷þùø»ÁeÛàm …ïó+¥þ¬1F¾ÄM?€7bKGDˆHúIDATÓc`F&f`ec·²æàä‚r¹yxmlíìùø@|A!'gW7wO/oaQ1_?ÿ€À àаðq‰È¨è˜Ø¸ø„Ĥä”Ô4I©ôŒÌ¬ìœÜ̼ü‚¢bi™’Ò²òŠÊªêšÚºú†FY¹¦æ–Ö¶öŽÎ®îžÞ¾þ ò'Mž2uÚô3gÍž3wžƒâü -^²tÙò+W­^£Ä ¼vÝú 7mÞ²uÛö;w©0¨ª©ïÞ³wßþm:¬¡©tª¶Žî‘£ÇŽŸ8©§oõ¡‘ñ©ÓgLL‘ükfna aRÎÂ/ VtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/page_white_go.png000077500000000000000000000012761254023100400167610ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<)PLTEÿÿÿøøøûûû”””•••™™™ 5‡*{% {wüüü :ùùùüüüÕäÑ!| 2üüüøøøôôôõõõñññïïïéééççç÷÷÷ùùùóóóðððêêêöööòòòëëëûûûìììæææíííp¦>eŸ1íïëúúúi£9³ZW˜(öøôo¥=n¤9h¢5bž1|¯UªË‘t«OFk£7°Î–­Í”¨Ë¼t¡ÇŠh¥FcŸ1Žºo‰¸k…¶f€³a‚µg˜Âƒ\ <[›)©Ê¥È¡ÆŠžÅˆ…¶j—‚f¥HR—"J‘D<‹^Ÿ:–Á€b£F4‰3ˆ _¡C6‰{ÿÿÿÆ"÷–tRNS36 ðý‘‘‘?ýüî1þW4õýõ÷PZ¤€bKGDˆH»IDATÓMÏÕÂ@EÑÒâî6Ð-ÐâîîîîüÿO0a?®äææ„ˆ¤pb "¥2ðÎ%ÿ%sÓ4Íx¼À§@¢¤Ë±¬?¼[%~Çq,/„Ð¥D 좙H0ŠEðÄþJ<‘üJg²¹ä ÅR¹R­©¿Po4+­v§«Ñbè5ûƒáh<™ê0Ìæ‹åj½Ùê ŒvûÃñt¾\Mp‹Ùbý¼¹Ý6‚ÝŒ×;žNÒN¼ ‹Û@;=Œ9:Š6€Íu‚Íx§Ùœ¢Ø˜¢Ø——ÒƒÐyÎv~Ët{Éq¡×—Õ“€Îv¦Ù›¤Ø™¢×˜¡×–žÕ•œÔ“˜Ñ•ÐŒ”ÏŠ^­W}Ës£Ø™‡ËywÃisÂeqÁdsÂgqÀf”Ï‹Z©SzÇpwÄm…É|rÁeoÀbl¾_i½]f»ZψmµfR¡LNHJ™Eo½f™Òm¿`j½^h¼[d»YaºVo¿e‹Ìƒ‰Ê‚ˆÊ€F–Ak¹c–Ñi¼]cºX`¹U]·Rk½bˆÊ…È~„È~B’>n¼ej¸bzÀr“Ï‹d»X_¸S[¶QYµO‡Ê€b¬\B‘=?Ž:•Ï‹’ÏŠrÁiŽÍ†ŒÌ„ŠËƒf»]E”@e´^‘ψa¹VÍ…lµeOžIc­]ƒÇ|Æ{A)0oBDQn¿Å—2½QϾ+Qn.‡š,T‚ Á…B˜§©¡ŠÂ2é=ˆÊÒˆ(ꊨRùù-f n‚а±¾…¶ {ùQÌsoðZ¶íÀɾã.¢Ô­ªC͈-áPúØoÃÉξggÓ>æ’C“èèŠkË/§ÛPœ‰ïã´×wðòÃ(êÆ»é×ÄAßç°{ó~æÂOMôO—åYn˜ »ÿ4AQŠˆ 5[3§f'©_³á÷ ¼éÿ’‡KAÍH¯ë@ÔÈ~Â¥è7/“X%akCšLóaž¼ë§oìî§DèÌvû«áÉØSDAD‹·NŒW¼z@+P†?<Œ£¸Šûc}£3Ûí£¿sð/Õ|!Ê«•­.˜6çº}bqþ ÒF•äóKQøIEND®B`‚Slic3r-1.2.9/var/printer_empty.png000077500000000000000000000006501254023100400170540ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿IDAT(ϥѽ+…qÆñÏÃ“Ž’”Éf9 %2˜°Éÿ`,þve‘ '³:«ÅnĤœ¤“³Îñòœ—ŸÁKOƒ{ºëú^÷}Õ¿OÛºøsÉ‚ %XRÀf§ÉA3ÊpÖ‰•Ùätÿ™(Ø\¶ÝßÕžrܹ¬&ë…|´17p<®÷‡ï%GÎçc[Súìü¬Yr³Ë`Qî›|!+«07;š¸–ýŠøóÊ*QÜPDÙ«j èr¯ˆ¢¸®„' I h¨)á^\— ã/jUT£ÔÅIë¡­[Y=ž,¸Õ-ï%Óõ±á”ûQÍ "áÌAôï6ßT(]¥rý:tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/shading.png000077500000000000000000000005561254023100400155750ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<KPLTEÿÿÿlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇlšÇÿÿÿ 4×2tRNS[Ù†åx‚jâIá„¶wye}JÆã|Òsˆ”ÅÔ¸bKGDˆHLIDATÓµÎ9€0 DÑ6« açþ7! ' Ò4Oš‘ŠÑ7U ³´tôΊ $¾“ÛЯJnùgÒï#sžÌ˃•mç8¹mK5H‰®—tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/shape_flip_horizontal.png000077500000000000000000000006231254023100400205360ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<%IDAT8ËÝÓ=JQà3ù1>4-ÙM .ÀÖÎz nÀf@D› ‚.ÂBA±ÁÂ4)Li)™Iî9×Â‰Š‘`ãYÀ÷Þ;÷¾ÈÝ1OJ˜3œÝÃémÞ€½‹a÷ü9ü =1!ÉcÊ“™ÞMÞ2y×ø^®$ê&½Ak&ÀèéúJ)L“cc­ŒJN®³v#DF- …PV—Ê£§ö·ÀñUŒžÖkrLÓ ¯±XÌ”n<†/£'Íz)®–£ƒ*ž`Âh,˜ÍF56*ù^ŽZFï./D˜§SÓ‰Ù˜åä ÕÝÚ¿ÿ(´R•Rà 耨Xq£0xÉ ”CîAòÀ&Dÿô/ü&o~ß°ŒÖÙ ÜIEND®B`‚Slic3r-1.2.9/var/shape_handles.png000077500000000000000000000010321254023100400167440ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<¬IDAT8Ë¥’1‹Q…¿‘QÐB\AØvI M~Â"ÿ"Xæ„ü„€d‹”I¿e‚v6ÊÚhé$M$³èÂÖ&˜÷îÙ"o& F ½ðàñÞ=‡sν‘$þ§b€Á`JªHJz½^ýo€n·û!Ïó—ιd2™Ôãð^i·ÛÌf³Ú›w72 Þ™ao°Ýnét:ŒF£Z©À{ÿu:6ãGOy~v è€äöÉ9ÃáçÜ'€¨È`ðö§ÎÏì’À¤’ÈL˜‰åMÆ ó"*äz$̰¸Û1Iîí8DyÛ5 Ì `%° tî30/,4íš‹»í•Ìéws†7•>uàYÁR¡Ä"p¼·Òÿ1É^‘ÎNYðïãz*Œò @IxRÁk`Áûá8²**’D«ÕJó<¯<|üŒW‹£Ä¥Kâòâ5ß“Ïxï“,Ëê÷²,«ôû}n,Y\¯Y\o˜§k«_ÌÓ5óÕ†dµ&I7,¿]1qÎÕvó—h6›_†U«Õ+…úÓ‰ãø}E>JÚ¯ò¿Öù5¼oqIRriYyE%D ªº¦¶.§¨¾¡± "À/ ¦…š…1=Ç|&‰P V¾tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/spool.png000066400000000000000000000010411254023100400152770ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<ÏPLTE‘““€­­­ÂÂÂbccåääs}z=J'þþþ~‹‡¦Vv§Hnc7U.;ˆŽŒ¨ ¢Ú©Þc“®Swh*@G$…‹¢šœóËÙø¥Éù•»õ“´«Mr[,ššš¨y…ô´Èÿ²Õÿ§ÒÿÉßõ°Æ×\n3G«‹ävž÷y°ÿºÕÿÜêñ®Éð„¯A #r'Fé…§ÿÀÝŠ•‘µ´µqqq¦„ЍŸ¢‘™ÛÛÛ^_^¥ž Ÿ¤¢|||¬Š˜ ž£££nnnÿÿÿ±»‰tRNS@æØfbKGDDù´˜Á pHYs  šœ¢IDATÓUP‰‚P {9ñVåðoT¼þÿŸŒï¡Â’-i³¦Ý¡\ãs ‚˜% Hr±„ VÊj¥ZÃ×ÍV»£!ÕKÝžÞæí`$-{2ufæ\£î€ëùÁb¹2Ì5ÏÌFA0ÙXêv·¡K‡È·õã)># Ðs.W%a:ÜÛ]y¤¾Ÿ~‚‹“4“A|ý²³Ó¿xg N7ûêrIEND®B`‚Slic3r-1.2.9/var/tag_blue.png000077500000000000000000000013451254023100400157370ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<;PLTEÿÿÿËËËÈÈÈÄÄÄÊÊÊÀÀÀÊÊÊÈÈȽ½½¹¹¹ËË˵µµÉÉÉÇÇDZ±±ÅÅÅÃÃî®®ÁÁÁÀÀÀ¬¬¬½½½¼¼¼¬¬¬ººº¸¸¸«««¶¶¶´´´²²²±±±¯¯¯­­­ªªª«««ªªªÕÕÕÔÔÔÒÒÒÑÑÑÐÐÐÎÎÎÍÍÍÓÓÓýýýäääâââàààËËËóóóûûûÇÇÇ÷÷÷èóû½ãýÞÞÞÃÃÃüüüêõýŠÏþƒÌÿ¼âýÜÜÜÀÀÀëëëúúúçóû‰Îþ–ÔÿšÕÿ‹ÏÿÚÚÚ½½½éééÓëû˜Ôÿž×ÿ›ÖÿÙÙÙ¹¹¹èèèÖìûˆÍý˜Õÿ‰Îÿºáý×××µµµçççœÖÿŽÐÿÌ÷ûüý···ååå†ÍýŒÏÿ—Ñúó÷ùÕìû—Òý÷ûý¶¶¶ØØØÿÿÿ]´$tRNSÍùâö=ö;ö;ö;ö!;ö!;ö8ö8ö!8µ4Í‹bKGDˆHÇIDATÓcPQUS×ÐÔbd€mÐÕb‚ ¨éèiêëèè0C4t´X tôY!š:úlì&¦f:æ`-K+k[;{N—ƒŽ£“³‹«›­Ž»7P€‡×SÇËÆÛÇ(âçÏáÐ öñ Õ Š G€D"£¢cübE@ƈŠÅéÆ'$&ùÅŠC¬–ÔÕINIõ‹•‚9WZFOGÇ/ Îg`•ÓOGæ30È+¤)2 %e 'ˆ!oA¿TXtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/textfield.png000077500000000000000000000002311254023100400161360ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<+IDAT(ÏcøÏ€2ÐAÁÉÿø!PÁ<ðDÁÉA ¯#_üÇA\+Šv*³Îæ“IEND®B`‚Slic3r-1.2.9/var/time.png000077500000000000000000000016771254023100400151230ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ûPLTEÿÿÿ¿¨ŠÆ«~ͳxи{Í´zÄ©q¹q°™zª†Òºx¸œg©lÀ¨…Ÿ…d¹£ˆÌ³t¤ƒX›†j¼¡w˜|Y¾¢m›{S½¡lž{T¸šgyS­Ž^•uP …^tT‡k¢‚W”qM‘{bšawZ•|]šxP”rMvY•}a‘uS“rN—tMtR‘z^ãÙÈêçÝññîððìäàÒÒĨÜÌ—îëáÙáߖ­Õ’ªÔ£·ÜÎÚèßÙÆ¶›hìéßÂÏæ®¿ßÜãñìñóëñôÙâñ£ºÜ¯ÁÞØÏ·ÛÏ·×àíª½ÞÿÿÿîððÚåç÷ÿÿóþÿ˜´ÜÄÑỤsæáÓ¤¹ÛÖãñõûþóõõ•žŸéððòøø­¹¾ÂÚð‹§ÑÓÊ©îì䊥Ñéöúîøýùÿÿy€¤®±wºÆÉÓðúr•ÉâßÉìêÝŸÏâóúéöýãðò„ŒÂÏÒëûÿÌìúmÇáÝÁÝÓ·ªÑÁÛðæ÷ÿãòú¶ÀÅÞíñèüÿßõÿ¯Ôï{›ÉÐÂ’À©xÆÑÚˆªÙëþÿîúÿòýÿòþÿïüÿëýÿz£Ù¹ÆË±”XØÌ¢¥·Í‰ªØÏßïóöúóõúÑßט¬Ã킦ˆUÖÈ–¿Ê̆ŸÈv’ÅsÄ€šÄ¹ÄÃͽ}˜wG±—Zν…ÞÖ¤Þՠ˹{«ŒOŽê/tRNSB½íúúè°9–ùô}‘u<úõ1¶£çß÷ö÷öÞ⟪0ö÷5x€}óô‚6§áö©7ôß6FbKGDˆHÖIDATÓc`À™˜YXÙØ9`|N.}C#cnŸ×ÔÌÜÂÒÊÚÆÖŽÄç°wptrvqus÷ {zyûøúùû‹DCBÃÂ#"£¢cbãâŀ⠉IÉ)©ié™YÙ@ɜܼü‚Âô¢â’Ò2) €tyEeUuMm]}Cc“ P@¶¹¥µ­½£³«»§·O( ¯Ð?aâ¤ÉS¦N›>CQ ä噳fÏ™;oþ‚…‹T NUU[¼dé²å+Ô5`žÑÔÒÖÑÑÖÕÃês«ù8‚¼Ç˜tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/wrench.png000077500000000000000000000013461254023100400154440ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<YPLTEÿÿÿžžžœœœ›››™™™››››››˜˜˜’’’ŠŠŠ–––‡‡‡•••“““………‹­ãˆ§Ú’’’„„„‚‚‚‰©á‰‰‰………ƒƒƒ‡©áˆˆˆ†††…§á]‰Òƒ¥ßQƒÕ¥ßOÓMÓI}Ñe‘ÙG{ÑW‡ÕEyÑäääîîî–––âââççç¹¹¹“““ÏÏÏŽŽŽŒŒŒÝÝÝÅÅŬ¬¬×××ÔÔÔÈÈȼ¼¼ºººÂÂÂÄÄÄÇÇÇÌÌÌÆÆÆÃÃÃÀÀÀœ¹ç˜¶æŒ©ØÒÒÒµµµœ·æ¼ÏïºÎâ{›Ò™·æ»Îî§À飽è³Èì¤ß˜´æºÍ閴å¹ç®ÆëyŸÞ”³æ¸Ì锳囷æ­ÄêxžÞyŸÝ·Ì풱䚶æ¬ÃêtœÞo—Û³È훸ç«ÃêsœÞ«Âê¨Àêq™ÜQƒÕK}Ñÿÿÿ ”(+tRNSpÖûûpõÖûÂûüÁÃûHþÖ’Ïûõp’þûÖp’÷#’É’ª’¬¬¯Ì¯Õ¯ÅXtYbKGDˆH£IDATÓc`€F&f$Àʦ­£‹,À®§o`ˆ,À¡mÄÉ`lÂà65ãá5·àƒñù,­¬mlí!\!a{{G'gQ10_ÜÅÕÍÝC‚ORJÌ—ñôòöñ••ƒi—÷ó V€ñCBÃÂ#"£” |åè˜Ø¸ø„D˜‚¤äÀ”Ô´tU¸ƒ22³B²sÔNVÎÍË×@ö„fA¡ETšoÇ”“tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-1.2.9/var/zoom.png000077500000000000000000000012641254023100400151410ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<FIDAT8ËÒKHÔAÀñï̪-–ŠK¾C=Ø ¡CmÒfuîP–‰)D—êB§"ÂK»$A„ e¤¦t¶¨¤ð ô‚JŒÍZÿyuÙmüÁ0ø}f~3#œsLÞ²|!!|g-V©9­¼Që鎺³ýc¬`²ïT«™C8*¬—èô£5Þb¬F'âÎR!DÆÐ‹› ­kbâ^sPÈÌ!«½»²¾«¨ÊLocÂgÀע≣‡.=^uéíÖ˜ˆ¬ïê[ €¨ê65ôÞ¶FG´§• !§Ô P ä%wN5•\«Òq=(¡t@ȀѶ(t2)Õ§ "«L?Bþ2¬uXcY‘äV$¦ÆÚƒÔ&ý´çÍa]còÂt²¥€Ô<â *¡æÒã©QçÄ1àð}E )h˜’Y—­§§Ò„ê°Ž’…æ6` øDXr<:=UR½Íï÷ÌžáóûO®úÎ9žÝ8Ò*}òªV&b==h”é·ÖqÙÜ+;¶ËœâJT<ÆÔÓçüúò­øø­—³ËÀÈõÃA !CÎ:”§£¾Ì¼Êú}änAHÉ~0bM”ž¸36» ü+†/ç——ö”ƒøsóIĢ̾™àÉÀ«ÏEkÿ ž …óJ {ÊvíÄ!ùùé=_ßMÏyKªb]Àö½aµ¤{6W°03ñ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', build_requires => {qw( ExtUtils::ParseXS 3.18 ExtUtils::Typemaps 1.00 ExtUtils::Typemaps::Default 1.05 ExtUtils::XSpp 0.17 Module::Build 0.3601 Test::More 0 )}, configure_requires => {qw( ExtUtils::CppGuess 0.07 Module::Build 0.38 Module::Build::WithXSpp 0.13 )}, extra_compiler_flags => \@cflags, # Provides extra C typemaps that are auto-merged extra_typemap_modules => { 'ExtUtils::Typemaps::Basic' => '1.05', }, # for MSVC builds early_includes => [qw( cstring cstdlib ostream sstream )] ); $build->create_build_script; __END__ Slic3r-1.2.9/xs/MANIFEST000066400000000000000000002212441254023100400144410ustar00rootroot00000000000000Build.PL lib/Slic3r/XS.pm MANIFEST src/admesh/connect.c src/admesh/normals.c src/admesh/shared.c src/admesh/stl.h src/admesh/stl_io.c src/admesh/stlinit.c src/admesh/util.c src/boost/aligned_storage.hpp src/boost/array.hpp src/boost/assert.hpp src/boost/bind.hpp src/boost/bind/arg.hpp src/boost/bind/bind.hpp src/boost/bind/bind_cc.hpp src/boost/bind/bind_mf2_cc.hpp src/boost/bind/bind_mf_cc.hpp src/boost/bind/bind_template.hpp src/boost/bind/mem_fn.hpp src/boost/bind/mem_fn_cc.hpp src/boost/bind/mem_fn_template.hpp src/boost/bind/mem_fn_vw.hpp src/boost/bind/placeholders.hpp src/boost/bind/storage.hpp src/boost/call_traits.hpp src/boost/cerrno.hpp src/boost/checked_delete.hpp src/boost/chrono/chrono.hpp src/boost/chrono/clock_string.hpp src/boost/chrono/config.hpp src/boost/chrono/detail/inlined/chrono.hpp src/boost/chrono/detail/inlined/mac/chrono.hpp src/boost/chrono/detail/inlined/posix/chrono.hpp src/boost/chrono/detail/inlined/win/chrono.hpp src/boost/chrono/detail/is_evenly_divisible_by.hpp src/boost/chrono/detail/static_assert.hpp src/boost/chrono/detail/system.hpp src/boost/chrono/duration.hpp src/boost/chrono/system_clocks.hpp src/boost/chrono/time_point.hpp src/boost/concept/assert.hpp src/boost/concept/detail/backward_compatibility.hpp src/boost/concept/detail/borland.hpp src/boost/concept/detail/concept_def.hpp src/boost/concept/detail/concept_undef.hpp src/boost/concept/detail/general.hpp src/boost/concept/detail/has_constraints.hpp src/boost/concept/detail/msvc.hpp src/boost/concept/usage.hpp src/boost/concept_check.hpp src/boost/config.hpp src/boost/config/abi/borland_prefix.hpp src/boost/config/abi/borland_suffix.hpp src/boost/config/abi/msvc_prefix.hpp src/boost/config/abi/msvc_suffix.hpp src/boost/config/abi_prefix.hpp src/boost/config/abi_suffix.hpp src/boost/config/auto_link.hpp src/boost/config/compiler/borland.hpp src/boost/config/compiler/clang.hpp src/boost/config/compiler/codegear.hpp src/boost/config/compiler/comeau.hpp src/boost/config/compiler/common_edg.hpp src/boost/config/compiler/compaq_cxx.hpp src/boost/config/compiler/cray.hpp src/boost/config/compiler/digitalmars.hpp src/boost/config/compiler/gcc.hpp src/boost/config/compiler/gcc_xml.hpp src/boost/config/compiler/greenhills.hpp src/boost/config/compiler/hp_acc.hpp src/boost/config/compiler/intel.hpp src/boost/config/compiler/kai.hpp src/boost/config/compiler/metrowerks.hpp src/boost/config/compiler/mpw.hpp src/boost/config/compiler/nvcc.hpp src/boost/config/compiler/pathscale.hpp src/boost/config/compiler/pgi.hpp src/boost/config/compiler/sgi_mipspro.hpp src/boost/config/compiler/sunpro_cc.hpp src/boost/config/compiler/vacpp.hpp src/boost/config/compiler/visualc.hpp src/boost/config/no_tr1/cmath.hpp src/boost/config/no_tr1/complex.hpp src/boost/config/no_tr1/functional.hpp src/boost/config/no_tr1/memory.hpp src/boost/config/no_tr1/utility.hpp src/boost/config/platform/aix.hpp src/boost/config/platform/amigaos.hpp src/boost/config/platform/beos.hpp src/boost/config/platform/bsd.hpp src/boost/config/platform/cray.hpp src/boost/config/platform/cygwin.hpp src/boost/config/platform/hpux.hpp src/boost/config/platform/irix.hpp src/boost/config/platform/linux.hpp src/boost/config/platform/macos.hpp src/boost/config/platform/qnxnto.hpp src/boost/config/platform/solaris.hpp src/boost/config/platform/symbian.hpp src/boost/config/platform/vms.hpp src/boost/config/platform/vxworks.hpp src/boost/config/platform/win32.hpp src/boost/config/posix_features.hpp src/boost/config/requires_threads.hpp src/boost/config/select_compiler_config.hpp src/boost/config/select_platform_config.hpp src/boost/config/select_stdlib_config.hpp src/boost/config/stdlib/dinkumware.hpp src/boost/config/stdlib/libcomo.hpp src/boost/config/stdlib/libcpp.hpp src/boost/config/stdlib/libstdcpp3.hpp src/boost/config/stdlib/modena.hpp src/boost/config/stdlib/msl.hpp src/boost/config/stdlib/roguewave.hpp src/boost/config/stdlib/sgi.hpp src/boost/config/stdlib/stlport.hpp src/boost/config/stdlib/vacpp.hpp src/boost/config/suffix.hpp src/boost/config/user.hpp src/boost/config/warning_disable.hpp src/boost/container/container_fwd.hpp src/boost/cstdint.hpp src/boost/cstdlib.hpp src/boost/current_function.hpp src/boost/detail/binary_search.hpp src/boost/detail/call_traits.hpp src/boost/detail/endian.hpp src/boost/detail/fenv.hpp src/boost/detail/indirect_traits.hpp src/boost/detail/interlocked.hpp src/boost/detail/is_function_ref_tester.hpp src/boost/detail/iterator.hpp src/boost/detail/lcast_precision.hpp src/boost/detail/lightweight_mutex.hpp src/boost/detail/ob_call_traits.hpp src/boost/detail/reference_content.hpp src/boost/detail/sp_typeinfo.hpp src/boost/detail/winapi/basic_types.hpp src/boost/detail/winapi/GetLastError.hpp src/boost/detail/winapi/time.hpp src/boost/detail/winapi/timers.hpp src/boost/detail/workaround.hpp src/boost/exception/current_exception_cast.hpp src/boost/exception/detail/attribute_noreturn.hpp src/boost/exception/detail/error_info_impl.hpp src/boost/exception/detail/type_info.hpp src/boost/exception/exception.hpp src/boost/exception/get_error_info.hpp src/boost/functional/hash/hash_fwd.hpp src/boost/functional/hash_fwd.hpp src/boost/get_pointer.hpp src/boost/integer.hpp src/boost/integer/integer_mask.hpp src/boost/integer/static_log2.hpp src/boost/integer_fwd.hpp src/boost/integer_traits.hpp src/boost/io/ios_state.hpp src/boost/io_fwd.hpp src/boost/is_placeholder.hpp src/boost/iterator.hpp src/boost/iterator/detail/config_def.hpp src/boost/iterator/detail/config_undef.hpp src/boost/iterator/detail/enable_if.hpp src/boost/iterator/detail/facade_iterator_category.hpp src/boost/iterator/interoperable.hpp src/boost/iterator/iterator_adaptor.hpp src/boost/iterator/iterator_categories.hpp src/boost/iterator/iterator_concepts.hpp src/boost/iterator/iterator_facade.hpp src/boost/iterator/iterator_traits.hpp src/boost/iterator/reverse_iterator.hpp src/boost/lexical_cast.hpp src/boost/limits.hpp src/boost/math/common_factor_rt.hpp src/boost/math/policies/policy.hpp src/boost/math/special_functions/detail/fp_traits.hpp src/boost/math/special_functions/detail/round_fwd.hpp src/boost/math/special_functions/fpclassify.hpp src/boost/math/special_functions/math_fwd.hpp src/boost/math/special_functions/sign.hpp src/boost/math/tools/config.hpp src/boost/math/tools/promotion.hpp src/boost/math/tools/real_cast.hpp src/boost/math/tools/user.hpp src/boost/math_fwd.hpp src/boost/mem_fn.hpp src/boost/memory_order.hpp src/boost/mpl/advance.hpp src/boost/mpl/advance_fwd.hpp src/boost/mpl/always.hpp src/boost/mpl/and.hpp src/boost/mpl/apply.hpp src/boost/mpl/apply_fwd.hpp src/boost/mpl/apply_wrap.hpp src/boost/mpl/arg.hpp src/boost/mpl/arg_fwd.hpp src/boost/mpl/assert.hpp src/boost/mpl/at.hpp src/boost/mpl/at_fwd.hpp src/boost/mpl/aux_/adl_barrier.hpp src/boost/mpl/aux_/advance_backward.hpp src/boost/mpl/aux_/advance_forward.hpp src/boost/mpl/aux_/arg_typedef.hpp src/boost/mpl/aux_/arithmetic_op.hpp src/boost/mpl/aux_/arity.hpp src/boost/mpl/aux_/arity_spec.hpp src/boost/mpl/aux_/at_impl.hpp src/boost/mpl/aux_/begin_end_impl.hpp src/boost/mpl/aux_/clear_impl.hpp src/boost/mpl/aux_/common_name_wknd.hpp src/boost/mpl/aux_/comparison_op.hpp src/boost/mpl/aux_/config/adl.hpp src/boost/mpl/aux_/config/arrays.hpp src/boost/mpl/aux_/config/bcc.hpp src/boost/mpl/aux_/config/bind.hpp src/boost/mpl/aux_/config/compiler.hpp src/boost/mpl/aux_/config/ctps.hpp src/boost/mpl/aux_/config/dependent_nttp.hpp src/boost/mpl/aux_/config/dmc_ambiguous_ctps.hpp src/boost/mpl/aux_/config/dtp.hpp src/boost/mpl/aux_/config/eti.hpp src/boost/mpl/aux_/config/forwarding.hpp src/boost/mpl/aux_/config/gcc.hpp src/boost/mpl/aux_/config/has_apply.hpp src/boost/mpl/aux_/config/has_xxx.hpp src/boost/mpl/aux_/config/integral.hpp src/boost/mpl/aux_/config/intel.hpp src/boost/mpl/aux_/config/lambda.hpp src/boost/mpl/aux_/config/msvc.hpp src/boost/mpl/aux_/config/msvc_typename.hpp src/boost/mpl/aux_/config/nttp.hpp src/boost/mpl/aux_/config/overload_resolution.hpp src/boost/mpl/aux_/config/pp_counter.hpp src/boost/mpl/aux_/config/preprocessor.hpp src/boost/mpl/aux_/config/static_constant.hpp src/boost/mpl/aux_/config/ttp.hpp src/boost/mpl/aux_/config/typeof.hpp src/boost/mpl/aux_/config/use_preprocessed.hpp src/boost/mpl/aux_/config/workaround.hpp src/boost/mpl/aux_/contains_impl.hpp src/boost/mpl/aux_/count_args.hpp src/boost/mpl/aux_/find_if_pred.hpp src/boost/mpl/aux_/fold_impl.hpp src/boost/mpl/aux_/fold_impl_body.hpp src/boost/mpl/aux_/full_lambda.hpp src/boost/mpl/aux_/has_apply.hpp src/boost/mpl/aux_/has_begin.hpp src/boost/mpl/aux_/has_rebind.hpp src/boost/mpl/aux_/has_size.hpp src/boost/mpl/aux_/has_tag.hpp src/boost/mpl/aux_/has_type.hpp src/boost/mpl/aux_/include_preprocessed.hpp src/boost/mpl/aux_/inserter_algorithm.hpp src/boost/mpl/aux_/integral_wrapper.hpp src/boost/mpl/aux_/is_msvc_eti_arg.hpp src/boost/mpl/aux_/iter_apply.hpp src/boost/mpl/aux_/iter_fold_if_impl.hpp src/boost/mpl/aux_/iter_fold_impl.hpp src/boost/mpl/aux_/lambda_arity_param.hpp src/boost/mpl/aux_/lambda_no_ctps.hpp src/boost/mpl/aux_/lambda_spec.hpp src/boost/mpl/aux_/lambda_support.hpp src/boost/mpl/aux_/largest_int.hpp src/boost/mpl/aux_/logical_op.hpp src/boost/mpl/aux_/msvc_dtw.hpp src/boost/mpl/aux_/msvc_eti_base.hpp src/boost/mpl/aux_/msvc_is_class.hpp src/boost/mpl/aux_/msvc_never_true.hpp src/boost/mpl/aux_/msvc_type.hpp src/boost/mpl/aux_/na.hpp src/boost/mpl/aux_/na_assert.hpp src/boost/mpl/aux_/na_fwd.hpp src/boost/mpl/aux_/na_spec.hpp src/boost/mpl/aux_/nested_type_wknd.hpp src/boost/mpl/aux_/nttp_decl.hpp src/boost/mpl/aux_/numeric_cast_utils.hpp src/boost/mpl/aux_/numeric_op.hpp src/boost/mpl/aux_/O1_size_impl.hpp src/boost/mpl/aux_/preprocessed/bcc/advance_backward.hpp src/boost/mpl/aux_/preprocessed/bcc/advance_forward.hpp src/boost/mpl/aux_/preprocessed/bcc/and.hpp src/boost/mpl/aux_/preprocessed/bcc/apply.hpp src/boost/mpl/aux_/preprocessed/bcc/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/bcc/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/bcc/arg.hpp src/boost/mpl/aux_/preprocessed/bcc/basic_bind.hpp src/boost/mpl/aux_/preprocessed/bcc/bind.hpp src/boost/mpl/aux_/preprocessed/bcc/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/bcc/bitand.hpp src/boost/mpl/aux_/preprocessed/bcc/bitor.hpp src/boost/mpl/aux_/preprocessed/bcc/bitxor.hpp src/boost/mpl/aux_/preprocessed/bcc/deque.hpp src/boost/mpl/aux_/preprocessed/bcc/divides.hpp src/boost/mpl/aux_/preprocessed/bcc/equal_to.hpp src/boost/mpl/aux_/preprocessed/bcc/fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc/full_lambda.hpp src/boost/mpl/aux_/preprocessed/bcc/greater.hpp src/boost/mpl/aux_/preprocessed/bcc/greater_equal.hpp src/boost/mpl/aux_/preprocessed/bcc/inherit.hpp src/boost/mpl/aux_/preprocessed/bcc/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/bcc/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/bcc/less.hpp src/boost/mpl/aux_/preprocessed/bcc/less_equal.hpp src/boost/mpl/aux_/preprocessed/bcc/list.hpp src/boost/mpl/aux_/preprocessed/bcc/list_c.hpp src/boost/mpl/aux_/preprocessed/bcc/map.hpp src/boost/mpl/aux_/preprocessed/bcc/minus.hpp src/boost/mpl/aux_/preprocessed/bcc/modulus.hpp src/boost/mpl/aux_/preprocessed/bcc/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/bcc/or.hpp src/boost/mpl/aux_/preprocessed/bcc/placeholders.hpp src/boost/mpl/aux_/preprocessed/bcc/plus.hpp src/boost/mpl/aux_/preprocessed/bcc/quote.hpp src/boost/mpl/aux_/preprocessed/bcc/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc/set.hpp src/boost/mpl/aux_/preprocessed/bcc/set_c.hpp src/boost/mpl/aux_/preprocessed/bcc/shift_left.hpp src/boost/mpl/aux_/preprocessed/bcc/shift_right.hpp src/boost/mpl/aux_/preprocessed/bcc/template_arity.hpp src/boost/mpl/aux_/preprocessed/bcc/times.hpp src/boost/mpl/aux_/preprocessed/bcc/unpack_args.hpp src/boost/mpl/aux_/preprocessed/bcc/vector.hpp src/boost/mpl/aux_/preprocessed/bcc/vector_c.hpp src/boost/mpl/aux_/preprocessed/bcc551/advance_backward.hpp src/boost/mpl/aux_/preprocessed/bcc551/advance_forward.hpp src/boost/mpl/aux_/preprocessed/bcc551/and.hpp src/boost/mpl/aux_/preprocessed/bcc551/apply.hpp src/boost/mpl/aux_/preprocessed/bcc551/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/bcc551/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/bcc551/arg.hpp src/boost/mpl/aux_/preprocessed/bcc551/basic_bind.hpp src/boost/mpl/aux_/preprocessed/bcc551/bind.hpp src/boost/mpl/aux_/preprocessed/bcc551/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/bcc551/bitand.hpp src/boost/mpl/aux_/preprocessed/bcc551/bitor.hpp src/boost/mpl/aux_/preprocessed/bcc551/bitxor.hpp src/boost/mpl/aux_/preprocessed/bcc551/deque.hpp src/boost/mpl/aux_/preprocessed/bcc551/divides.hpp src/boost/mpl/aux_/preprocessed/bcc551/equal_to.hpp src/boost/mpl/aux_/preprocessed/bcc551/fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc551/full_lambda.hpp src/boost/mpl/aux_/preprocessed/bcc551/greater.hpp src/boost/mpl/aux_/preprocessed/bcc551/greater_equal.hpp src/boost/mpl/aux_/preprocessed/bcc551/inherit.hpp src/boost/mpl/aux_/preprocessed/bcc551/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/bcc551/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc551/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/bcc551/less.hpp src/boost/mpl/aux_/preprocessed/bcc551/less_equal.hpp src/boost/mpl/aux_/preprocessed/bcc551/list.hpp src/boost/mpl/aux_/preprocessed/bcc551/list_c.hpp src/boost/mpl/aux_/preprocessed/bcc551/map.hpp src/boost/mpl/aux_/preprocessed/bcc551/minus.hpp src/boost/mpl/aux_/preprocessed/bcc551/modulus.hpp src/boost/mpl/aux_/preprocessed/bcc551/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/bcc551/or.hpp src/boost/mpl/aux_/preprocessed/bcc551/placeholders.hpp src/boost/mpl/aux_/preprocessed/bcc551/plus.hpp src/boost/mpl/aux_/preprocessed/bcc551/quote.hpp src/boost/mpl/aux_/preprocessed/bcc551/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc551/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc551/set.hpp src/boost/mpl/aux_/preprocessed/bcc551/set_c.hpp src/boost/mpl/aux_/preprocessed/bcc551/shift_left.hpp src/boost/mpl/aux_/preprocessed/bcc551/shift_right.hpp src/boost/mpl/aux_/preprocessed/bcc551/template_arity.hpp src/boost/mpl/aux_/preprocessed/bcc551/times.hpp src/boost/mpl/aux_/preprocessed/bcc551/unpack_args.hpp src/boost/mpl/aux_/preprocessed/bcc551/vector.hpp src/boost/mpl/aux_/preprocessed/bcc551/vector_c.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/advance_backward.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/advance_forward.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/and.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/apply.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/arg.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/basic_bind.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/bind.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/bitand.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/bitor.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/bitxor.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/deque.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/divides.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/equal_to.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/full_lambda.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/greater.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/greater_equal.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/inherit.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/less.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/less_equal.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/list.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/list_c.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/map.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/minus.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/modulus.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/or.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/placeholders.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/plus.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/quote.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/set.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/set_c.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/shift_left.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/shift_right.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/template_arity.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/times.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/unpack_args.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/vector.hpp src/boost/mpl/aux_/preprocessed/bcc_pre590/vector_c.hpp src/boost/mpl/aux_/preprocessed/dmc/advance_backward.hpp src/boost/mpl/aux_/preprocessed/dmc/advance_forward.hpp src/boost/mpl/aux_/preprocessed/dmc/and.hpp src/boost/mpl/aux_/preprocessed/dmc/apply.hpp src/boost/mpl/aux_/preprocessed/dmc/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/dmc/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/dmc/arg.hpp src/boost/mpl/aux_/preprocessed/dmc/basic_bind.hpp src/boost/mpl/aux_/preprocessed/dmc/bind.hpp src/boost/mpl/aux_/preprocessed/dmc/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/dmc/bitand.hpp src/boost/mpl/aux_/preprocessed/dmc/bitor.hpp src/boost/mpl/aux_/preprocessed/dmc/bitxor.hpp src/boost/mpl/aux_/preprocessed/dmc/deque.hpp src/boost/mpl/aux_/preprocessed/dmc/divides.hpp src/boost/mpl/aux_/preprocessed/dmc/equal_to.hpp src/boost/mpl/aux_/preprocessed/dmc/fold_impl.hpp src/boost/mpl/aux_/preprocessed/dmc/full_lambda.hpp src/boost/mpl/aux_/preprocessed/dmc/greater.hpp src/boost/mpl/aux_/preprocessed/dmc/greater_equal.hpp src/boost/mpl/aux_/preprocessed/dmc/inherit.hpp src/boost/mpl/aux_/preprocessed/dmc/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/dmc/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/dmc/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/dmc/less.hpp src/boost/mpl/aux_/preprocessed/dmc/less_equal.hpp src/boost/mpl/aux_/preprocessed/dmc/list.hpp src/boost/mpl/aux_/preprocessed/dmc/list_c.hpp src/boost/mpl/aux_/preprocessed/dmc/map.hpp src/boost/mpl/aux_/preprocessed/dmc/minus.hpp src/boost/mpl/aux_/preprocessed/dmc/modulus.hpp src/boost/mpl/aux_/preprocessed/dmc/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/dmc/or.hpp src/boost/mpl/aux_/preprocessed/dmc/placeholders.hpp src/boost/mpl/aux_/preprocessed/dmc/plus.hpp src/boost/mpl/aux_/preprocessed/dmc/quote.hpp src/boost/mpl/aux_/preprocessed/dmc/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/dmc/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/dmc/set.hpp src/boost/mpl/aux_/preprocessed/dmc/set_c.hpp src/boost/mpl/aux_/preprocessed/dmc/shift_left.hpp src/boost/mpl/aux_/preprocessed/dmc/shift_right.hpp src/boost/mpl/aux_/preprocessed/dmc/template_arity.hpp src/boost/mpl/aux_/preprocessed/dmc/times.hpp src/boost/mpl/aux_/preprocessed/dmc/unpack_args.hpp src/boost/mpl/aux_/preprocessed/dmc/vector.hpp src/boost/mpl/aux_/preprocessed/dmc/vector_c.hpp src/boost/mpl/aux_/preprocessed/gcc/advance_backward.hpp src/boost/mpl/aux_/preprocessed/gcc/advance_forward.hpp src/boost/mpl/aux_/preprocessed/gcc/and.hpp src/boost/mpl/aux_/preprocessed/gcc/apply.hpp src/boost/mpl/aux_/preprocessed/gcc/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/gcc/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/gcc/arg.hpp src/boost/mpl/aux_/preprocessed/gcc/basic_bind.hpp src/boost/mpl/aux_/preprocessed/gcc/bind.hpp src/boost/mpl/aux_/preprocessed/gcc/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/gcc/bitand.hpp src/boost/mpl/aux_/preprocessed/gcc/bitor.hpp src/boost/mpl/aux_/preprocessed/gcc/bitxor.hpp src/boost/mpl/aux_/preprocessed/gcc/deque.hpp src/boost/mpl/aux_/preprocessed/gcc/divides.hpp src/boost/mpl/aux_/preprocessed/gcc/equal_to.hpp src/boost/mpl/aux_/preprocessed/gcc/fold_impl.hpp src/boost/mpl/aux_/preprocessed/gcc/full_lambda.hpp src/boost/mpl/aux_/preprocessed/gcc/greater.hpp src/boost/mpl/aux_/preprocessed/gcc/greater_equal.hpp src/boost/mpl/aux_/preprocessed/gcc/inherit.hpp src/boost/mpl/aux_/preprocessed/gcc/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/gcc/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/gcc/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/gcc/less.hpp src/boost/mpl/aux_/preprocessed/gcc/less_equal.hpp src/boost/mpl/aux_/preprocessed/gcc/list.hpp src/boost/mpl/aux_/preprocessed/gcc/list_c.hpp src/boost/mpl/aux_/preprocessed/gcc/map.hpp src/boost/mpl/aux_/preprocessed/gcc/minus.hpp src/boost/mpl/aux_/preprocessed/gcc/modulus.hpp src/boost/mpl/aux_/preprocessed/gcc/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/gcc/or.hpp src/boost/mpl/aux_/preprocessed/gcc/placeholders.hpp src/boost/mpl/aux_/preprocessed/gcc/plus.hpp src/boost/mpl/aux_/preprocessed/gcc/quote.hpp src/boost/mpl/aux_/preprocessed/gcc/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/gcc/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/gcc/set.hpp src/boost/mpl/aux_/preprocessed/gcc/set_c.hpp src/boost/mpl/aux_/preprocessed/gcc/shift_left.hpp src/boost/mpl/aux_/preprocessed/gcc/shift_right.hpp src/boost/mpl/aux_/preprocessed/gcc/template_arity.hpp src/boost/mpl/aux_/preprocessed/gcc/times.hpp src/boost/mpl/aux_/preprocessed/gcc/unpack_args.hpp src/boost/mpl/aux_/preprocessed/gcc/vector.hpp src/boost/mpl/aux_/preprocessed/gcc/vector_c.hpp src/boost/mpl/aux_/preprocessed/msvc60/advance_backward.hpp src/boost/mpl/aux_/preprocessed/msvc60/advance_forward.hpp src/boost/mpl/aux_/preprocessed/msvc60/and.hpp src/boost/mpl/aux_/preprocessed/msvc60/apply.hpp src/boost/mpl/aux_/preprocessed/msvc60/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/msvc60/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/msvc60/arg.hpp src/boost/mpl/aux_/preprocessed/msvc60/basic_bind.hpp src/boost/mpl/aux_/preprocessed/msvc60/bind.hpp src/boost/mpl/aux_/preprocessed/msvc60/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/msvc60/bitand.hpp src/boost/mpl/aux_/preprocessed/msvc60/bitor.hpp src/boost/mpl/aux_/preprocessed/msvc60/bitxor.hpp src/boost/mpl/aux_/preprocessed/msvc60/deque.hpp src/boost/mpl/aux_/preprocessed/msvc60/divides.hpp src/boost/mpl/aux_/preprocessed/msvc60/equal_to.hpp src/boost/mpl/aux_/preprocessed/msvc60/fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc60/full_lambda.hpp src/boost/mpl/aux_/preprocessed/msvc60/greater.hpp src/boost/mpl/aux_/preprocessed/msvc60/greater_equal.hpp src/boost/mpl/aux_/preprocessed/msvc60/inherit.hpp src/boost/mpl/aux_/preprocessed/msvc60/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/msvc60/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc60/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/msvc60/less.hpp src/boost/mpl/aux_/preprocessed/msvc60/less_equal.hpp src/boost/mpl/aux_/preprocessed/msvc60/list.hpp src/boost/mpl/aux_/preprocessed/msvc60/list_c.hpp src/boost/mpl/aux_/preprocessed/msvc60/map.hpp src/boost/mpl/aux_/preprocessed/msvc60/minus.hpp src/boost/mpl/aux_/preprocessed/msvc60/modulus.hpp src/boost/mpl/aux_/preprocessed/msvc60/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/msvc60/or.hpp src/boost/mpl/aux_/preprocessed/msvc60/placeholders.hpp src/boost/mpl/aux_/preprocessed/msvc60/plus.hpp src/boost/mpl/aux_/preprocessed/msvc60/quote.hpp src/boost/mpl/aux_/preprocessed/msvc60/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc60/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc60/set.hpp src/boost/mpl/aux_/preprocessed/msvc60/set_c.hpp src/boost/mpl/aux_/preprocessed/msvc60/shift_left.hpp src/boost/mpl/aux_/preprocessed/msvc60/shift_right.hpp src/boost/mpl/aux_/preprocessed/msvc60/template_arity.hpp src/boost/mpl/aux_/preprocessed/msvc60/times.hpp src/boost/mpl/aux_/preprocessed/msvc60/unpack_args.hpp src/boost/mpl/aux_/preprocessed/msvc60/vector.hpp src/boost/mpl/aux_/preprocessed/msvc60/vector_c.hpp src/boost/mpl/aux_/preprocessed/msvc70/advance_backward.hpp src/boost/mpl/aux_/preprocessed/msvc70/advance_forward.hpp src/boost/mpl/aux_/preprocessed/msvc70/and.hpp src/boost/mpl/aux_/preprocessed/msvc70/apply.hpp src/boost/mpl/aux_/preprocessed/msvc70/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/msvc70/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/msvc70/arg.hpp src/boost/mpl/aux_/preprocessed/msvc70/basic_bind.hpp src/boost/mpl/aux_/preprocessed/msvc70/bind.hpp src/boost/mpl/aux_/preprocessed/msvc70/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/msvc70/bitand.hpp src/boost/mpl/aux_/preprocessed/msvc70/bitor.hpp src/boost/mpl/aux_/preprocessed/msvc70/bitxor.hpp src/boost/mpl/aux_/preprocessed/msvc70/deque.hpp src/boost/mpl/aux_/preprocessed/msvc70/divides.hpp src/boost/mpl/aux_/preprocessed/msvc70/equal_to.hpp src/boost/mpl/aux_/preprocessed/msvc70/fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc70/full_lambda.hpp src/boost/mpl/aux_/preprocessed/msvc70/greater.hpp src/boost/mpl/aux_/preprocessed/msvc70/greater_equal.hpp src/boost/mpl/aux_/preprocessed/msvc70/inherit.hpp src/boost/mpl/aux_/preprocessed/msvc70/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/msvc70/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc70/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/msvc70/less.hpp src/boost/mpl/aux_/preprocessed/msvc70/less_equal.hpp src/boost/mpl/aux_/preprocessed/msvc70/list.hpp src/boost/mpl/aux_/preprocessed/msvc70/list_c.hpp src/boost/mpl/aux_/preprocessed/msvc70/map.hpp src/boost/mpl/aux_/preprocessed/msvc70/minus.hpp src/boost/mpl/aux_/preprocessed/msvc70/modulus.hpp src/boost/mpl/aux_/preprocessed/msvc70/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/msvc70/or.hpp src/boost/mpl/aux_/preprocessed/msvc70/placeholders.hpp src/boost/mpl/aux_/preprocessed/msvc70/plus.hpp src/boost/mpl/aux_/preprocessed/msvc70/quote.hpp src/boost/mpl/aux_/preprocessed/msvc70/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc70/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/msvc70/set.hpp src/boost/mpl/aux_/preprocessed/msvc70/set_c.hpp src/boost/mpl/aux_/preprocessed/msvc70/shift_left.hpp src/boost/mpl/aux_/preprocessed/msvc70/shift_right.hpp src/boost/mpl/aux_/preprocessed/msvc70/template_arity.hpp src/boost/mpl/aux_/preprocessed/msvc70/times.hpp src/boost/mpl/aux_/preprocessed/msvc70/unpack_args.hpp src/boost/mpl/aux_/preprocessed/msvc70/vector.hpp src/boost/mpl/aux_/preprocessed/msvc70/vector_c.hpp src/boost/mpl/aux_/preprocessed/mwcw/advance_backward.hpp src/boost/mpl/aux_/preprocessed/mwcw/advance_forward.hpp src/boost/mpl/aux_/preprocessed/mwcw/and.hpp src/boost/mpl/aux_/preprocessed/mwcw/apply.hpp src/boost/mpl/aux_/preprocessed/mwcw/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/mwcw/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/mwcw/arg.hpp src/boost/mpl/aux_/preprocessed/mwcw/basic_bind.hpp src/boost/mpl/aux_/preprocessed/mwcw/bind.hpp src/boost/mpl/aux_/preprocessed/mwcw/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/mwcw/bitand.hpp src/boost/mpl/aux_/preprocessed/mwcw/bitor.hpp src/boost/mpl/aux_/preprocessed/mwcw/bitxor.hpp src/boost/mpl/aux_/preprocessed/mwcw/deque.hpp src/boost/mpl/aux_/preprocessed/mwcw/divides.hpp src/boost/mpl/aux_/preprocessed/mwcw/equal_to.hpp src/boost/mpl/aux_/preprocessed/mwcw/fold_impl.hpp src/boost/mpl/aux_/preprocessed/mwcw/full_lambda.hpp src/boost/mpl/aux_/preprocessed/mwcw/greater.hpp src/boost/mpl/aux_/preprocessed/mwcw/greater_equal.hpp src/boost/mpl/aux_/preprocessed/mwcw/inherit.hpp src/boost/mpl/aux_/preprocessed/mwcw/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/mwcw/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/mwcw/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/mwcw/less.hpp src/boost/mpl/aux_/preprocessed/mwcw/less_equal.hpp src/boost/mpl/aux_/preprocessed/mwcw/list.hpp src/boost/mpl/aux_/preprocessed/mwcw/list_c.hpp src/boost/mpl/aux_/preprocessed/mwcw/map.hpp src/boost/mpl/aux_/preprocessed/mwcw/minus.hpp src/boost/mpl/aux_/preprocessed/mwcw/modulus.hpp src/boost/mpl/aux_/preprocessed/mwcw/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/mwcw/or.hpp src/boost/mpl/aux_/preprocessed/mwcw/placeholders.hpp src/boost/mpl/aux_/preprocessed/mwcw/plus.hpp src/boost/mpl/aux_/preprocessed/mwcw/quote.hpp src/boost/mpl/aux_/preprocessed/mwcw/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/mwcw/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/mwcw/set.hpp src/boost/mpl/aux_/preprocessed/mwcw/set_c.hpp src/boost/mpl/aux_/preprocessed/mwcw/shift_left.hpp src/boost/mpl/aux_/preprocessed/mwcw/shift_right.hpp src/boost/mpl/aux_/preprocessed/mwcw/template_arity.hpp src/boost/mpl/aux_/preprocessed/mwcw/times.hpp src/boost/mpl/aux_/preprocessed/mwcw/unpack_args.hpp src/boost/mpl/aux_/preprocessed/mwcw/vector.hpp src/boost/mpl/aux_/preprocessed/mwcw/vector_c.hpp src/boost/mpl/aux_/preprocessed/no_ctps/advance_backward.hpp src/boost/mpl/aux_/preprocessed/no_ctps/advance_forward.hpp src/boost/mpl/aux_/preprocessed/no_ctps/and.hpp src/boost/mpl/aux_/preprocessed/no_ctps/apply.hpp src/boost/mpl/aux_/preprocessed/no_ctps/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/no_ctps/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/no_ctps/arg.hpp src/boost/mpl/aux_/preprocessed/no_ctps/basic_bind.hpp src/boost/mpl/aux_/preprocessed/no_ctps/bind.hpp src/boost/mpl/aux_/preprocessed/no_ctps/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/no_ctps/bitand.hpp src/boost/mpl/aux_/preprocessed/no_ctps/bitor.hpp src/boost/mpl/aux_/preprocessed/no_ctps/bitxor.hpp src/boost/mpl/aux_/preprocessed/no_ctps/deque.hpp src/boost/mpl/aux_/preprocessed/no_ctps/divides.hpp src/boost/mpl/aux_/preprocessed/no_ctps/equal_to.hpp src/boost/mpl/aux_/preprocessed/no_ctps/fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ctps/full_lambda.hpp src/boost/mpl/aux_/preprocessed/no_ctps/greater.hpp src/boost/mpl/aux_/preprocessed/no_ctps/greater_equal.hpp src/boost/mpl/aux_/preprocessed/no_ctps/inherit.hpp src/boost/mpl/aux_/preprocessed/no_ctps/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/no_ctps/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ctps/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/no_ctps/less.hpp src/boost/mpl/aux_/preprocessed/no_ctps/less_equal.hpp src/boost/mpl/aux_/preprocessed/no_ctps/list.hpp src/boost/mpl/aux_/preprocessed/no_ctps/list_c.hpp src/boost/mpl/aux_/preprocessed/no_ctps/map.hpp src/boost/mpl/aux_/preprocessed/no_ctps/minus.hpp src/boost/mpl/aux_/preprocessed/no_ctps/modulus.hpp src/boost/mpl/aux_/preprocessed/no_ctps/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/no_ctps/or.hpp src/boost/mpl/aux_/preprocessed/no_ctps/placeholders.hpp src/boost/mpl/aux_/preprocessed/no_ctps/plus.hpp src/boost/mpl/aux_/preprocessed/no_ctps/quote.hpp src/boost/mpl/aux_/preprocessed/no_ctps/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ctps/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ctps/set.hpp src/boost/mpl/aux_/preprocessed/no_ctps/set_c.hpp src/boost/mpl/aux_/preprocessed/no_ctps/shift_left.hpp src/boost/mpl/aux_/preprocessed/no_ctps/shift_right.hpp src/boost/mpl/aux_/preprocessed/no_ctps/template_arity.hpp src/boost/mpl/aux_/preprocessed/no_ctps/times.hpp src/boost/mpl/aux_/preprocessed/no_ctps/unpack_args.hpp src/boost/mpl/aux_/preprocessed/no_ctps/vector.hpp src/boost/mpl/aux_/preprocessed/no_ctps/vector_c.hpp src/boost/mpl/aux_/preprocessed/no_ttp/advance_backward.hpp src/boost/mpl/aux_/preprocessed/no_ttp/advance_forward.hpp src/boost/mpl/aux_/preprocessed/no_ttp/and.hpp src/boost/mpl/aux_/preprocessed/no_ttp/apply.hpp src/boost/mpl/aux_/preprocessed/no_ttp/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/no_ttp/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/no_ttp/arg.hpp src/boost/mpl/aux_/preprocessed/no_ttp/basic_bind.hpp src/boost/mpl/aux_/preprocessed/no_ttp/bind.hpp src/boost/mpl/aux_/preprocessed/no_ttp/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/no_ttp/bitand.hpp src/boost/mpl/aux_/preprocessed/no_ttp/bitor.hpp src/boost/mpl/aux_/preprocessed/no_ttp/bitxor.hpp src/boost/mpl/aux_/preprocessed/no_ttp/deque.hpp src/boost/mpl/aux_/preprocessed/no_ttp/divides.hpp src/boost/mpl/aux_/preprocessed/no_ttp/equal_to.hpp src/boost/mpl/aux_/preprocessed/no_ttp/fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ttp/full_lambda.hpp src/boost/mpl/aux_/preprocessed/no_ttp/greater.hpp src/boost/mpl/aux_/preprocessed/no_ttp/greater_equal.hpp src/boost/mpl/aux_/preprocessed/no_ttp/inherit.hpp src/boost/mpl/aux_/preprocessed/no_ttp/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/no_ttp/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ttp/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/no_ttp/less.hpp src/boost/mpl/aux_/preprocessed/no_ttp/less_equal.hpp src/boost/mpl/aux_/preprocessed/no_ttp/list.hpp src/boost/mpl/aux_/preprocessed/no_ttp/list_c.hpp src/boost/mpl/aux_/preprocessed/no_ttp/map.hpp src/boost/mpl/aux_/preprocessed/no_ttp/minus.hpp src/boost/mpl/aux_/preprocessed/no_ttp/modulus.hpp src/boost/mpl/aux_/preprocessed/no_ttp/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/no_ttp/or.hpp src/boost/mpl/aux_/preprocessed/no_ttp/placeholders.hpp src/boost/mpl/aux_/preprocessed/no_ttp/plus.hpp src/boost/mpl/aux_/preprocessed/no_ttp/quote.hpp src/boost/mpl/aux_/preprocessed/no_ttp/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ttp/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/no_ttp/set.hpp src/boost/mpl/aux_/preprocessed/no_ttp/set_c.hpp src/boost/mpl/aux_/preprocessed/no_ttp/shift_left.hpp src/boost/mpl/aux_/preprocessed/no_ttp/shift_right.hpp src/boost/mpl/aux_/preprocessed/no_ttp/template_arity.hpp src/boost/mpl/aux_/preprocessed/no_ttp/times.hpp src/boost/mpl/aux_/preprocessed/no_ttp/unpack_args.hpp src/boost/mpl/aux_/preprocessed/no_ttp/vector.hpp src/boost/mpl/aux_/preprocessed/no_ttp/vector_c.hpp src/boost/mpl/aux_/preprocessed/plain/advance_backward.hpp src/boost/mpl/aux_/preprocessed/plain/advance_forward.hpp src/boost/mpl/aux_/preprocessed/plain/and.hpp src/boost/mpl/aux_/preprocessed/plain/apply.hpp src/boost/mpl/aux_/preprocessed/plain/apply_fwd.hpp src/boost/mpl/aux_/preprocessed/plain/apply_wrap.hpp src/boost/mpl/aux_/preprocessed/plain/arg.hpp src/boost/mpl/aux_/preprocessed/plain/basic_bind.hpp src/boost/mpl/aux_/preprocessed/plain/bind.hpp src/boost/mpl/aux_/preprocessed/plain/bind_fwd.hpp src/boost/mpl/aux_/preprocessed/plain/bitand.hpp src/boost/mpl/aux_/preprocessed/plain/bitor.hpp src/boost/mpl/aux_/preprocessed/plain/bitxor.hpp src/boost/mpl/aux_/preprocessed/plain/deque.hpp src/boost/mpl/aux_/preprocessed/plain/divides.hpp src/boost/mpl/aux_/preprocessed/plain/equal_to.hpp src/boost/mpl/aux_/preprocessed/plain/fold_impl.hpp src/boost/mpl/aux_/preprocessed/plain/full_lambda.hpp src/boost/mpl/aux_/preprocessed/plain/greater.hpp src/boost/mpl/aux_/preprocessed/plain/greater_equal.hpp src/boost/mpl/aux_/preprocessed/plain/inherit.hpp src/boost/mpl/aux_/preprocessed/plain/iter_fold_if_impl.hpp src/boost/mpl/aux_/preprocessed/plain/iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/plain/lambda_no_ctps.hpp src/boost/mpl/aux_/preprocessed/plain/less.hpp src/boost/mpl/aux_/preprocessed/plain/less_equal.hpp src/boost/mpl/aux_/preprocessed/plain/list.hpp src/boost/mpl/aux_/preprocessed/plain/list_c.hpp src/boost/mpl/aux_/preprocessed/plain/map.hpp src/boost/mpl/aux_/preprocessed/plain/minus.hpp src/boost/mpl/aux_/preprocessed/plain/modulus.hpp src/boost/mpl/aux_/preprocessed/plain/not_equal_to.hpp src/boost/mpl/aux_/preprocessed/plain/or.hpp src/boost/mpl/aux_/preprocessed/plain/placeholders.hpp src/boost/mpl/aux_/preprocessed/plain/plus.hpp src/boost/mpl/aux_/preprocessed/plain/quote.hpp src/boost/mpl/aux_/preprocessed/plain/reverse_fold_impl.hpp src/boost/mpl/aux_/preprocessed/plain/reverse_iter_fold_impl.hpp src/boost/mpl/aux_/preprocessed/plain/set.hpp src/boost/mpl/aux_/preprocessed/plain/set_c.hpp src/boost/mpl/aux_/preprocessed/plain/shift_left.hpp src/boost/mpl/aux_/preprocessed/plain/shift_right.hpp src/boost/mpl/aux_/preprocessed/plain/template_arity.hpp src/boost/mpl/aux_/preprocessed/plain/times.hpp src/boost/mpl/aux_/preprocessed/plain/unpack_args.hpp src/boost/mpl/aux_/preprocessed/plain/vector.hpp src/boost/mpl/aux_/preprocessed/plain/vector_c.hpp src/boost/mpl/aux_/preprocessor/add.hpp src/boost/mpl/aux_/preprocessor/def_params_tail.hpp src/boost/mpl/aux_/preprocessor/default_params.hpp src/boost/mpl/aux_/preprocessor/enum.hpp src/boost/mpl/aux_/preprocessor/ext_params.hpp src/boost/mpl/aux_/preprocessor/filter_params.hpp src/boost/mpl/aux_/preprocessor/params.hpp src/boost/mpl/aux_/preprocessor/partial_spec_params.hpp src/boost/mpl/aux_/preprocessor/range.hpp src/boost/mpl/aux_/preprocessor/repeat.hpp src/boost/mpl/aux_/preprocessor/sub.hpp src/boost/mpl/aux_/preprocessor/tuple.hpp src/boost/mpl/aux_/push_back_impl.hpp src/boost/mpl/aux_/push_front_impl.hpp src/boost/mpl/aux_/reverse_fold_impl.hpp src/boost/mpl/aux_/reverse_fold_impl_body.hpp src/boost/mpl/aux_/sequence_wrapper.hpp src/boost/mpl/aux_/size_impl.hpp src/boost/mpl/aux_/static_cast.hpp src/boost/mpl/aux_/template_arity.hpp src/boost/mpl/aux_/template_arity_fwd.hpp src/boost/mpl/aux_/traits_lambda_spec.hpp src/boost/mpl/aux_/type_wrapper.hpp src/boost/mpl/aux_/unwrap.hpp src/boost/mpl/aux_/value_wknd.hpp src/boost/mpl/aux_/yes_no.hpp src/boost/mpl/back_fwd.hpp src/boost/mpl/back_inserter.hpp src/boost/mpl/begin_end.hpp src/boost/mpl/begin_end_fwd.hpp src/boost/mpl/bind.hpp src/boost/mpl/bind_fwd.hpp src/boost/mpl/bool.hpp src/boost/mpl/bool_fwd.hpp src/boost/mpl/clear.hpp src/boost/mpl/clear_fwd.hpp src/boost/mpl/comparison.hpp src/boost/mpl/contains.hpp src/boost/mpl/contains_fwd.hpp src/boost/mpl/copy.hpp src/boost/mpl/deref.hpp src/boost/mpl/distance.hpp src/boost/mpl/distance_fwd.hpp src/boost/mpl/empty_fwd.hpp src/boost/mpl/equal_to.hpp src/boost/mpl/eval_if.hpp src/boost/mpl/find.hpp src/boost/mpl/find_if.hpp src/boost/mpl/fold.hpp src/boost/mpl/for_each.hpp src/boost/mpl/front_fwd.hpp src/boost/mpl/front_inserter.hpp src/boost/mpl/greater.hpp src/boost/mpl/greater_equal.hpp src/boost/mpl/has_xxx.hpp src/boost/mpl/identity.hpp src/boost/mpl/if.hpp src/boost/mpl/inserter.hpp src/boost/mpl/int.hpp src/boost/mpl/int_fwd.hpp src/boost/mpl/integral_c.hpp src/boost/mpl/integral_c_fwd.hpp src/boost/mpl/integral_c_tag.hpp src/boost/mpl/is_placeholder.hpp src/boost/mpl/is_sequence.hpp src/boost/mpl/iter_fold.hpp src/boost/mpl/iter_fold_if.hpp src/boost/mpl/iterator_range.hpp src/boost/mpl/iterator_tags.hpp src/boost/mpl/lambda.hpp src/boost/mpl/lambda_fwd.hpp src/boost/mpl/less.hpp src/boost/mpl/less_equal.hpp src/boost/mpl/limits/arity.hpp src/boost/mpl/limits/list.hpp src/boost/mpl/limits/unrolling.hpp src/boost/mpl/limits/vector.hpp src/boost/mpl/list.hpp src/boost/mpl/list/aux_/begin_end.hpp src/boost/mpl/list/aux_/clear.hpp src/boost/mpl/list/aux_/empty.hpp src/boost/mpl/list/aux_/front.hpp src/boost/mpl/list/aux_/include_preprocessed.hpp src/boost/mpl/list/aux_/item.hpp src/boost/mpl/list/aux_/iterator.hpp src/boost/mpl/list/aux_/numbered.hpp src/boost/mpl/list/aux_/numbered_c.hpp src/boost/mpl/list/aux_/O1_size.hpp src/boost/mpl/list/aux_/pop_front.hpp src/boost/mpl/list/aux_/preprocessed/plain/list10.hpp src/boost/mpl/list/aux_/preprocessed/plain/list10_c.hpp src/boost/mpl/list/aux_/preprocessed/plain/list20.hpp src/boost/mpl/list/aux_/preprocessed/plain/list20_c.hpp src/boost/mpl/list/aux_/preprocessed/plain/list30.hpp src/boost/mpl/list/aux_/preprocessed/plain/list30_c.hpp src/boost/mpl/list/aux_/preprocessed/plain/list40.hpp src/boost/mpl/list/aux_/preprocessed/plain/list40_c.hpp src/boost/mpl/list/aux_/preprocessed/plain/list50.hpp src/boost/mpl/list/aux_/preprocessed/plain/list50_c.hpp src/boost/mpl/list/aux_/push_back.hpp src/boost/mpl/list/aux_/push_front.hpp src/boost/mpl/list/aux_/size.hpp src/boost/mpl/list/aux_/tag.hpp src/boost/mpl/list/list0.hpp src/boost/mpl/list/list0_c.hpp src/boost/mpl/list/list10.hpp src/boost/mpl/list/list10_c.hpp src/boost/mpl/list/list20.hpp src/boost/mpl/list/list20_c.hpp src/boost/mpl/list/list30.hpp src/boost/mpl/list/list30_c.hpp src/boost/mpl/list/list40.hpp src/boost/mpl/list/list40_c.hpp src/boost/mpl/list/list50.hpp src/boost/mpl/list/list50_c.hpp src/boost/mpl/logical.hpp src/boost/mpl/long.hpp src/boost/mpl/long_fwd.hpp src/boost/mpl/minus.hpp src/boost/mpl/multiplies.hpp src/boost/mpl/negate.hpp src/boost/mpl/next.hpp src/boost/mpl/next_prior.hpp src/boost/mpl/not.hpp src/boost/mpl/not_equal_to.hpp src/boost/mpl/numeric_cast.hpp src/boost/mpl/O1_size.hpp src/boost/mpl/O1_size_fwd.hpp src/boost/mpl/or.hpp src/boost/mpl/pair.hpp src/boost/mpl/placeholders.hpp src/boost/mpl/plus.hpp src/boost/mpl/pop_back_fwd.hpp src/boost/mpl/pop_front_fwd.hpp src/boost/mpl/prior.hpp src/boost/mpl/protect.hpp src/boost/mpl/push_back.hpp src/boost/mpl/push_back_fwd.hpp src/boost/mpl/push_front.hpp src/boost/mpl/push_front_fwd.hpp src/boost/mpl/quote.hpp src/boost/mpl/remove_if.hpp src/boost/mpl/reverse_fold.hpp src/boost/mpl/same_as.hpp src/boost/mpl/sequence_tag.hpp src/boost/mpl/sequence_tag_fwd.hpp src/boost/mpl/size.hpp src/boost/mpl/size_fwd.hpp src/boost/mpl/size_t.hpp src/boost/mpl/size_t_fwd.hpp src/boost/mpl/tag.hpp src/boost/mpl/times.hpp src/boost/mpl/vector.hpp src/boost/mpl/vector/aux_/at.hpp src/boost/mpl/vector/aux_/back.hpp src/boost/mpl/vector/aux_/begin_end.hpp src/boost/mpl/vector/aux_/clear.hpp src/boost/mpl/vector/aux_/empty.hpp src/boost/mpl/vector/aux_/front.hpp src/boost/mpl/vector/aux_/include_preprocessed.hpp src/boost/mpl/vector/aux_/item.hpp src/boost/mpl/vector/aux_/iterator.hpp src/boost/mpl/vector/aux_/numbered.hpp src/boost/mpl/vector/aux_/numbered_c.hpp src/boost/mpl/vector/aux_/O1_size.hpp src/boost/mpl/vector/aux_/pop_back.hpp src/boost/mpl/vector/aux_/pop_front.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector10.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector10_c.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector20.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector20_c.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector30.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector30_c.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector40.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector40_c.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector50.hpp src/boost/mpl/vector/aux_/preprocessed/no_ctps/vector50_c.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector10.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector10_c.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector20.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector20_c.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector30.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector30_c.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector40.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector40_c.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector50.hpp src/boost/mpl/vector/aux_/preprocessed/plain/vector50_c.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector10.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector10_c.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector20.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector20_c.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector30.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector30_c.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector40.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector40_c.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector50.hpp src/boost/mpl/vector/aux_/preprocessed/typeof_based/vector50_c.hpp src/boost/mpl/vector/aux_/push_back.hpp src/boost/mpl/vector/aux_/push_front.hpp src/boost/mpl/vector/aux_/size.hpp src/boost/mpl/vector/aux_/tag.hpp src/boost/mpl/vector/aux_/vector0.hpp src/boost/mpl/vector/vector0.hpp src/boost/mpl/vector/vector0_c.hpp src/boost/mpl/vector/vector10.hpp src/boost/mpl/vector/vector10_c.hpp src/boost/mpl/vector/vector20.hpp src/boost/mpl/vector/vector20_c.hpp src/boost/mpl/vector/vector30.hpp src/boost/mpl/vector/vector30_c.hpp src/boost/mpl/vector/vector40.hpp src/boost/mpl/vector/vector40_c.hpp src/boost/mpl/vector/vector50.hpp src/boost/mpl/vector/vector50_c.hpp src/boost/mpl/void.hpp src/boost/mpl/void_fwd.hpp src/boost/next_prior.hpp src/boost/non_type.hpp src/boost/noncopyable.hpp src/boost/none.hpp src/boost/none_t.hpp src/boost/numeric/conversion/bounds.hpp src/boost/numeric/conversion/cast.hpp src/boost/numeric/conversion/conversion_traits.hpp src/boost/numeric/conversion/converter.hpp src/boost/numeric/conversion/converter_policies.hpp src/boost/numeric/conversion/detail/bounds.hpp src/boost/numeric/conversion/detail/conversion_traits.hpp src/boost/numeric/conversion/detail/converter.hpp src/boost/numeric/conversion/detail/int_float_mixture.hpp src/boost/numeric/conversion/detail/is_subranged.hpp src/boost/numeric/conversion/detail/meta.hpp src/boost/numeric/conversion/detail/numeric_cast_traits.hpp src/boost/numeric/conversion/detail/old_numeric_cast.hpp src/boost/numeric/conversion/detail/preprocessed/numeric_cast_traits_common.hpp src/boost/numeric/conversion/detail/preprocessed/numeric_cast_traits_long_long.hpp src/boost/numeric/conversion/detail/sign_mixture.hpp src/boost/numeric/conversion/detail/udt_builtin_mixture.hpp src/boost/numeric/conversion/int_float_mixture_enum.hpp src/boost/numeric/conversion/numeric_cast_traits.hpp src/boost/numeric/conversion/sign_mixture_enum.hpp src/boost/numeric/conversion/udt_builtin_mixture_enum.hpp src/boost/operators.hpp src/boost/optional.hpp src/boost/optional/optional.hpp src/boost/optional/optional_fwd.hpp src/boost/pending/integer_log2.hpp src/boost/polygon/detail/boolean_op.hpp src/boost/polygon/detail/boolean_op_45.hpp src/boost/polygon/detail/iterator_compact_to_points.hpp src/boost/polygon/detail/iterator_geometry_to_set.hpp src/boost/polygon/detail/iterator_points_to_compact.hpp src/boost/polygon/detail/max_cover.hpp src/boost/polygon/detail/minkowski.hpp src/boost/polygon/detail/polygon_45_formation.hpp src/boost/polygon/detail/polygon_45_set_view.hpp src/boost/polygon/detail/polygon_45_touch.hpp src/boost/polygon/detail/polygon_90_set_view.hpp src/boost/polygon/detail/polygon_90_touch.hpp src/boost/polygon/detail/polygon_arbitrary_formation.hpp src/boost/polygon/detail/polygon_formation.hpp src/boost/polygon/detail/polygon_set_view.hpp src/boost/polygon/detail/polygon_simplify.hpp src/boost/polygon/detail/polygon_sort_adaptor.hpp src/boost/polygon/detail/property_merge.hpp src/boost/polygon/detail/property_merge_45.hpp src/boost/polygon/detail/rectangle_formation.hpp src/boost/polygon/detail/scan_arbitrary.hpp src/boost/polygon/detail/voronoi_ctypes.hpp src/boost/polygon/detail/voronoi_predicates.hpp src/boost/polygon/detail/voronoi_robust_fpt.hpp src/boost/polygon/detail/voronoi_structures.hpp src/boost/polygon/gmp_override.hpp src/boost/polygon/gtl.hpp src/boost/polygon/interval_concept.hpp src/boost/polygon/interval_data.hpp src/boost/polygon/interval_traits.hpp src/boost/polygon/isotropy.hpp src/boost/polygon/point_concept.hpp src/boost/polygon/point_data.hpp src/boost/polygon/point_traits.hpp src/boost/polygon/polygon.hpp src/boost/polygon/polygon_45_data.hpp src/boost/polygon/polygon_45_set_concept.hpp src/boost/polygon/polygon_45_set_data.hpp src/boost/polygon/polygon_45_set_traits.hpp src/boost/polygon/polygon_45_with_holes_data.hpp src/boost/polygon/polygon_90_data.hpp src/boost/polygon/polygon_90_set_concept.hpp src/boost/polygon/polygon_90_set_data.hpp src/boost/polygon/polygon_90_set_traits.hpp src/boost/polygon/polygon_90_with_holes_data.hpp src/boost/polygon/polygon_data.hpp src/boost/polygon/polygon_set_concept.hpp src/boost/polygon/polygon_set_data.hpp src/boost/polygon/polygon_set_traits.hpp src/boost/polygon/polygon_traits.hpp src/boost/polygon/polygon_with_holes_data.hpp src/boost/polygon/rectangle_concept.hpp src/boost/polygon/rectangle_data.hpp src/boost/polygon/rectangle_traits.hpp src/boost/polygon/segment_concept.hpp src/boost/polygon/segment_data.hpp src/boost/polygon/segment_traits.hpp src/boost/polygon/segment_utils.hpp src/boost/polygon/transform.hpp src/boost/polygon/voronoi.hpp src/boost/polygon/voronoi_builder.hpp src/boost/polygon/voronoi_diagram.hpp src/boost/polygon/voronoi_geometry_type.hpp src/boost/predef/architecture.h src/boost/predef/architecture/alpha.h src/boost/predef/architecture/arm.h src/boost/predef/architecture/blackfin.h src/boost/predef/architecture/convex.h src/boost/predef/architecture/ia64.h src/boost/predef/architecture/m68k.h src/boost/predef/architecture/mips.h src/boost/predef/architecture/parisc.h src/boost/predef/architecture/ppc.h src/boost/predef/architecture/pyramid.h src/boost/predef/architecture/rs6k.h src/boost/predef/architecture/sparc.h src/boost/predef/architecture/superh.h src/boost/predef/architecture/sys370.h src/boost/predef/architecture/sys390.h src/boost/predef/architecture/x86.h src/boost/predef/architecture/x86/32.h src/boost/predef/architecture/x86/64.h src/boost/predef/architecture/z.h src/boost/predef/detail/_cassert.h src/boost/predef/detail/endian_compat.h src/boost/predef/detail/os_detected.h src/boost/predef/detail/test.h src/boost/predef/library/c/_prefix.h src/boost/predef/library/c/gnu.h src/boost/predef/make.h src/boost/predef/os/bsd.h src/boost/predef/os/bsd/bsdi.h src/boost/predef/os/bsd/dragonfly.h src/boost/predef/os/bsd/free.h src/boost/predef/os/bsd/net.h src/boost/predef/os/bsd/open.h src/boost/predef/os/macos.h src/boost/predef/os/windows.h src/boost/predef/other/endian.h src/boost/predef/version_number.h src/boost/preprocessor/arithmetic/add.hpp src/boost/preprocessor/arithmetic/dec.hpp src/boost/preprocessor/arithmetic/detail/div_base.hpp src/boost/preprocessor/arithmetic/inc.hpp src/boost/preprocessor/arithmetic/mod.hpp src/boost/preprocessor/arithmetic/sub.hpp src/boost/preprocessor/array/data.hpp src/boost/preprocessor/array/elem.hpp src/boost/preprocessor/array/size.hpp src/boost/preprocessor/cat.hpp src/boost/preprocessor/comma_if.hpp src/boost/preprocessor/comparison/less_equal.hpp src/boost/preprocessor/config/config.hpp src/boost/preprocessor/control/deduce_d.hpp src/boost/preprocessor/control/detail/dmc/while.hpp src/boost/preprocessor/control/detail/edg/while.hpp src/boost/preprocessor/control/detail/msvc/while.hpp src/boost/preprocessor/control/detail/while.hpp src/boost/preprocessor/control/expr_if.hpp src/boost/preprocessor/control/expr_iif.hpp src/boost/preprocessor/control/if.hpp src/boost/preprocessor/control/iif.hpp src/boost/preprocessor/control/while.hpp src/boost/preprocessor/debug/error.hpp src/boost/preprocessor/dec.hpp src/boost/preprocessor/detail/auto_rec.hpp src/boost/preprocessor/detail/check.hpp src/boost/preprocessor/detail/dmc/auto_rec.hpp src/boost/preprocessor/detail/is_binary.hpp src/boost/preprocessor/detail/is_unary.hpp src/boost/preprocessor/empty.hpp src/boost/preprocessor/enum.hpp src/boost/preprocessor/enum_params.hpp src/boost/preprocessor/enum_params_with_a_default.hpp src/boost/preprocessor/enum_shifted_params.hpp src/boost/preprocessor/expr_if.hpp src/boost/preprocessor/facilities/empty.hpp src/boost/preprocessor/facilities/identity.hpp src/boost/preprocessor/facilities/intercept.hpp src/boost/preprocessor/facilities/overload.hpp src/boost/preprocessor/identity.hpp src/boost/preprocessor/if.hpp src/boost/preprocessor/inc.hpp src/boost/preprocessor/iterate.hpp src/boost/preprocessor/iteration/detail/bounds/lower1.hpp src/boost/preprocessor/iteration/detail/bounds/lower2.hpp src/boost/preprocessor/iteration/detail/bounds/lower3.hpp src/boost/preprocessor/iteration/detail/bounds/lower4.hpp src/boost/preprocessor/iteration/detail/bounds/lower5.hpp src/boost/preprocessor/iteration/detail/bounds/upper1.hpp src/boost/preprocessor/iteration/detail/bounds/upper2.hpp src/boost/preprocessor/iteration/detail/bounds/upper3.hpp src/boost/preprocessor/iteration/detail/bounds/upper4.hpp src/boost/preprocessor/iteration/detail/bounds/upper5.hpp src/boost/preprocessor/iteration/detail/finish.hpp src/boost/preprocessor/iteration/detail/iter/forward1.hpp src/boost/preprocessor/iteration/detail/iter/forward2.hpp src/boost/preprocessor/iteration/detail/iter/forward3.hpp src/boost/preprocessor/iteration/detail/iter/forward4.hpp src/boost/preprocessor/iteration/detail/iter/forward5.hpp src/boost/preprocessor/iteration/detail/iter/reverse1.hpp src/boost/preprocessor/iteration/detail/iter/reverse2.hpp src/boost/preprocessor/iteration/detail/iter/reverse3.hpp src/boost/preprocessor/iteration/detail/iter/reverse4.hpp src/boost/preprocessor/iteration/detail/iter/reverse5.hpp src/boost/preprocessor/iteration/detail/local.hpp src/boost/preprocessor/iteration/detail/rlocal.hpp src/boost/preprocessor/iteration/detail/self.hpp src/boost/preprocessor/iteration/detail/start.hpp src/boost/preprocessor/iteration/iterate.hpp src/boost/preprocessor/iteration/local.hpp src/boost/preprocessor/iteration/self.hpp src/boost/preprocessor/list/adt.hpp src/boost/preprocessor/list/append.hpp src/boost/preprocessor/list/detail/dmc/fold_left.hpp src/boost/preprocessor/list/detail/edg/fold_left.hpp src/boost/preprocessor/list/detail/edg/fold_right.hpp src/boost/preprocessor/list/detail/fold_left.hpp src/boost/preprocessor/list/detail/fold_right.hpp src/boost/preprocessor/list/fold_left.hpp src/boost/preprocessor/list/fold_right.hpp src/boost/preprocessor/list/for_each_i.hpp src/boost/preprocessor/list/reverse.hpp src/boost/preprocessor/list/transform.hpp src/boost/preprocessor/logical/and.hpp src/boost/preprocessor/logical/bitand.hpp src/boost/preprocessor/logical/bitor.hpp src/boost/preprocessor/logical/bool.hpp src/boost/preprocessor/logical/compl.hpp src/boost/preprocessor/logical/not.hpp src/boost/preprocessor/logical/or.hpp src/boost/preprocessor/punctuation/comma.hpp src/boost/preprocessor/punctuation/comma_if.hpp src/boost/preprocessor/punctuation/paren.hpp src/boost/preprocessor/repeat.hpp src/boost/preprocessor/repeat_from_to.hpp src/boost/preprocessor/repetition/detail/dmc/for.hpp src/boost/preprocessor/repetition/detail/edg/for.hpp src/boost/preprocessor/repetition/detail/for.hpp src/boost/preprocessor/repetition/detail/msvc/for.hpp src/boost/preprocessor/repetition/enum.hpp src/boost/preprocessor/repetition/enum_binary_params.hpp src/boost/preprocessor/repetition/enum_params.hpp src/boost/preprocessor/repetition/enum_params_with_a_default.hpp src/boost/preprocessor/repetition/enum_shifted_params.hpp src/boost/preprocessor/repetition/enum_trailing.hpp src/boost/preprocessor/repetition/enum_trailing_params.hpp src/boost/preprocessor/repetition/for.hpp src/boost/preprocessor/repetition/repeat.hpp src/boost/preprocessor/repetition/repeat_from_to.hpp src/boost/preprocessor/seq/cat.hpp src/boost/preprocessor/seq/detail/split.hpp src/boost/preprocessor/seq/elem.hpp src/boost/preprocessor/seq/enum.hpp src/boost/preprocessor/seq/first_n.hpp src/boost/preprocessor/seq/fold_left.hpp src/boost/preprocessor/seq/for_each.hpp src/boost/preprocessor/seq/for_each_i.hpp src/boost/preprocessor/seq/rest_n.hpp src/boost/preprocessor/seq/seq.hpp src/boost/preprocessor/seq/size.hpp src/boost/preprocessor/seq/subseq.hpp src/boost/preprocessor/seq/transform.hpp src/boost/preprocessor/slot/detail/counter.hpp src/boost/preprocessor/slot/detail/def.hpp src/boost/preprocessor/slot/detail/shared.hpp src/boost/preprocessor/slot/detail/slot1.hpp src/boost/preprocessor/slot/detail/slot2.hpp src/boost/preprocessor/slot/detail/slot3.hpp src/boost/preprocessor/slot/detail/slot4.hpp src/boost/preprocessor/slot/detail/slot5.hpp src/boost/preprocessor/slot/slot.hpp src/boost/preprocessor/stringize.hpp src/boost/preprocessor/tuple/eat.hpp src/boost/preprocessor/tuple/elem.hpp src/boost/preprocessor/tuple/rem.hpp src/boost/preprocessor/tuple/to_list.hpp src/boost/preprocessor/variadic/elem.hpp src/boost/preprocessor/variadic/size.hpp src/boost/progress.hpp src/boost/random/detail/config.hpp src/boost/random/detail/const_mod.hpp src/boost/random/detail/disable_warnings.hpp src/boost/random/detail/enable_warnings.hpp src/boost/random/detail/generator_bits.hpp src/boost/random/detail/generator_seed_seq.hpp src/boost/random/detail/integer_log2.hpp src/boost/random/detail/large_arithmetic.hpp src/boost/random/detail/operators.hpp src/boost/random/detail/ptr_helper.hpp src/boost/random/detail/seed.hpp src/boost/random/detail/seed_impl.hpp src/boost/random/detail/signed_unsigned_tools.hpp src/boost/random/detail/uniform_int_float.hpp src/boost/random/mersenne_twister.hpp src/boost/random/uniform_int_distribution.hpp src/boost/range/algorithm/equal.hpp src/boost/range/begin.hpp src/boost/range/concepts.hpp src/boost/range/config.hpp src/boost/range/const_iterator.hpp src/boost/range/detail/begin.hpp src/boost/range/detail/common.hpp src/boost/range/detail/const_iterator.hpp src/boost/range/detail/end.hpp src/boost/range/detail/extract_optional_type.hpp src/boost/range/detail/implementation_help.hpp src/boost/range/detail/iterator.hpp src/boost/range/detail/misc_concept.hpp src/boost/range/detail/remove_extent.hpp src/boost/range/detail/safe_bool.hpp src/boost/range/detail/sfinae.hpp src/boost/range/detail/size_type.hpp src/boost/range/detail/vc6/end.hpp src/boost/range/difference_type.hpp src/boost/range/distance.hpp src/boost/range/empty.hpp src/boost/range/end.hpp src/boost/range/functions.hpp src/boost/range/iterator.hpp src/boost/range/iterator_range_core.hpp src/boost/range/mutable_iterator.hpp src/boost/range/rbegin.hpp src/boost/range/rend.hpp src/boost/range/result_iterator.hpp src/boost/range/reverse_iterator.hpp src/boost/range/size.hpp src/boost/range/size_type.hpp src/boost/range/value_type.hpp src/boost/ratio/config.hpp src/boost/ratio/detail/mpl/abs.hpp src/boost/ratio/detail/mpl/gcd.hpp src/boost/ratio/detail/mpl/lcm.hpp src/boost/ratio/detail/mpl/sign.hpp src/boost/ratio/detail/overflow_helpers.hpp src/boost/ratio/mpl/rational_c_tag.hpp src/boost/ratio/ratio.hpp src/boost/ratio/ratio_fwd.hpp src/boost/rational.hpp src/boost/ref.hpp src/boost/scoped_array.hpp src/boost/scoped_ptr.hpp src/boost/shared_array.hpp src/boost/shared_ptr.hpp src/boost/smart_ptr/bad_weak_ptr.hpp src/boost/smart_ptr/detail/atomic_count.hpp src/boost/smart_ptr/detail/atomic_count_gcc.hpp src/boost/smart_ptr/detail/atomic_count_gcc_x86.hpp src/boost/smart_ptr/detail/atomic_count_pthreads.hpp src/boost/smart_ptr/detail/atomic_count_sync.hpp src/boost/smart_ptr/detail/atomic_count_win32.hpp src/boost/smart_ptr/detail/lightweight_mutex.hpp src/boost/smart_ptr/detail/lwm_nop.hpp src/boost/smart_ptr/detail/lwm_pthreads.hpp src/boost/smart_ptr/detail/lwm_win32_cs.hpp src/boost/smart_ptr/detail/operator_bool.hpp src/boost/smart_ptr/detail/quick_allocator.hpp src/boost/smart_ptr/detail/shared_array_nmt.hpp src/boost/smart_ptr/detail/shared_count.hpp src/boost/smart_ptr/detail/shared_ptr_nmt.hpp src/boost/smart_ptr/detail/sp_convertible.hpp src/boost/smart_ptr/detail/sp_counted_base.hpp src/boost/smart_ptr/detail/sp_counted_base_acc_ia64.hpp src/boost/smart_ptr/detail/sp_counted_base_aix.hpp src/boost/smart_ptr/detail/sp_counted_base_cw_ppc.hpp src/boost/smart_ptr/detail/sp_counted_base_gcc_ia64.hpp src/boost/smart_ptr/detail/sp_counted_base_gcc_mips.hpp src/boost/smart_ptr/detail/sp_counted_base_gcc_ppc.hpp src/boost/smart_ptr/detail/sp_counted_base_gcc_sparc.hpp src/boost/smart_ptr/detail/sp_counted_base_gcc_x86.hpp src/boost/smart_ptr/detail/sp_counted_base_nt.hpp src/boost/smart_ptr/detail/sp_counted_base_pt.hpp src/boost/smart_ptr/detail/sp_counted_base_snc_ps3.hpp src/boost/smart_ptr/detail/sp_counted_base_spin.hpp src/boost/smart_ptr/detail/sp_counted_base_sync.hpp src/boost/smart_ptr/detail/sp_counted_base_vacpp_ppc.hpp src/boost/smart_ptr/detail/sp_counted_base_w32.hpp src/boost/smart_ptr/detail/sp_counted_impl.hpp src/boost/smart_ptr/detail/sp_has_sync.hpp src/boost/smart_ptr/detail/sp_nullptr_t.hpp src/boost/smart_ptr/detail/spinlock.hpp src/boost/smart_ptr/detail/spinlock_gcc_arm.hpp src/boost/smart_ptr/detail/spinlock_nt.hpp src/boost/smart_ptr/detail/spinlock_pool.hpp src/boost/smart_ptr/detail/spinlock_pt.hpp src/boost/smart_ptr/detail/spinlock_sync.hpp src/boost/smart_ptr/detail/spinlock_w32.hpp src/boost/smart_ptr/detail/yield_k.hpp src/boost/smart_ptr/scoped_array.hpp src/boost/smart_ptr/scoped_ptr.hpp src/boost/smart_ptr/shared_array.hpp src/boost/smart_ptr/shared_ptr.hpp src/boost/static_assert.hpp src/boost/swap.hpp src/boost/system/api_config.hpp src/boost/system/config.hpp src/boost/system/error_code.hpp src/boost/system/system_error.hpp src/boost/test/debug.hpp src/boost/test/debug_config.hpp src/boost/test/detail/config.hpp src/boost/test/detail/enable_warnings.hpp src/boost/test/detail/fwd_decl.hpp src/boost/test/detail/global_typedef.hpp src/boost/test/detail/log_level.hpp src/boost/test/detail/suppress_warnings.hpp src/boost/test/detail/unit_test_parameters.hpp src/boost/test/detail/workaround.hpp src/boost/test/execution_monitor.hpp src/boost/test/floating_point_comparison.hpp src/boost/test/framework.hpp src/boost/test/impl/compiler_log_formatter.ipp src/boost/test/impl/cpp_main.ipp src/boost/test/impl/debug.ipp src/boost/test/impl/exception_safety.ipp src/boost/test/impl/execution_monitor.ipp src/boost/test/impl/framework.ipp src/boost/test/impl/interaction_based.ipp src/boost/test/impl/logged_expectations.ipp src/boost/test/impl/plain_report_formatter.ipp src/boost/test/impl/progress_monitor.ipp src/boost/test/impl/results_collector.ipp src/boost/test/impl/results_reporter.ipp src/boost/test/impl/test_main.ipp src/boost/test/impl/test_tools.ipp src/boost/test/impl/unit_test_log.ipp src/boost/test/impl/unit_test_main.ipp src/boost/test/impl/unit_test_monitor.ipp src/boost/test/impl/unit_test_parameters.ipp src/boost/test/impl/unit_test_suite.ipp src/boost/test/impl/xml_log_formatter.ipp src/boost/test/impl/xml_report_formatter.ipp src/boost/test/interaction_based.hpp src/boost/test/mock_object.hpp src/boost/test/output/compiler_log_formatter.hpp src/boost/test/output/plain_report_formatter.hpp src/boost/test/output/xml_log_formatter.hpp src/boost/test/output/xml_report_formatter.hpp src/boost/test/output_test_stream.hpp src/boost/test/predicate_result.hpp src/boost/test/progress_monitor.hpp src/boost/test/results_collector.hpp src/boost/test/results_reporter.hpp src/boost/test/test_case_template.hpp src/boost/test/test_observer.hpp src/boost/test/test_tools.hpp src/boost/test/unit_test.hpp src/boost/test/unit_test_log.hpp src/boost/test/unit_test_log_formatter.hpp src/boost/test/unit_test_monitor.hpp src/boost/test/unit_test_suite.hpp src/boost/test/unit_test_suite_impl.hpp src/boost/test/utils/algorithm.hpp src/boost/test/utils/assign_op.hpp src/boost/test/utils/basic_cstring/basic_cstring.hpp src/boost/test/utils/basic_cstring/basic_cstring_fwd.hpp src/boost/test/utils/basic_cstring/bcs_char_traits.hpp src/boost/test/utils/basic_cstring/compare.hpp src/boost/test/utils/basic_cstring/io.hpp src/boost/test/utils/callback.hpp src/boost/test/utils/class_properties.hpp src/boost/test/utils/custom_manip.hpp src/boost/test/utils/fixed_mapping.hpp src/boost/test/utils/foreach.hpp src/boost/test/utils/iterator/input_iterator_facade.hpp src/boost/test/utils/iterator/token_iterator.hpp src/boost/test/utils/lazy_ostream.hpp src/boost/test/utils/named_params.hpp src/boost/test/utils/rtti.hpp src/boost/test/utils/runtime/argument.hpp src/boost/test/utils/runtime/cla/argument_factory.hpp src/boost/test/utils/runtime/cla/argv_traverser.hpp src/boost/test/utils/runtime/cla/argv_traverser.ipp src/boost/test/utils/runtime/cla/basic_parameter.hpp src/boost/test/utils/runtime/cla/char_parameter.hpp src/boost/test/utils/runtime/cla/char_parameter.ipp src/boost/test/utils/runtime/cla/detail/argument_value_usage.hpp src/boost/test/utils/runtime/cla/dual_name_parameter.hpp src/boost/test/utils/runtime/cla/dual_name_parameter.ipp src/boost/test/utils/runtime/cla/fwd.hpp src/boost/test/utils/runtime/cla/id_policy.hpp src/boost/test/utils/runtime/cla/id_policy.ipp src/boost/test/utils/runtime/cla/iface/argument_factory.hpp src/boost/test/utils/runtime/cla/iface/id_policy.hpp src/boost/test/utils/runtime/cla/modifier.hpp src/boost/test/utils/runtime/cla/named_parameter.hpp src/boost/test/utils/runtime/cla/named_parameter.ipp src/boost/test/utils/runtime/cla/parameter.hpp src/boost/test/utils/runtime/cla/parser.hpp src/boost/test/utils/runtime/cla/parser.ipp src/boost/test/utils/runtime/cla/typed_parameter.hpp src/boost/test/utils/runtime/cla/validation.hpp src/boost/test/utils/runtime/cla/validation.ipp src/boost/test/utils/runtime/cla/value_generator.hpp src/boost/test/utils/runtime/cla/value_handler.hpp src/boost/test/utils/runtime/config.hpp src/boost/test/utils/runtime/env/environment.hpp src/boost/test/utils/runtime/env/environment.ipp src/boost/test/utils/runtime/env/fwd.hpp src/boost/test/utils/runtime/env/modifier.hpp src/boost/test/utils/runtime/env/variable.hpp src/boost/test/utils/runtime/fwd.hpp src/boost/test/utils/runtime/interpret_argument_value.hpp src/boost/test/utils/runtime/parameter.hpp src/boost/test/utils/runtime/trace.hpp src/boost/test/utils/runtime/validation.hpp src/boost/test/utils/trivial_singleton.hpp src/boost/test/utils/wrap_stringstream.hpp src/boost/test/utils/xml_printer.hpp src/boost/throw_exception.hpp src/boost/timer.hpp src/boost/timer/config.hpp src/boost/timer/timer.hpp src/boost/type.hpp src/boost/type_traits/add_const.hpp src/boost/type_traits/add_cv.hpp src/boost/type_traits/add_lvalue_reference.hpp src/boost/type_traits/add_pointer.hpp src/boost/type_traits/add_reference.hpp src/boost/type_traits/add_rvalue_reference.hpp src/boost/type_traits/add_volatile.hpp src/boost/type_traits/alignment_of.hpp src/boost/type_traits/arithmetic_traits.hpp src/boost/type_traits/broken_compiler_spec.hpp src/boost/type_traits/common_type.hpp src/boost/type_traits/composite_traits.hpp src/boost/type_traits/config.hpp src/boost/type_traits/conversion_traits.hpp src/boost/type_traits/cv_traits.hpp src/boost/type_traits/detail/bool_trait_def.hpp src/boost/type_traits/detail/bool_trait_undef.hpp src/boost/type_traits/detail/common_type_imp.hpp src/boost/type_traits/detail/cv_traits_impl.hpp src/boost/type_traits/detail/false_result.hpp src/boost/type_traits/detail/has_binary_operator.hpp src/boost/type_traits/detail/ice_and.hpp src/boost/type_traits/detail/ice_eq.hpp src/boost/type_traits/detail/ice_not.hpp src/boost/type_traits/detail/ice_or.hpp src/boost/type_traits/detail/is_function_ptr_helper.hpp src/boost/type_traits/detail/is_function_ptr_tester.hpp src/boost/type_traits/detail/is_mem_fun_pointer_impl.hpp src/boost/type_traits/detail/is_mem_fun_pointer_tester.hpp src/boost/type_traits/detail/size_t_trait_def.hpp src/boost/type_traits/detail/size_t_trait_undef.hpp src/boost/type_traits/detail/template_arity_spec.hpp src/boost/type_traits/detail/type_trait_def.hpp src/boost/type_traits/detail/type_trait_undef.hpp src/boost/type_traits/detail/wrap.hpp src/boost/type_traits/detail/yes_no_type.hpp src/boost/type_traits/function_traits.hpp src/boost/type_traits/has_left_shift.hpp src/boost/type_traits/has_nothrow_constructor.hpp src/boost/type_traits/has_nothrow_copy.hpp src/boost/type_traits/has_right_shift.hpp src/boost/type_traits/has_trivial_constructor.hpp src/boost/type_traits/has_trivial_copy.hpp src/boost/type_traits/ice.hpp src/boost/type_traits/integral_constant.hpp src/boost/type_traits/intrinsics.hpp src/boost/type_traits/is_abstract.hpp src/boost/type_traits/is_arithmetic.hpp src/boost/type_traits/is_array.hpp src/boost/type_traits/is_base_and_derived.hpp src/boost/type_traits/is_base_of.hpp src/boost/type_traits/is_class.hpp src/boost/type_traits/is_const.hpp src/boost/type_traits/is_convertible.hpp src/boost/type_traits/is_enum.hpp src/boost/type_traits/is_float.hpp src/boost/type_traits/is_floating_point.hpp src/boost/type_traits/is_function.hpp src/boost/type_traits/is_fundamental.hpp src/boost/type_traits/is_integral.hpp src/boost/type_traits/is_lvalue_reference.hpp src/boost/type_traits/is_member_function_pointer.hpp src/boost/type_traits/is_member_pointer.hpp src/boost/type_traits/is_pod.hpp src/boost/type_traits/is_pointer.hpp src/boost/type_traits/is_polymorphic.hpp src/boost/type_traits/is_reference.hpp src/boost/type_traits/is_rvalue_reference.hpp src/boost/type_traits/is_same.hpp src/boost/type_traits/is_scalar.hpp src/boost/type_traits/is_signed.hpp src/boost/type_traits/is_union.hpp src/boost/type_traits/is_unsigned.hpp src/boost/type_traits/is_void.hpp src/boost/type_traits/is_volatile.hpp src/boost/type_traits/make_signed.hpp src/boost/type_traits/make_unsigned.hpp src/boost/type_traits/msvc/remove_bounds.hpp src/boost/type_traits/msvc/remove_const.hpp src/boost/type_traits/msvc/remove_cv.hpp src/boost/type_traits/msvc/remove_pointer.hpp src/boost/type_traits/msvc/remove_reference.hpp src/boost/type_traits/msvc/remove_volatile.hpp src/boost/type_traits/msvc/typeof.hpp src/boost/type_traits/remove_bounds.hpp src/boost/type_traits/remove_const.hpp src/boost/type_traits/remove_cv.hpp src/boost/type_traits/remove_pointer.hpp src/boost/type_traits/remove_reference.hpp src/boost/type_traits/remove_volatile.hpp src/boost/type_traits/type_with_alignment.hpp src/boost/typeof/dmc/typeof_impl.hpp src/boost/typeof/encode_decode.hpp src/boost/typeof/encode_decode_params.hpp src/boost/typeof/int_encoding.hpp src/boost/typeof/integral_template_param.hpp src/boost/typeof/message.hpp src/boost/typeof/modifiers.hpp src/boost/typeof/msvc/typeof_impl.hpp src/boost/typeof/native.hpp src/boost/typeof/pointers_data_members.hpp src/boost/typeof/register_functions.hpp src/boost/typeof/register_functions_iterate.hpp src/boost/typeof/register_fundamental.hpp src/boost/typeof/register_mem_functions.hpp src/boost/typeof/template_encoding.hpp src/boost/typeof/template_template_param.hpp src/boost/typeof/type_encoding.hpp src/boost/typeof/type_template_param.hpp src/boost/typeof/typeof.hpp src/boost/typeof/typeof_impl.hpp src/boost/typeof/unsupported.hpp src/boost/typeof/vector.hpp src/boost/typeof/vector100.hpp src/boost/typeof/vector150.hpp src/boost/typeof/vector200.hpp src/boost/typeof/vector50.hpp src/boost/utility.hpp src/boost/utility/addressof.hpp src/boost/utility/base_from_member.hpp src/boost/utility/binary.hpp src/boost/utility/compare_pointees.hpp src/boost/utility/declval.hpp src/boost/utility/detail/in_place_factory_prefix.hpp src/boost/utility/detail/in_place_factory_suffix.hpp src/boost/utility/enable_if.hpp src/boost/utility/identity_type.hpp src/boost/utility/in_place_factory.hpp src/boost/utility/swap.hpp src/boost/utility/value_init.hpp src/boost/version.hpp src/boost/visit_each.hpp src/clipper.cpp src/clipper.hpp src/libslic3r/BoundingBox.cpp src/libslic3r/BoundingBox.hpp src/libslic3r/BridgeDetector.cpp src/libslic3r/BridgeDetector.hpp src/libslic3r/ClipperUtils.cpp src/libslic3r/ClipperUtils.hpp src/libslic3r/Config.cpp src/libslic3r/Config.hpp src/libslic3r/ExPolygon.cpp src/libslic3r/ExPolygon.hpp src/libslic3r/ExPolygonCollection.cpp src/libslic3r/ExPolygonCollection.hpp src/libslic3r/Extruder.cpp src/libslic3r/Extruder.hpp src/libslic3r/ExtrusionEntity.cpp src/libslic3r/ExtrusionEntity.hpp src/libslic3r/ExtrusionEntityCollection.cpp src/libslic3r/ExtrusionEntityCollection.hpp src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp src/libslic3r/GCode.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp src/libslic3r/Geometry.cpp src/libslic3r/Geometry.hpp src/libslic3r/GUI/3DScene.cpp src/libslic3r/GUI/3DScene.hpp src/libslic3r/Layer.cpp src/libslic3r/Layer.hpp src/libslic3r/LayerRegion.cpp src/libslic3r/libslic3r.h src/libslic3r/Line.cpp src/libslic3r/Line.hpp src/libslic3r/Model.cpp src/libslic3r/Model.hpp src/libslic3r/MotionPlanner.cpp src/libslic3r/MotionPlanner.hpp src/libslic3r/MultiPoint.cpp src/libslic3r/MultiPoint.hpp src/libslic3r/PlaceholderParser.cpp src/libslic3r/PlaceholderParser.hpp src/libslic3r/Point.cpp src/libslic3r/Point.hpp src/libslic3r/Polygon.cpp src/libslic3r/Polygon.hpp src/libslic3r/Polyline.cpp src/libslic3r/Polyline.hpp src/libslic3r/PolylineCollection.cpp src/libslic3r/PolylineCollection.hpp src/libslic3r/Print.cpp src/libslic3r/Print.hpp src/libslic3r/PrintConfig.cpp src/libslic3r/PrintConfig.hpp src/libslic3r/PrintObject.cpp src/libslic3r/PrintRegion.cpp src/libslic3r/SupportMaterial.hpp src/libslic3r/Surface.cpp src/libslic3r/Surface.hpp src/libslic3r/SurfaceCollection.cpp src/libslic3r/SurfaceCollection.hpp src/libslic3r/SVG.cpp src/libslic3r/SVG.hpp src/libslic3r/TriangleMesh.cpp src/libslic3r/TriangleMesh.hpp src/libslic3r/utils.cpp src/myinit.h src/perlglue.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/polypartition.cpp src/polypartition.h src/ppport.h t/01_trianglemesh.t t/03_point.t t/04_expolygon.t t/05_surface.t t/06_polygon.t t/07_extrusionpath.t t/08_extrusionloop.t t/09_polyline.t t/10_line.t t/11_clipper.t t/12_extrusionpathcollection.t t/13_polylinecollection.t t/14_geometry.t t/15_config.t t/16_flow.t t/17_boundingbox.t t/18_motionplanner.t t/19_model.t t/20_print.t xsp/BoundingBox.xsp xsp/BridgeDetector.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/GCodeWriter.xsp xsp/Geometry.xsp xsp/GUI_3DScene.xsp xsp/Layer.xsp xsp/Line.xsp xsp/Model.xsp xsp/MotionPlanner.xsp xsp/my.map xsp/mytype.map xsp/PlaceholderParser.xsp xsp/Point.xsp xsp/Polygon.xsp xsp/Polyline.xsp xsp/PolylineCollection.xsp xsp/Print.xsp xsp/SupportMaterial.xsp xsp/Surface.xsp xsp/SurfaceCollection.xsp xsp/TriangleMesh.xsp xsp/typemap.xspt xsp/XS.xsp Slic3r-1.2.9/xs/MANIFEST.SKIP000066400000000000000000000022671254023100400152100ustar00rootroot00000000000000 #!start included /Library/Perl/Updates/5.12.4/ExtUtils/MANIFEST.SKIP # Avoid version control files. \bRCS\b \bCVS\b \bSCCS\b ,v$ \B\.svn\b \B\.git\b \B\.gitignore\b \b_darcs\b \B\.cvsignore$ # Avoid VMS specific MakeMaker generated files \bDescrip.MMS$ \bDESCRIP.MMS$ \bdescrip.mms$ # Avoid Makemaker generated and utility files. \bMANIFEST\.bak \bMakefile$ \bblib/ \bMakeMaker-\d \bpm_to_blib\.ts$ \bpm_to_blib$ \bblibdirs\.ts$ # 6.18 through 6.25 generated this # Avoid Module::Build generated and utility files. \bBuild$ \b_build/ \bBuild.bat$ \bBuild.COM$ \bBUILD.COM$ \bbuild.com$ # Avoid temp and backup files. ~$ \.old$ \#$ \b\.# \.bak$ \.tmp$ \.# \.rej$ # Avoid OS-specific files/dirs # Mac OSX metadata \B\.DS_Store # Mac OSX SMB mount metadata files \B\._ # Avoid Devel::Cover and Devel::CoverX::Covered files. \bcover_db\b \bcovered\b # Avoid MYMETA files ^MYMETA\. #!end included /Library/Perl/Updates/5.12.4/ExtUtils/MANIFEST.SKIP # Avoid configuration metadata file ^MYMETA\. # Avoid Module::Build generated and utility files. \bBuild$ \bBuild.bat$ \b_build \bBuild.COM$ \bBUILD.COM$ \bbuild.com$ ^MANIFEST\.SKIP # Avoid archives of this distribution \bSlic3r-XS-[\d\.\_]+ Slic3r-1.2.9/xs/lib/000077500000000000000000000000001254023100400140515ustar00rootroot00000000000000Slic3r-1.2.9/xs/lib/Slic3r/000077500000000000000000000000001254023100400152105ustar00rootroot00000000000000Slic3r-1.2.9/xs/lib/Slic3r/XS.pm000066400000000000000000000134171254023100400161060ustar00rootroot00000000000000package Slic3r::XS; use warnings; use strict; our $VERSION = '0.01'; use Carp qw(); use XSLoader; XSLoader::load(__PACKAGE__, $VERSION); package Slic3r::Line; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Point; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Point3; use overload '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #, 'fallback' => 1; sub pp { my ($self) = @_; return [ @$self ]; } package Slic3r::Pointf; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Pointf3; use overload '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #, 'fallback' => 1; sub pp { my ($self) = @_; return [ @$self ]; } package Slic3r::ExPolygon; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Polyline; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Polyline::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Polygon; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::ExPolygon::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::ExtrusionPath::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new { my ($class, @paths) = @_; my $self = $class->_new; $self->append(@paths); return $self; } package Slic3r::ExtrusionLoop; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new_from_paths { my ($class, @paths) = @_; my $loop = $class->new; $loop->append($_) for @paths; return $loop; } package Slic3r::ExtrusionPath; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new { my ($class, %args) = @_; return $class->_new( $args{polyline}, # required $args{role}, # required $args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionPath constructor"), $args{width} // -1, $args{height} // -1, ); } sub clone { my ($self, %args) = @_; return __PACKAGE__->_new( $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::Flow; sub new { my ($class, %args) = @_; my $self = $class->_new( @args{qw(width height nozzle_diameter)}, ); $self->set_bridge($args{bridge} // 0); return $self; } sub new_from_width { my ($class, %args) = @_; return $class->_new_from_width( @args{qw(role width nozzle_diameter layer_height bridge_flow_ratio)}, ); } sub new_from_spacing { my ($class, %args) = @_; return $class->_new_from_spacing( @args{qw(spacing nozzle_diameter layer_height bridge)}, ); } package Slic3r::Surface; sub new { my ($class, %args) = @_; # defensive programming: make sure no negative bridge_angle is supplied die "Error: invalid negative bridge_angle\n" if defined $args{bridge_angle} && $args{bridge_angle} < 0; return $class->_new( $args{expolygon} // (die "Missing required expolygon\n"), $args{surface_type} // (die "Missing required surface_type\n"), $args{thickness} // -1, $args{thickness_layers} // 1, $args{bridge_angle} // -1, $args{extra_perimeters} // 0, ); } sub clone { my ($self, %args) = @_; return (ref $self)->_new( delete $args{expolygon} // $self->expolygon, delete $args{surface_type} // $self->surface_type, delete $args{thickness} // $self->thickness, delete $args{thickness_layers} // $self->thickness_layers, delete $args{bridge_angle} // $self->bridge_angle, delete $args{extra_perimeters} // $self->extra_perimeters, ); } package Slic3r::Surface::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new { my ($class, @surfaces) = @_; my $self = $class->_new; $self->append($_) for @surfaces; return $self; } package Slic3r::GUI::_3DScene::GLVertexArray; sub CLONE_SKIP { 1 } package main; for my $class (qw( Slic3r::BridgeDetector Slic3r::Config Slic3r::Config::Full Slic3r::Config::GCode 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::Linef3 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; Slic3r-1.2.9/xs/src/000077500000000000000000000000001254023100400140725ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/admesh/000077500000000000000000000000001254023100400153335ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/admesh/connect.c000066400000000000000000000754461254023100400171500ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" static void stl_match_neighbors_exact(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_initialize_facet_check_exact(stl_file *stl); static void stl_initialize_facet_check_nearby(stl_file *stl); static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b); static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance); static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)); static int stl_get_hash_for_edge(int M, stl_hash_edge *edge); static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_free_edges(stl_file *stl); static void stl_remove_facet(stl_file *stl, int facet_number); static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex); static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b, int *facet1, int *vertex1, int *facet2, int *vertex2, stl_vertex *new_vertex1, stl_vertex *new_vertex2); static void stl_remove_degenerate(stl_file *stl, int facet); extern int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void stl_update_connects_remove_1(stl_file *stl, int facet_num); void stl_check_facets_exact(stl_file *stl) { /* This function builds the neighbors list. No modifications are made * to any of the facets. The edges are said to match only if all six * floats of the first edge matches all six floats of the second edge. */ stl_hash_edge edge; stl_facet facet; int i; int j; if (stl->error) return; stl->stats.connected_edges = 0; stl->stats.connected_facets_1_edge = 0; stl->stats.connected_facets_2_edge = 0; stl->stats.connected_facets_3_edge = 0; stl_initialize_facet_check_exact(stl); for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; /* If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. */ if( !memcmp(&facet.vertex[0], &facet.vertex[1], sizeof(stl_vertex)) || !memcmp(&facet.vertex[1], &facet.vertex[2], sizeof(stl_vertex)) || !memcmp(&facet.vertex[0], &facet.vertex[2], sizeof(stl_vertex))) { stl->stats.degenerate_facets += 1; stl_remove_facet(stl, i); i--; continue; } for(j = 0; j < 3; j++) { edge.facet_number = i; edge.which_edge = j; stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); insert_hash_edge(stl, edge, stl_match_neighbors_exact); } } stl_free_edges(stl); } static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b) { float diff_x; float diff_y; float diff_z; float max_diff; if (stl->error) return; diff_x = ABS(a->x - b->x); diff_y = ABS(a->y - b->y); diff_z = ABS(a->z - b->z); max_diff = STL_MAX(diff_x, diff_y); max_diff = STL_MAX(diff_z, max_diff); stl->stats.shortest_edge = STL_MIN(max_diff, stl->stats.shortest_edge); if(diff_x == max_diff) { if(a->x > b->x) { memcpy(&edge->key[0], a, sizeof(stl_vertex)); memcpy(&edge->key[3], b, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], b, sizeof(stl_vertex)); memcpy(&edge->key[3], a, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } else if(diff_y == max_diff) { if(a->y > b->y) { memcpy(&edge->key[0], a, sizeof(stl_vertex)); memcpy(&edge->key[3], b, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], b, sizeof(stl_vertex)); memcpy(&edge->key[3], a, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } else { if(a->z > b->z) { memcpy(&edge->key[0], a, sizeof(stl_vertex)); memcpy(&edge->key[3], b, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], b, sizeof(stl_vertex)); memcpy(&edge->key[3], a, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } } static void stl_initialize_facet_check_exact(stl_file *stl) { int i; if (stl->error) return; stl->stats.malloced = 0; stl->stats.freed = 0; stl->stats.collisions = 0; stl->M = 81397; for(i = 0; i < stl->stats.number_of_facets ; i++) { /* initialize neighbors list to -1 to mark unconnected edges */ stl->neighbors_start[i].neighbor[0] = -1; stl->neighbors_start[i].neighbor[1] = -1; stl->neighbors_start[i].neighbor[2] = -1; } stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); if(stl->heads == NULL) perror("stl_initialize_facet_check_exact"); stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(stl->tail == NULL) perror("stl_initialize_facet_check_exact"); stl->tail->next = stl->tail; for(i = 0; i < stl->M; i++) { stl->heads[i] = stl->tail; } } static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)) { stl_hash_edge *link; stl_hash_edge *new_edge; stl_hash_edge *temp; int chain_number; if (stl->error) return; chain_number = stl_get_hash_for_edge(stl->M, &edge); link = stl->heads[chain_number]; if(link == stl->tail) { /* This list doesn't have any edges currently in it. Add this one. */ new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(new_edge == NULL) perror("insert_hash_edge"); stl->stats.malloced++; *new_edge = edge; new_edge->next = stl->tail; stl->heads[chain_number] = new_edge; return; } else if(!stl_compare_function(&edge, link)) { /* This is a match. Record result in neighbors list. */ match_neighbors(stl, &edge, link); /* Delete the matched edge from the list. */ stl->heads[chain_number] = link->next; free(link); stl->stats.freed++; return; } else { /* Continue through the rest of the list */ for(;;) { if(link->next == stl->tail) { /* This is the last item in the list. Insert a new edge. */ new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(new_edge == NULL) perror("insert_hash_edge"); stl->stats.malloced++; *new_edge = edge; new_edge->next = stl->tail; link->next = new_edge; stl->stats.collisions++; return; } else if(!stl_compare_function(&edge, link->next)) { /* This is a match. Record result in neighbors list. */ match_neighbors(stl, &edge, link->next); /* Delete the matched edge from the list. */ temp = link->next; link->next = link->next->next; free(temp); stl->stats.freed++; return; } else { /* This is not a match. Go to the next link */ link = link->next; stl->stats.collisions++; } } } } static int stl_get_hash_for_edge(int M, stl_hash_edge *edge) { return ((edge->key[0] / 23 + edge->key[1] / 19 + edge->key[2] / 17 + edge->key[3] /13 + edge->key[4] / 11 + edge->key[5] / 7 ) % M); } static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) { if(edge_a->facet_number == edge_b->facet_number) { return 1; /* Don't match edges of the same facet */ } else { return memcmp(edge_a, edge_b, SIZEOF_EDGE_SORT); } } void stl_check_facets_nearby(stl_file *stl, float tolerance) { stl_hash_edge edge[3]; stl_facet facet; int i; int j; if (stl->error) return; if( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { /* No need to check any further. All facets are connected */ return; } stl_initialize_facet_check_nearby(stl); for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] == -1) { edge[j].facet_number = i; edge[j].which_edge = j; if(stl_load_edge_nearby(stl, &edge[j], &facet.vertex[j], &facet.vertex[(j + 1) % 3], tolerance)) { /* only insert edges that have different keys */ insert_hash_edge(stl, edge[j], stl_match_neighbors_nearby); } } } } stl_free_edges(stl); } static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance) { float diff_x; float diff_y; float diff_z; float max_diff; unsigned vertex1[3]; unsigned vertex2[3]; diff_x = ABS(a->x - b->x); diff_y = ABS(a->y - b->y); diff_z = ABS(a->z - b->z); max_diff = STL_MAX(diff_x, diff_y); max_diff = STL_MAX(diff_z, max_diff); vertex1[0] = (unsigned)((a->x - stl->stats.min.x) / tolerance); vertex1[1] = (unsigned)((a->y - stl->stats.min.y) / tolerance); vertex1[2] = (unsigned)((a->z - stl->stats.min.z) / tolerance); vertex2[0] = (unsigned)((b->x - stl->stats.min.x) / tolerance); vertex2[1] = (unsigned)((b->y - stl->stats.min.y) / tolerance); vertex2[2] = (unsigned)((b->z - stl->stats.min.z) / tolerance); if( (vertex1[0] == vertex2[0]) && (vertex1[1] == vertex2[1]) && (vertex1[2] == vertex2[2])) { /* Both vertices hash to the same value */ return 0; } if(diff_x == max_diff) { if(a->x > b->x) { memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } else if(diff_y == max_diff) { if(a->y > b->y) { memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } else { if(a->z > b->z) { memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } return 1; } static void stl_free_edges(stl_file *stl) { int i; stl_hash_edge *temp; if (stl->error) return; if(stl->stats.malloced != stl->stats.freed) { for(i = 0; i < stl->M; i++) { for(temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) { stl->heads[i] = stl->heads[i]->next; free(temp); stl->stats.freed++; } } } free(stl->heads); free(stl->tail); } static void stl_initialize_facet_check_nearby(stl_file *stl) { int i; if (stl->error) return; stl->stats.malloced = 0; stl->stats.freed = 0; stl->stats.collisions = 0; /* tolerance = STL_MAX(stl->stats.shortest_edge, tolerance);*/ /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ /* tolerance *= 0.5;*/ stl->M = 81397; stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); if(stl->heads == NULL) perror("stl_initialize_facet_check_nearby"); stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(stl->tail == NULL) perror("stl_initialize_facet_check_nearby"); stl->tail->next = stl->tail; for(i = 0; i < stl->M; i++) { stl->heads[i] = stl->tail; } } static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { int i; int j; if (stl->error) return; /* Facet a's neighbor is facet b */ stl->neighbors_start[edge_a->facet_number].neighbor[edge_a->which_edge % 3] = edge_b->facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_a->facet_number]. which_vertex_not[edge_a->which_edge % 3] = (edge_b->which_edge + 2) % 3; /* sets the .which_vertex_not part */ /* Facet b's neighbor is facet a */ stl->neighbors_start[edge_b->facet_number].neighbor[edge_b->which_edge % 3] = edge_a->facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_b->facet_number]. which_vertex_not[edge_b->which_edge % 3] = (edge_a->which_edge + 2) % 3; /* sets the .which_vertex_not part */ if( ((edge_a->which_edge < 3) && (edge_b->which_edge < 3)) || ((edge_a->which_edge > 2) && (edge_b->which_edge > 2))) { /* these facets are oriented in opposite directions. */ /* their normals are probably messed up. */ stl->neighbors_start[edge_a->facet_number]. which_vertex_not[edge_a->which_edge % 3] += 3; stl->neighbors_start[edge_b->facet_number]. which_vertex_not[edge_b->which_edge % 3] += 3; } /* Count successful connects */ /* Total connects */ stl->stats.connected_edges += 2; /* Count individual connects */ i = ((stl->neighbors_start[edge_a->facet_number].neighbor[0] == -1) + (stl->neighbors_start[edge_a->facet_number].neighbor[1] == -1) + (stl->neighbors_start[edge_a->facet_number].neighbor[2] == -1)); j = ((stl->neighbors_start[edge_b->facet_number].neighbor[0] == -1) + (stl->neighbors_start[edge_b->facet_number].neighbor[1] == -1) + (stl->neighbors_start[edge_b->facet_number].neighbor[2] == -1)); if(i == 2) { stl->stats.connected_facets_1_edge +=1; } else if(i == 1) { stl->stats.connected_facets_2_edge +=1; } else { stl->stats.connected_facets_3_edge +=1; } if(j == 2) { stl->stats.connected_facets_1_edge +=1; } else if(j == 1) { stl->stats.connected_facets_2_edge +=1; } else { stl->stats.connected_facets_3_edge +=1; } } static void stl_match_neighbors_exact(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { if (stl->error) return; stl_record_neighbors(stl, edge_a, edge_b); } static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { int facet1; int facet2; int vertex1; int vertex2; int vnot1; int vnot2; stl_vertex new_vertex1; stl_vertex new_vertex2; if (stl->error) return; stl_record_neighbors(stl, edge_a, edge_b); stl_which_vertices_to_change(stl, edge_a, edge_b, &facet1, &vertex1, &facet2, &vertex2, &new_vertex1, &new_vertex2); if(facet1 != -1) { if(facet1 == edge_a->facet_number) { vnot1 = (edge_a->which_edge + 2) % 3; } else { vnot1 = (edge_b->which_edge + 2) % 3; } if(((vnot1 + 2) % 3) == vertex1) { vnot1 += 3; } stl_change_vertices(stl, facet1, vnot1, new_vertex1); } if(facet2 != -1) { if(facet2 == edge_a->facet_number) { vnot2 = (edge_a->which_edge + 2) % 3; } else { vnot2 = (edge_b->which_edge + 2) % 3; } if(((vnot2 + 2) % 3) == vertex2) { vnot2 += 3; } stl_change_vertices(stl, facet2, vnot2, new_vertex2); } stl->stats.edges_fixed += 2; } static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex) { int first_facet; int direction; int next_edge; int pivot_vertex; if (stl->error) return; first_facet = facet_num; direction = 0; for(;;) { if(vnot > 2) { if(direction == 0) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; direction = 1; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; direction = 0; } } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; if(facet_num == -1) { break; } if(facet_num == first_facet) { /* back to the beginning */ printf("\ Back to the first facet changing vertices: probably a mobius part.\n\ Try using a smaller tolerance or don't do a nearby check\n"); return; } } } static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b, int *facet1, int *vertex1, int *facet2, int *vertex2, stl_vertex *new_vertex1, stl_vertex *new_vertex2) { int v1a; /* pair 1, facet a */ int v1b; /* pair 1, facet b */ int v2a; /* pair 2, facet a */ int v2b; /* pair 2, facet b */ /* Find first pair */ if(edge_a->which_edge < 3) { v1a = edge_a->which_edge; v2a = (edge_a->which_edge + 1) % 3; } else { v2a = edge_a->which_edge % 3; v1a = (edge_a->which_edge + 1) % 3; } if(edge_b->which_edge < 3) { v1b = edge_b->which_edge; v2b = (edge_b->which_edge + 1) % 3; } else { v2b = edge_b->which_edge % 3; v1b = (edge_b->which_edge + 1) % 3; } /* Of the first pair, which vertex, if any, should be changed */ if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v1a], &stl->facet_start[edge_b->facet_number].vertex[v1b], sizeof(stl_vertex))) { /* These facets are already equal. No need to change. */ *facet1 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1) && (stl->neighbors_start[edge_a->facet_number]. neighbor[(v1a + 2) % 3] == -1)) { /* This vertex has no neighbors. This is a good one to change */ *facet1 = edge_a->facet_number; *vertex1 = v1a; *new_vertex1 = stl->facet_start[edge_b->facet_number].vertex[v1b]; } else { *facet1 = edge_b->facet_number; *vertex1 = v1b; *new_vertex1 = stl->facet_start[edge_a->facet_number].vertex[v1a]; } } /* Of the second pair, which vertex, if any, should be changed */ if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v2a], &stl->facet_start[edge_b->facet_number].vertex[v2b], sizeof(stl_vertex))) { /* These facets are already equal. No need to change. */ *facet2 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1) && (stl->neighbors_start[edge_a->facet_number]. neighbor[(v2a + 2) % 3] == -1)) { /* This vertex has no neighbors. This is a good one to change */ *facet2 = edge_a->facet_number; *vertex2 = v2a; *new_vertex2 = stl->facet_start[edge_b->facet_number].vertex[v2b]; } else { *facet2 = edge_b->facet_number; *vertex2 = v2b; *new_vertex2 = stl->facet_start[edge_a->facet_number].vertex[v2a]; } } } static void stl_remove_facet(stl_file *stl, int facet_number) { int neighbor[3]; int vnot[3]; int i; int j; if (stl->error) return; stl->stats.facets_removed += 1; /* Update list of connected edges */ j = ((stl->neighbors_start[facet_number].neighbor[0] == -1) + (stl->neighbors_start[facet_number].neighbor[1] == -1) + (stl->neighbors_start[facet_number].neighbor[2] == -1)); if(j == 2) { stl->stats.connected_facets_1_edge -= 1; } else if(j == 1) { stl->stats.connected_facets_2_edge -= 1; stl->stats.connected_facets_1_edge -= 1; } else if(j == 0) { stl->stats.connected_facets_3_edge -= 1; stl->stats.connected_facets_2_edge -= 1; stl->stats.connected_facets_1_edge -= 1; } stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets - 1]; /* I could reallocate at this point, but it is not really necessary. */ stl->neighbors_start[facet_number] = stl->neighbors_start[stl->stats.number_of_facets - 1]; stl->stats.number_of_facets -= 1; for(i = 0; i < 3; i++) { neighbor[i] = stl->neighbors_start[facet_number].neighbor[i]; vnot[i] = stl->neighbors_start[facet_number].which_vertex_not[i]; } for(i = 0; i < 3; i++) { if(neighbor[i] != -1) { if(stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] != stl->stats.number_of_facets) { printf("\ in stl_remove_facet: neighbor = %d numfacets = %d this is wrong\n", stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3], stl->stats.number_of_facets); return; } stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] = facet_number; } } } void stl_remove_unconnected_facets(stl_file *stl) { /* A couple of things need to be done here. One is to remove any */ /* completely unconnected facets (0 edges connected) since these are */ /* useless and could be completely wrong. The second thing that needs to */ /* be done is to remove any degenerate facets that were created during */ /* stl_check_facets_nearby(). */ int i; if (stl->error) return; /* remove degenerate facets */ for(i = 0; i < stl->stats.number_of_facets; i++) { if( !memcmp(&stl->facet_start[i].vertex[0], &stl->facet_start[i].vertex[1], sizeof(stl_vertex)) || !memcmp(&stl->facet_start[i].vertex[1], &stl->facet_start[i].vertex[2], sizeof(stl_vertex)) || !memcmp(&stl->facet_start[i].vertex[0], &stl->facet_start[i].vertex[2], sizeof(stl_vertex))) { stl_remove_degenerate(stl, i); i--; } } if(stl->stats.connected_facets_1_edge < stl->stats.number_of_facets) { /* remove completely unconnected facets */ for(i = 0; i < stl->stats.number_of_facets; i++) { if( (stl->neighbors_start[i].neighbor[0] == -1) && (stl->neighbors_start[i].neighbor[1] == -1) && (stl->neighbors_start[i].neighbor[2] == -1)) { /* This facet is completely unconnected. Remove it. */ stl_remove_facet(stl, i); i--; } } } } static void stl_remove_degenerate(stl_file *stl, int facet) { int edge1; int edge2; int edge3; int neighbor1; int neighbor2; int neighbor3; int vnot1; int vnot2; int vnot3; if (stl->error) return; if( !memcmp(&stl->facet_start[facet].vertex[0], &stl->facet_start[facet].vertex[1], sizeof(stl_vertex)) && !memcmp(&stl->facet_start[facet].vertex[1], &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) { /* all 3 vertices are equal. Just remove the facet. I don't think*/ /* this is really possible, but just in case... */ printf("removing a facet in stl_remove_degenerate\n"); stl_remove_facet(stl, facet); return; } if(!memcmp(&stl->facet_start[facet].vertex[0], &stl->facet_start[facet].vertex[1], sizeof(stl_vertex))) { edge1 = 1; edge2 = 2; edge3 = 0; } else if(!memcmp(&stl->facet_start[facet].vertex[1], &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) { edge1 = 0; edge2 = 2; edge3 = 1; } else if(!memcmp(&stl->facet_start[facet].vertex[2], &stl->facet_start[facet].vertex[0], sizeof(stl_vertex))) { edge1 = 0; edge2 = 1; edge3 = 2; } else { /* No degenerate. Function shouldn't have been called. */ return; } neighbor1 = stl->neighbors_start[facet].neighbor[edge1]; neighbor2 = stl->neighbors_start[facet].neighbor[edge2]; if(neighbor1 == -1) { stl_update_connects_remove_1(stl, neighbor2); } if(neighbor2 == -1) { stl_update_connects_remove_1(stl, neighbor1); } neighbor3 = stl->neighbors_start[facet].neighbor[edge3]; vnot1 = stl->neighbors_start[facet].which_vertex_not[edge1]; vnot2 = stl->neighbors_start[facet].which_vertex_not[edge2]; vnot3 = stl->neighbors_start[facet].which_vertex_not[edge3]; stl->neighbors_start[neighbor1].neighbor[(vnot1 + 1) % 3] = neighbor2; stl->neighbors_start[neighbor2].neighbor[(vnot2 + 1) % 3] = neighbor1; stl->neighbors_start[neighbor1].which_vertex_not[(vnot1 + 1) % 3] = vnot2; stl->neighbors_start[neighbor2].which_vertex_not[(vnot2 + 1) % 3] = vnot1; stl_remove_facet(stl, facet); if(neighbor3 != -1) { stl_update_connects_remove_1(stl, neighbor3); stl->neighbors_start[neighbor3].neighbor[(vnot3 + 1) % 3] = -1; } } void stl_update_connects_remove_1(stl_file *stl, int facet_num) { int j; if (stl->error) return; /* Update list of connected edges */ j = ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)); if(j == 0) { /* Facet has 3 neighbors */ stl->stats.connected_facets_3_edge -= 1; } else if(j == 1) { /* Facet has 2 neighbors */ stl->stats.connected_facets_2_edge -= 1; } else if(j == 2) { /* Facet has 1 neighbor */ stl->stats.connected_facets_1_edge -= 1; } } void stl_fill_holes(stl_file *stl) { stl_facet facet; stl_facet new_facet; int neighbors_initial[3]; stl_hash_edge edge; int first_facet; int direction; int facet_num; int vnot; int next_edge; int pivot_vertex; int next_facet; int i; int j; int k; if (stl->error) return; /* Insert all unconnected edges into hash list */ stl_initialize_facet_check_nearby(stl); for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] != -1) continue; edge.facet_number = i; edge.which_edge = j; stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); insert_hash_edge(stl, edge, stl_match_neighbors_exact); } } for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; neighbors_initial[0] = stl->neighbors_start[i].neighbor[0]; neighbors_initial[1] = stl->neighbors_start[i].neighbor[1]; neighbors_initial[2] = stl->neighbors_start[i].neighbor[2]; first_facet = i; for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] != -1) continue; new_facet.vertex[0] = facet.vertex[j]; new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; if(neighbors_initial[(j + 2) % 3] == -1) { direction = 1; } else { direction = 0; } facet_num = i; vnot = (j + 2) % 3; for(;;) { if(vnot > 2) { if(direction == 0) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; direction = 1; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; direction = 0; } } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; if(next_facet == -1) { new_facet.vertex[2] = stl->facet_start[facet_num]. vertex[vnot % 3]; stl_add_facet(stl, &new_facet); for(k = 0; k < 3; k++) { edge.facet_number = stl->stats.number_of_facets - 1; edge.which_edge = k; stl_load_edge_exact(stl, &edge, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); insert_hash_edge(stl, edge, stl_match_neighbors_exact); } break; } else { vnot = stl->neighbors_start[facet_num]. which_vertex_not[next_edge]; facet_num = next_facet; } if(facet_num == first_facet) { /* back to the beginning */ printf("\ Back to the first facet filling holes: probably a mobius part.\n\ Try using a smaller tolerance or don't do a nearby check\n"); return; } } } } } void stl_add_facet(stl_file *stl, stl_facet *new_facet) { if (stl->error) return; stl->stats.facets_added += 1; if(stl->stats.facets_malloced < stl->stats.number_of_facets + 1) { stl->facet_start = (stl_facet*)realloc(stl->facet_start, (sizeof(stl_facet) * (stl->stats.facets_malloced + 256))); if(stl->facet_start == NULL) perror("stl_add_facet"); stl->neighbors_start = (stl_neighbors*)realloc(stl->neighbors_start, (sizeof(stl_neighbors) * (stl->stats.facets_malloced + 256))); if(stl->neighbors_start == NULL) perror("stl_add_facet"); stl->stats.facets_malloced += 256; } stl->facet_start[stl->stats.number_of_facets] = *new_facet; /* note that the normal vector is not set here, just initialized to 0 */ stl->facet_start[stl->stats.number_of_facets].normal.x = 0.0; stl->facet_start[stl->stats.number_of_facets].normal.y = 0.0; stl->facet_start[stl->stats.number_of_facets].normal.z = 0.0; stl->neighbors_start[stl->stats.number_of_facets].neighbor[0] = -1; stl->neighbors_start[stl->stats.number_of_facets].neighbor[1] = -1; stl->neighbors_start[stl->stats.number_of_facets].neighbor[2] = -1; stl->stats.number_of_facets += 1; } Slic3r-1.2.9/xs/src/admesh/normals.c000066400000000000000000000253241254023100400171600ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" static void stl_reverse_facet(stl_file *stl, int facet_num); static void stl_reverse_vector(float v[]); int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void stl_reverse_facet(stl_file *stl, int facet_num) { stl_vertex tmp_vertex; /* int tmp_neighbor;*/ int neighbor[3]; int vnot[3]; stl->stats.facets_reversed += 1; neighbor[0] = stl->neighbors_start[facet_num].neighbor[0]; neighbor[1] = stl->neighbors_start[facet_num].neighbor[1]; neighbor[2] = stl->neighbors_start[facet_num].neighbor[2]; vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0]; vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1]; vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2]; /* reverse the facet */ tmp_vertex = stl->facet_start[facet_num].vertex[0]; stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1]; stl->facet_start[facet_num].vertex[1] = tmp_vertex; /* fix the vnots of the neighboring facets */ if(neighbor[0] != -1) stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = (stl->neighbors_start[neighbor[0]]. which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; if(neighbor[1] != -1) stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]]. which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; if(neighbor[2] != -1) stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]]. which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; /* swap the neighbors of the facet that is being reversed */ stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; /* swap the vnots of the facet that is being reversed */ stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; /* reverse the values of the vnots of the facet that is being reversed */ stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; } void stl_fix_normal_directions(stl_file *stl) { char *norm_sw; /* int edge_num;*/ /* int vnot;*/ int checked = 0; int facet_num; /* int next_facet;*/ int i; int j; struct stl_normal { int facet_num; struct stl_normal *next; }; struct stl_normal *head; struct stl_normal *tail; struct stl_normal *newn; struct stl_normal *temp; if (stl->error) return; /* Initialize linked list. */ head = (struct stl_normal*)malloc(sizeof(struct stl_normal)); if(head == NULL) perror("stl_fix_normal_directions"); tail = (struct stl_normal*)malloc(sizeof(struct stl_normal)); if(tail == NULL) perror("stl_fix_normal_directions"); head->next = tail; tail->next = tail; /* Initialize list that keeps track of already fixed facets. */ norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); if(norm_sw == NULL) perror("stl_fix_normal_directions"); facet_num = 0; /* If normal vector is not within tolerance and backwards: Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances of it being wrong randomly are low if most of the triangles are right: */ if(stl_check_normal_vector(stl, 0, 0) == 2) stl_reverse_facet(stl, 0); /* Say that we've fixed this facet: */ norm_sw[facet_num] = 1; checked++; for(;;) { /* Add neighbors_to_list. Add unconnected neighbors to the list:a */ for(j = 0; j < 3; j++) { /* Reverse the neighboring facets if necessary. */ if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { /* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */ if(stl->neighbors_start[facet_num].neighbor[j] != -1) { stl_reverse_facet (stl, stl->neighbors_start[facet_num].neighbor[j]); } } /* If this edge of the facet is connected: */ if(stl->neighbors_start[facet_num].neighbor[j] != -1) { /* If we haven't fixed this facet yet, add it to the list: */ if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { /* Add node to beginning of list. */ newn = (struct stl_normal*)malloc(sizeof(struct stl_normal)); if(newn == NULL) perror("stl_fix_normal_directions"); newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; newn->next = head->next; head->next = newn; } } } /* Get next facet to fix from top of list. */ if(head->next != tail) { facet_num = head->next->facet_num; if(norm_sw[facet_num] != 1) { /* If facet is in list mutiple times */ norm_sw[facet_num] = 1; /* Record this one as being fixed. */ checked++; } temp = head->next; /* Delete this facet from the list. */ head->next = head->next->next; free(temp); } else { /* if we ran out of facets to fix: */ /* All of the facets in this part have been fixed. */ stl->stats.number_of_parts += 1; if(checked >= stl->stats.number_of_facets) { /* All of the facets have been checked. Bail out. */ break; } else { /* There is another part here. Find it and continue. */ for(i = 0; i < stl->stats.number_of_facets; i++) { if(norm_sw[i] == 0) { /* This is the first facet of the next part. */ facet_num = i; if(stl_check_normal_vector(stl, i, 0) == 2) { stl_reverse_facet(stl, i); } norm_sw[facet_num] = 1; checked++; break; } } } } } free(head); free(tail); free(norm_sw); } int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { /* Returns 0 if the normal is within tolerance */ /* Returns 1 if the normal is not within tolerance, but direction is OK */ /* Returns 2 if the normal is not within tolerance and backwards */ /* Returns 4 if the status is unknown. */ float normal[3]; float test_norm[3]; stl_facet *facet; facet = &stl->facet_start[facet_num]; stl_calculate_normal(normal, facet); stl_normalize_vector(normal); if( (ABS(normal[0] - facet->normal.x) < 0.001) && (ABS(normal[1] - facet->normal.y) < 0.001) && (ABS(normal[2] - facet->normal.z) < 0.001)) { /* It is not really necessary to change the values here */ /* but just for consistency, I will. */ facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; return 0; } test_norm[0] = facet->normal.x; test_norm[1] = facet->normal.y; test_norm[2] = facet->normal.z; stl_normalize_vector(test_norm); if( (ABS(normal[0] - test_norm[0]) < 0.001) && (ABS(normal[1] - test_norm[1]) < 0.001) && (ABS(normal[2] - test_norm[2]) < 0.001)) { if(normal_fix_flag) { facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; stl->stats.normals_fixed += 1; } return 1; } stl_reverse_vector(test_norm); if( (ABS(normal[0] - test_norm[0]) < 0.001) && (ABS(normal[1] - test_norm[1]) < 0.001) && (ABS(normal[2] - test_norm[2]) < 0.001)) { /* Facet is backwards. */ if(normal_fix_flag) { facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; stl->stats.normals_fixed += 1; } return 2; } if(normal_fix_flag) { facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; stl->stats.normals_fixed += 1; } return 4; } static void stl_reverse_vector(float v[]) { v[0] *= -1; v[1] *= -1; v[2] *= -1; } void stl_calculate_normal(float normal[], stl_facet *facet) { float v1[3]; float v2[3]; v1[0] = facet->vertex[1].x - facet->vertex[0].x; v1[1] = facet->vertex[1].y - facet->vertex[0].y; v1[2] = facet->vertex[1].z - facet->vertex[0].z; v2[0] = facet->vertex[2].x - facet->vertex[0].x; v2[1] = facet->vertex[2].y - facet->vertex[0].y; v2[2] = facet->vertex[2].z - facet->vertex[0].z; normal[0] = (float)((double)v1[1] * (double)v2[2]) - ((double)v1[2] * (double)v2[1]); normal[1] = (float)((double)v1[2] * (double)v2[0]) - ((double)v1[0] * (double)v2[2]); normal[2] = (float)((double)v1[0] * (double)v2[1]) - ((double)v1[1] * (double)v2[0]); } void stl_normalize_vector(float v[]) { double length; double factor; float min_normal_length; length = sqrt((double)v[0] * (double)v[0] + (double)v[1] * (double)v[1] + (double)v[2] * (double)v[2]); min_normal_length = 0.000000000001; if(length < min_normal_length) { v[0] = 0.0; v[1] = 0.0; v[2] = 0.0; return; } factor = 1.0 / length; v[0] *= factor; v[1] *= factor; v[2] *= factor; } void stl_fix_normal_values(stl_file *stl) { int i; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { stl_check_normal_vector(stl, i, 1); } } void stl_reverse_all_facets(stl_file *stl) { int i; float normal[3]; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { stl_reverse_facet(stl, i); stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); stl->facet_start[i].normal.x = normal[0]; stl->facet_start[i].normal.y = normal[1]; stl->facet_start[i].normal.z = normal[2]; } } Slic3r-1.2.9/xs/src/admesh/shared.c000066400000000000000000000173531254023100400167560ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include "stl.h" void stl_invalidate_shared_vertices(stl_file *stl) { if (stl->error) return; if (stl->v_indices != NULL) { free(stl->v_indices); stl->v_indices = NULL; } if (stl->v_shared != NULL) { free(stl->v_shared); stl->v_shared = NULL; } } void stl_generate_shared_vertices(stl_file *stl) { int i; int j; int first_facet; int direction; int facet_num; int vnot; int next_edge; int pivot_vertex; int next_facet; int reversed; if (stl->error) return; /* make sure this function is idempotent and does not leak memory */ stl_invalidate_shared_vertices(stl); stl->v_indices = (v_indices_struct*) calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); if(stl->v_indices == NULL) perror("stl_generate_shared_vertices"); stl->v_shared = (stl_vertex*) calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); stl->stats.shared_malloced = stl->stats.number_of_facets / 2; stl->stats.shared_vertices = 0; for(i = 0; i < stl->stats.number_of_facets; i++) { stl->v_indices[i].vertex[0] = -1; stl->v_indices[i].vertex[1] = -1; stl->v_indices[i].vertex[2] = -1; } for(i = 0; i < stl->stats.number_of_facets; i++) { first_facet = i; for(j = 0; j < 3; j++) { if(stl->v_indices[i].vertex[j] != -1) { continue; } if(stl->stats.shared_vertices == stl->stats.shared_malloced) { stl->stats.shared_malloced += 1024; stl->v_shared = (stl_vertex*)realloc(stl->v_shared, stl->stats.shared_malloced * sizeof(stl_vertex)); if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); } stl->v_shared[stl->stats.shared_vertices] = stl->facet_start[i].vertex[j]; direction = 0; reversed = 0; facet_num = i; vnot = (j + 2) % 3; for(;;) { if(vnot > 2) { if(direction == 0) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; direction = 1; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; direction = 0; } } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } stl->v_indices[facet_num].vertex[pivot_vertex] = stl->stats.shared_vertices; next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; if(next_facet == -1) { if(reversed) { break; } else { direction = 1; vnot = (j + 1) % 3; reversed = 1; facet_num = first_facet; } } else if(next_facet != first_facet) { vnot = stl->neighbors_start[facet_num]. which_vertex_not[next_edge]; facet_num = next_facet; } else { break; } } stl->stats.shared_vertices += 1; } } } void stl_write_off(stl_file *stl, char *file) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "OFF\n"); fprintf(fp, "%d %d 0\n", stl->stats.shared_vertices, stl->stats.number_of_facets); for(i = 0; i < stl->stats.shared_vertices; i++) { fprintf(fp, "\t%f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); } for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); } fclose(fp); } void stl_write_vrml(stl_file *stl, char *file) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "#VRML V1.0 ascii\n\n"); fprintf(fp, "Separator {\n"); fprintf(fp, "\tDEF STLShape ShapeHints {\n"); fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); fprintf(fp, "\t\tfaceType CONVEX\n"); fprintf(fp, "\t\tshapeType SOLID\n"); fprintf(fp, "\t\tcreaseAngle 0.0\n"); fprintf(fp, "\t}\n"); fprintf(fp, "\tDEF STLModel Separator {\n"); fprintf(fp, "\t\tDEF STLColor Material {\n"); fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); fprintf(fp, "\t\t\tpoint [\n"); for(i = 0; i < (stl->stats.shared_vertices - 1); i++) { fprintf(fp, "\t\t\t\t%f %f %f,\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); } fprintf(fp, "\t\t\t\t%f %f %f]\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); fprintf(fp, "\t\t\tcoordIndex [\n"); for(i = 0; i < (stl->stats.number_of_facets - 1); i++) { fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); } fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t}\n"); fprintf(fp, "}\n"); fclose(fp); } void stl_write_obj (stl_file *stl, char *file) { int i; FILE* fp; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if (fp == NULL) { char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } for (i = 0; i < stl->stats.shared_vertices; i++) { fprintf(fp, "v %f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); } for (i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); } fclose(fp); } Slic3r-1.2.9/xs/src/admesh/stl.h000066400000000000000000000154361254023100400163170ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #ifndef __admesh_stl__ #define __admesh_stl__ #include #ifdef __cplusplus extern "C" { #endif #define STL_MAX(A,B) ((A)>(B)? (A):(B)) #define STL_MIN(A,B) ((A)<(B)? (A):(B)) #define ABS(X) ((X) < 0 ? -(X) : (X)) #define LABEL_SIZE 80 #define NUM_FACET_SIZE 4 #define HEADER_SIZE 84 #define STL_MIN_FILE_SIZE 284 #define ASCII_LINES_PER_FACET 7 #define SIZEOF_EDGE_SORT 24 typedef struct { float x; float y; float z; } stl_vertex; typedef struct { float x; float y; float z; } stl_normal; typedef char stl_extra[2]; typedef struct { stl_normal normal; stl_vertex vertex[3]; stl_extra extra; } stl_facet; #define SIZEOF_STL_FACET 50 typedef enum {binary, ascii, inmemory} stl_type; typedef struct { stl_vertex p1; stl_vertex p2; int facet_number; } stl_edge; typedef struct stl_hash_edge { unsigned key[6]; int facet_number; int which_edge; struct stl_hash_edge *next; } stl_hash_edge; typedef struct { int neighbor[3]; char which_vertex_not[3]; } stl_neighbors; typedef struct { int vertex[3]; } v_indices_struct; typedef struct { char header[81]; stl_type type; int number_of_facets; stl_vertex max; stl_vertex min; stl_vertex size; float bounding_diameter; float shortest_edge; float volume; unsigned number_of_blocks; int connected_edges; int connected_facets_1_edge; int connected_facets_2_edge; int connected_facets_3_edge; int facets_w_1_bad_edge; int facets_w_2_bad_edge; int facets_w_3_bad_edge; int original_num_facets; int edges_fixed; int degenerate_facets; int facets_removed; int facets_added; int facets_reversed; int backwards_edges; int normals_fixed; int number_of_parts; int malloced; int freed; int facets_malloced; int collisions; int shared_vertices; int shared_malloced; } stl_stats; typedef struct { FILE *fp; stl_facet *facet_start; stl_edge *edge_start; stl_hash_edge **heads; stl_hash_edge *tail; int M; stl_neighbors *neighbors_start; v_indices_struct *v_indices; stl_vertex *v_shared; stl_stats stats; char error; } stl_file; extern void stl_open(stl_file *stl, char *file); extern void stl_close(stl_file *stl); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); extern void stl_print_edges(stl_file *stl, FILE *file); extern void stl_print_neighbors(stl_file *stl, char *file); extern void stl_put_little_int(FILE *fp, int value_in); extern void stl_put_little_float(FILE *fp, float value_in); extern void stl_write_ascii(stl_file *stl, const char *file, const char *label); extern void stl_write_binary(stl_file *stl, const char *file, const char *label); extern void stl_write_binary_block(stl_file *stl, FILE *fp); extern void stl_check_facets_exact(stl_file *stl); extern void stl_check_facets_nearby(stl_file *stl, float tolerance); extern void stl_remove_unconnected_facets(stl_file *stl); extern void stl_write_vertex(stl_file *stl, int facet, int vertex); extern void stl_write_facet(stl_file *stl, char *label, int facet); extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge); extern void stl_write_neighbor(stl_file *stl, int facet); extern void stl_write_quad_object(stl_file *stl, char *file); extern void stl_verify_neighbors(stl_file *stl); extern void stl_fill_holes(stl_file *stl); extern void stl_fix_normal_directions(stl_file *stl); extern void stl_fix_normal_values(stl_file *stl); extern void stl_reverse_all_facets(stl_file *stl); extern void stl_translate(stl_file *stl, float x, float y, float z); extern void stl_translate_relative(stl_file *stl, float x, float y, float z); extern void stl_scale_versor(stl_file *stl, float versor[3]); extern void stl_scale(stl_file *stl, float factor); extern void stl_rotate_x(stl_file *stl, float angle); extern void stl_rotate_y(stl_file *stl, float angle); extern void stl_rotate_z(stl_file *stl, float angle); extern void stl_mirror_xy(stl_file *stl); extern void stl_mirror_yz(stl_file *stl); extern void stl_mirror_xz(stl_file *stl); extern void stl_open_merge(stl_file *stl, char *file); extern void stl_invalidate_shared_vertices(stl_file *stl); extern void stl_generate_shared_vertices(stl_file *stl); extern void stl_write_obj(stl_file *stl, char *file); extern void stl_write_off(stl_file *stl, char *file); extern void stl_write_dxf(stl_file *stl, char *file, char *label); extern void stl_write_vrml(stl_file *stl, char *file); extern void stl_calculate_normal(float normal[], stl_facet *facet); extern void stl_normalize_vector(float v[]); extern void stl_calculate_volume(stl_file *stl); extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); extern void stl_initialize(stl_file *stl); extern void stl_count_facets(stl_file *stl, char *file); extern void stl_allocate(stl_file *stl); extern void stl_read(stl_file *stl, int first_facet, int first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first); extern void stl_reallocate(stl_file *stl); extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern void stl_get_size(stl_file *stl); extern void stl_clear_error(stl_file *stl); extern int stl_get_error(stl_file *stl); extern void stl_exit_on_error(stl_file *stl); #ifdef __cplusplus } #endif #endif Slic3r-1.2.9/xs/src/admesh/stl_io.c000066400000000000000000000355511254023100400170010ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include "stl.h" #include "config.h" #if !defined(SEEK_SET) #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 #endif void stl_print_edges(stl_file *stl, FILE *file) { int i; int edges_allocated; if (stl->error) return; edges_allocated = stl->stats.number_of_facets * 3; for(i = 0; i < edges_allocated; i++) { fprintf(file, "%d, %f, %f, %f, %f, %f, %f\n", stl->edge_start[i].facet_number, stl->edge_start[i].p1.x, stl->edge_start[i].p1.y, stl->edge_start[i].p1.z, stl->edge_start[i].p2.x, stl->edge_start[i].p2.y, stl->edge_start[i].p2.z); } } void stl_stats_out(stl_file *stl, FILE *file, char *input_file) { if (stl->error) return; /* this is here for Slic3r, without our config.h it won't use this part of the code anyway */ #ifndef VERSION #define VERSION "unknown" #endif fprintf(file, "\n\ ================= Results produced by ADMesh version " VERSION " ================\n"); fprintf(file, "\ Input file : %s\n", input_file); if(stl->stats.type == binary) { fprintf(file, "\ File type : Binary STL file\n"); } else { fprintf(file, "\ File type : ASCII STL file\n"); } fprintf(file, "\ Header : %s\n", stl->stats.header); fprintf(file, "============== Size ==============\n"); fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min.x, stl->stats.max.x); fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min.y, stl->stats.max.y); fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min.z, stl->stats.max.z); fprintf(file, "\ ========= Facet Status ========== Original ============ Final ====\n"); fprintf(file, "\ Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets); fprintf(file, "\ Facets with 1 disconnected edge : %5d %5d\n", stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); fprintf(file, "\ Facets with 2 disconnected edges : %5d %5d\n", stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); fprintf(file, "\ Facets with 3 disconnected edges : %5d %5d\n", stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); fprintf(file, "\ Total disconnected facets : %5d %5d\n", stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge); fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n"); fprintf(file, "\ Number of parts : %5d Volume : % f\n", stl->stats.number_of_parts, stl->stats.volume); fprintf(file, "\ Degenerate facets : %5d\n", stl->stats.degenerate_facets); fprintf(file, "\ Edges fixed : %5d\n", stl->stats.edges_fixed); fprintf(file, "\ Facets removed : %5d\n", stl->stats.facets_removed); fprintf(file, "\ Facets added : %5d\n", stl->stats.facets_added); fprintf(file, "\ Facets reversed : %5d\n", stl->stats.facets_reversed); fprintf(file, "\ Backwards edges : %5d\n", stl->stats.backwards_edges); fprintf(file, "\ Normals fixed : %5d\n", stl->stats.normals_fixed); } void stl_write_ascii(stl_file *stl, const char *file, const char *label) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "solid %s\n", label); for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal.x, stl->facet_start[i].normal.y, stl->facet_start[i].normal.z); fprintf(fp, " outer loop\n"); fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, stl->facet_start[i].vertex[0].z); fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, stl->facet_start[i].vertex[1].z); fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z); fprintf(fp, " endloop\n"); fprintf(fp, " endfacet\n"); } fprintf(fp, "endsolid %s\n", label); fclose(fp); } void stl_print_neighbors(stl_file *stl, char *file) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_print_neighbors: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", i, stl->neighbors_start[i].neighbor[0], (int)stl->neighbors_start[i].which_vertex_not[0], stl->neighbors_start[i].neighbor[1], (int)stl->neighbors_start[i].which_vertex_not[1], stl->neighbors_start[i].neighbor[2], (int)stl->neighbors_start[i].which_vertex_not[2]); } fclose(fp); } void stl_put_little_int(FILE *fp, int value_in) { int new_value; union { int int_value; char char_value[4]; } value; value.int_value = value_in; new_value = value.char_value[0] & 0xFF; new_value |= (value.char_value[1] & 0xFF) << 0x08; new_value |= (value.char_value[2] & 0xFF) << 0x10; new_value |= (value.char_value[3] & 0xFF) << 0x18; fwrite(&new_value, sizeof(int), 1, fp); } void stl_put_little_float(FILE *fp, float value_in) { int new_value; union { float float_value; char char_value[4]; } value; value.float_value = value_in; new_value = value.char_value[0] & 0xFF; new_value |= (value.char_value[1] & 0xFF) << 0x08; new_value |= (value.char_value[2] & 0xFF) << 0x10; new_value |= (value.char_value[3] & 0xFF) << 0x18; fwrite(&new_value, sizeof(int), 1, fp); } void stl_write_binary_block(stl_file *stl, FILE *fp) { int i; for(i = 0; i < stl->stats.number_of_facets; i++) { stl_put_little_float(fp, stl->facet_start[i].normal.x); stl_put_little_float(fp, stl->facet_start[i].normal.y); stl_put_little_float(fp, stl->facet_start[i].normal.z); stl_put_little_float(fp, stl->facet_start[i].vertex[0].x); stl_put_little_float(fp, stl->facet_start[i].vertex[0].y); stl_put_little_float(fp, stl->facet_start[i].vertex[0].z); stl_put_little_float(fp, stl->facet_start[i].vertex[1].x); stl_put_little_float(fp, stl->facet_start[i].vertex[1].y); stl_put_little_float(fp, stl->facet_start[i].vertex[1].z); stl_put_little_float(fp, stl->facet_start[i].vertex[2].x); stl_put_little_float(fp, stl->facet_start[i].vertex[2].y); stl_put_little_float(fp, stl->facet_start[i].vertex[2].z); fputc(stl->facet_start[i].extra[0], fp); fputc(stl->facet_start[i].extra[1], fp); } } void stl_write_binary(stl_file *stl, const char *file, const char *label) { FILE *fp; int i; char *error_msg; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "%s", label); for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); fseek(fp, LABEL_SIZE, SEEK_SET); stl_put_little_int(fp, stl->stats.number_of_facets); stl_write_binary_block(stl, fp); fclose(fp); } void stl_write_vertex(stl_file *stl, int facet, int vertex) { if (stl->error) return; printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, stl->facet_start[facet].vertex[vertex].x, stl->facet_start[facet].vertex[vertex].y, stl->facet_start[facet].vertex[vertex].z); } void stl_write_facet(stl_file *stl, char *label, int facet) { if (stl->error) return; printf("facet (%d)/ %s\n", facet, label); stl_write_vertex(stl, facet, 0); stl_write_vertex(stl, facet, 1); stl_write_vertex(stl, facet, 2); } void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge) { if (stl->error) return; printf("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label); if(edge.which_edge < 3) { stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); } else { stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); } } void stl_write_neighbor(stl_file *stl, int facet) { if (stl->error) return; printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, stl->neighbors_start[facet].neighbor[0], stl->neighbors_start[facet].neighbor[1], stl->neighbors_start[facet].neighbor[2], stl->neighbors_start[facet].which_vertex_not[0], stl->neighbors_start[facet].which_vertex_not[1], stl->neighbors_start[facet].which_vertex_not[2]); } void stl_write_quad_object(stl_file *stl, char *file) { FILE *fp; int i; int j; char *error_msg; stl_vertex connect_color; stl_vertex uncon_1_color; stl_vertex uncon_2_color; stl_vertex uncon_3_color; stl_vertex color; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } connect_color.x = 0.0; connect_color.y = 0.0; connect_color.z = 1.0; uncon_1_color.x = 0.0; uncon_1_color.y = 1.0; uncon_1_color.z = 0.0; uncon_2_color.x = 1.0; uncon_2_color.y = 1.0; uncon_2_color.z = 1.0; uncon_3_color.x = 1.0; uncon_3_color.y = 0.0; uncon_3_color.z = 0.0; fprintf(fp, "CQUAD\n"); for(i = 0; i < stl->stats.number_of_facets; i++) { j = ((stl->neighbors_start[i].neighbor[0] == -1) + (stl->neighbors_start[i].neighbor[1] == -1) + (stl->neighbors_start[i].neighbor[2] == -1)); if(j == 0) { color = connect_color; } else if(j == 1) { color = uncon_1_color; } else if(j == 2) { color = uncon_2_color; } else { color = uncon_3_color; } fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, stl->facet_start[i].vertex[0].z, color.x, color.y, color.z); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, stl->facet_start[i].vertex[1].z, color.x, color.y, color.z); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z, color.x, color.y, color.z); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z, color.x, color.y, color.z); } fclose(fp); } void stl_write_dxf(stl_file *stl, char *file, char *label) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "999\n%s\n", label); fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ 0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "0\n3DFACE\n8\n0\n"); fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, stl->facet_start[i].vertex[0].z); fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, stl->facet_start[i].vertex[1].z); fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z); fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z); } fprintf(fp, "0\nENDSEC\n0\nEOF\n"); fclose(fp); } void stl_clear_error(stl_file *stl) { stl->error = 0; } void stl_exit_on_error(stl_file *stl) { if (!stl->error) return; stl->error = 0; stl_close(stl); exit(1); } int stl_get_error(stl_file *stl) { return stl->error; } Slic3r-1.2.9/xs/src/admesh/stlinit.c000066400000000000000000000324751254023100400172000ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" #if !defined(SEEK_SET) #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 #endif void stl_open(stl_file *stl, char *file) { stl_initialize(stl); stl_count_facets(stl, file); stl_allocate(stl); stl_read(stl, 0, 1); if (!stl->error) fclose(stl->fp); } void stl_initialize(stl_file *stl) { stl->error = 0; stl->stats.degenerate_facets = 0; stl->stats.edges_fixed = 0; stl->stats.facets_added = 0; stl->stats.facets_removed = 0; stl->stats.facets_reversed = 0; stl->stats.normals_fixed = 0; stl->stats.number_of_parts = 0; stl->stats.original_num_facets = 0; stl->stats.number_of_facets = 0; stl->stats.facets_malloced = 0; stl->stats.volume = -1.0; stl->neighbors_start = NULL; stl->facet_start = NULL; stl->v_indices = NULL; stl->v_shared = NULL; } void stl_count_facets(stl_file *stl, char *file) { long file_size; int header_num_facets; int num_facets; int i, j; size_t s; unsigned char chtest[128]; int num_lines = 1; char *error_msg; if (stl->error) return; /* Open the file in binary mode first */ stl->fp = fopen(file, "rb"); if(stl->fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); stl->error = 1; return; } /* Find size of file */ fseek(stl->fp, 0, SEEK_END); file_size = ftell(stl->fp); /* Check for binary or ASCII file */ fseek(stl->fp, HEADER_SIZE, SEEK_SET); if (!fread(chtest, sizeof(chtest), 1, stl->fp)) { perror("The input is an empty file"); stl->error = 1; return; } stl->stats.type = ascii; for(s = 0; s < sizeof(chtest); s++) { if(chtest[s] > 127) { stl->stats.type = binary; break; } } rewind(stl->fp); /* Get the header and the number of facets in the .STL file */ /* If the .STL file is binary, then do the following */ if(stl->stats.type == binary) { /* Test if the STL file has the right size */ if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { fprintf(stderr, "The file %s has the wrong size.\n", file); stl->error = 1; return; } num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; /* Read the header */ if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) { stl->stats.header[80] = '\0'; } /* Read the int following the header. This should contain # of facets */ if((!fread(&header_num_facets, sizeof(int), 1, stl->fp)) || (num_facets != header_num_facets)) { fprintf(stderr, "Warning: File size doesn't match number of facets in the header\n"); } } /* Otherwise, if the .STL file is ASCII, then do the following */ else { /* Reopen the file in text mode (for getting correct newlines on Windows) */ // fix to silence a warning about unused return value. // obviously if it fails we have problems.... stl->fp = freopen(file, "r", stl->fp); // do another null check to be safe if(stl->fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); stl->error = 1; return; } /* Find the number of facets */ char linebuf[100]; while (fgets(linebuf, 100, stl->fp) != NULL) { /* don't count short lines */ if (strlen(linebuf) <= 4) continue; /* skip solid/endsolid lines as broken STL file generators may put several of them */ if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; ++num_lines; } rewind(stl->fp); /* Get the header */ for(i = 0; (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); stl->stats.header[i] = '\0'; /* Lose the '\n' */ stl->stats.header[80] = '\0'; num_facets = num_lines / ASCII_LINES_PER_FACET; } stl->stats.number_of_facets += num_facets; stl->stats.original_num_facets = stl->stats.number_of_facets; } void stl_allocate(stl_file *stl) { if (stl->error) return; /* Allocate memory for the entire .STL file */ stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets, sizeof(stl_facet)); if(stl->facet_start == NULL) perror("stl_initialize"); stl->stats.facets_malloced = stl->stats.number_of_facets; /* Allocate memory for the neighbors list */ stl->neighbors_start = (stl_neighbors*) calloc(stl->stats.number_of_facets, sizeof(stl_neighbors)); if(stl->facet_start == NULL) perror("stl_initialize"); } void stl_open_merge(stl_file *stl, char *file_to_merge) { int num_facets_so_far; stl_type origStlType; FILE *origFp; stl_file stl_to_merge; if (stl->error) return; /* Record how many facets we have so far from the first file. We will start putting facets in the next position. Since we're 0-indexed, it'l be the same position. */ num_facets_so_far = stl->stats.number_of_facets; /* Record the file type we started with: */ origStlType=stl->stats.type; /* Record the file pointer too: */ origFp=stl->fp; /* Initialize the sturucture with zero stats, header info and sizes: */ stl_initialize(&stl_to_merge); stl_count_facets(&stl_to_merge, file_to_merge); /* Copy what we need to into stl so that we can read the file_to_merge directly into it using stl_read: Save the rest of the valuable info: */ stl->stats.type=stl_to_merge.stats.type; stl->fp=stl_to_merge.fp; /* Add the number of facets we already have in stl with what we we found in stl_to_merge but haven't read yet. */ stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets; /* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */ stl_reallocate(stl); /* Read the file to merge directly into stl, adding it to what we have already. Start at num_facets_so_far, the index to the first unused facet. Also say that this isn't our first time so we should augment stats like min and max instead of erasing them. */ stl_read(stl, num_facets_so_far, 0); /* Restore the stl information we overwrote (for stl_read) so that it still accurately reflects the subject part: */ stl->stats.type=origStlType; stl->fp=origFp; } extern void stl_reallocate(stl_file *stl) { if (stl->error) return; /* Reallocate more memory for the .STL file(s) */ stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets * sizeof(stl_facet)); if(stl->facet_start == NULL) perror("stl_initialize"); stl->stats.facets_malloced = stl->stats.number_of_facets; /* Reallocate more memory for the neighbors list */ stl->neighbors_start = (stl_neighbors*) realloc(stl->neighbors_start, stl->stats.number_of_facets * sizeof(stl_neighbors)); if(stl->facet_start == NULL) perror("stl_initialize"); } /* Reads the contents of the file pointed to by stl->fp into the stl structure, starting at facet first_facet. The second argument says if it's our first time running this for the stl and therefore we should reset our max and min stats. */ void stl_read(stl_file *stl, int first_facet, int first) { stl_facet facet; int i; if (stl->error) return; if(stl->stats.type == binary) { fseek(stl->fp, HEADER_SIZE, SEEK_SET); } else { rewind(stl->fp); } for(i = first_facet; i < stl->stats.number_of_facets; i++) { if(stl->stats.type == binary) /* Read a single facet from a binary .STL file */ { /* we assume little-endian architecture! */ if (fread(&facet.normal, sizeof(stl_normal), 1, stl->fp) \ + fread(&facet.vertex, sizeof(stl_vertex), 3, stl->fp) \ + fread(&facet.extra, sizeof(char), 2, stl->fp) != 6) { perror("Cannot read facet"); stl->error = 1; return; } } else /* Read a single facet from an ASCII .STL file */ { // skip solid/endsolid // (in this order, otherwise it won't work when they are paired in the middle of a file) fscanf(stl->fp, "endsolid\n"); fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") if((fscanf(stl->fp, " facet normal %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \ fscanf(stl->fp, " outer loop\n") + \ fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + \ fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + \ fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + \ fscanf(stl->fp, " endloop\n") + \ fscanf(stl->fp, " endfacet\n")) != 12) { perror("Something is syntactically very wrong with this ASCII STL!"); stl->error = 1; return; } } /* Write the facet into memory. */ stl->facet_start[i] = facet; stl_facet_stats(stl, facet, first); first = 0; } stl->stats.size.x = stl->stats.max.x - stl->stats.min.x; stl->stats.size.y = stl->stats.max.y - stl->stats.min.y; stl->stats.size.z = stl->stats.max.z - stl->stats.min.z; stl->stats.bounding_diameter = sqrt( stl->stats.size.x * stl->stats.size.x + stl->stats.size.y * stl->stats.size.y + stl->stats.size.z * stl->stats.size.z ); } void stl_facet_stats(stl_file *stl, stl_facet facet, int first) { float diff_x; float diff_y; float diff_z; float max_diff; if (stl->error) return; /* while we are going through all of the facets, let's find the */ /* maximum and minimum values for x, y, and z */ /* Initialize the max and min values the first time through*/ if (first) { stl->stats.max.x = facet.vertex[0].x; stl->stats.min.x = facet.vertex[0].x; stl->stats.max.y = facet.vertex[0].y; stl->stats.min.y = facet.vertex[0].y; stl->stats.max.z = facet.vertex[0].z; stl->stats.min.z = facet.vertex[0].z; diff_x = ABS(facet.vertex[0].x - facet.vertex[1].x); diff_y = ABS(facet.vertex[0].y - facet.vertex[1].y); diff_z = ABS(facet.vertex[0].z - facet.vertex[1].z); max_diff = STL_MAX(diff_x, diff_y); max_diff = STL_MAX(diff_z, max_diff); stl->stats.shortest_edge = max_diff; first = 0; } /* now find the max and min values */ stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[0].x); stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[0].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[0].y); stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[0].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[0].z); stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[0].z); stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[1].x); stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[1].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[1].y); stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[1].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[1].z); stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[1].z); stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[2].x); stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[2].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[2].y); stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[2].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[2].z); stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[2].z); } void stl_close(stl_file *stl) { if (stl->error) return; if(stl->neighbors_start != NULL) free(stl->neighbors_start); if(stl->facet_start != NULL) free(stl->facet_start); if(stl->v_indices != NULL) free(stl->v_indices); if(stl->v_shared != NULL) free(stl->v_shared); } Slic3r-1.2.9/xs/src/admesh/util.c000066400000000000000000000370361254023100400164650ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" static void stl_rotate(float *x, float *y, float angle); static float get_area(stl_facet *facet); static float get_volume(stl_file *stl); void stl_verify_neighbors(stl_file *stl) { int i; int j; stl_edge edge_a; stl_edge edge_b; int neighbor; int vnot; if (stl->error) return; stl->stats.backwards_edges = 0; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { edge_a.p1 = stl->facet_start[i].vertex[j]; edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; neighbor = stl->neighbors_start[i].neighbor[j]; vnot = stl->neighbors_start[i].which_vertex_not[j]; if(neighbor == -1) continue; /* this edge has no neighbor... Continue. */ if(vnot < 3) { edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; } else { stl->stats.backwards_edges += 1; edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; } if(memcmp(&edge_a, &edge_b, SIZEOF_EDGE_SORT) != 0) { /* These edges should match but they don't. Print results. */ printf("edge %d of facet %d doesn't match edge %d of facet %d\n", j, i, vnot + 1, neighbor); stl_write_facet(stl, (char*)"first facet", i); stl_write_facet(stl, (char*)"second facet", neighbor); } } } } void stl_translate(stl_file *stl, float x, float y, float z) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x -= (stl->stats.min.x - x); stl->facet_start[i].vertex[j].y -= (stl->stats.min.y - y); stl->facet_start[i].vertex[j].z -= (stl->stats.min.z - z); } } stl->stats.max.x -= (stl->stats.min.x - x); stl->stats.max.y -= (stl->stats.min.y - y); stl->stats.max.z -= (stl->stats.min.z - z); stl->stats.min.x = x; stl->stats.min.y = y; stl->stats.min.z = z; stl_invalidate_shared_vertices(stl); } /* Translates the stl by x,y,z, relatively from wherever it is currently */ void stl_translate_relative(stl_file *stl, float x, float y, float z) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x += x; stl->facet_start[i].vertex[j].y += y; stl->facet_start[i].vertex[j].z += z; } } stl->stats.min.x += x; stl->stats.min.y += y; stl->stats.min.z += z; stl->stats.max.x += x; stl->stats.max.y += y; stl->stats.max.z += z; stl_invalidate_shared_vertices(stl); } void stl_scale_versor(stl_file *stl, float versor[3]) { int i; int j; if (stl->error) return; /* scale extents */ stl->stats.min.x *= versor[0]; stl->stats.min.y *= versor[1]; stl->stats.min.z *= versor[2]; stl->stats.max.x *= versor[0]; stl->stats.max.y *= versor[1]; stl->stats.max.z *= versor[2]; /* scale size */ stl->stats.size.x *= versor[0]; stl->stats.size.y *= versor[1]; stl->stats.size.z *= versor[2]; /* scale volume */ if (stl->stats.volume > 0.0) { stl->stats.volume *= (versor[0] * versor[1] * versor[2]); } for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x *= versor[0]; stl->facet_start[i].vertex[j].y *= versor[1]; stl->facet_start[i].vertex[j].z *= versor[2]; } } stl_invalidate_shared_vertices(stl); } void stl_scale(stl_file *stl, float factor) { float versor[3]; if (stl->error) return; versor[0] = factor; versor[1] = factor; versor[2] = factor; stl_scale_versor(stl, versor); } static void calculate_normals(stl_file *stl) { long i; float normal[3]; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); stl->facet_start[i].normal.x = normal[0]; stl->facet_start[i].normal.y = normal[1]; stl->facet_start[i].normal.z = normal[2]; } } void stl_rotate_x(stl_file *stl, float angle) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].y, &stl->facet_start[i].vertex[j].z, angle); } } stl_get_size(stl); calculate_normals(stl); } void stl_rotate_y(stl_file *stl, float angle) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].z, &stl->facet_start[i].vertex[j].x, angle); } } stl_get_size(stl); calculate_normals(stl); } void stl_rotate_z(stl_file *stl, float angle) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].x, &stl->facet_start[i].vertex[j].y, angle); } } stl_get_size(stl); calculate_normals(stl); } static void stl_rotate(float *x, float *y, float angle) { double r; double theta; double radian_angle; radian_angle = (angle / 180.0) * M_PI; r = sqrt((*x **x) + (*y **y)); theta = atan2(*y, *x); *x = r * cos(theta + radian_angle); *y = r * sin(theta + radian_angle); } extern void stl_get_size(stl_file *stl) { int i; int j; if (stl->error) return; if (stl->stats.number_of_facets == 0) return; stl->stats.min.x = stl->facet_start[0].vertex[0].x; stl->stats.min.y = stl->facet_start[0].vertex[0].y; stl->stats.min.z = stl->facet_start[0].vertex[0].z; stl->stats.max.x = stl->facet_start[0].vertex[0].x; stl->stats.max.y = stl->facet_start[0].vertex[0].y; stl->stats.max.z = stl->facet_start[0].vertex[0].z; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->stats.min.x = STL_MIN(stl->stats.min.x, stl->facet_start[i].vertex[j].x); stl->stats.min.y = STL_MIN(stl->stats.min.y, stl->facet_start[i].vertex[j].y); stl->stats.min.z = STL_MIN(stl->stats.min.z, stl->facet_start[i].vertex[j].z); stl->stats.max.x = STL_MAX(stl->stats.max.x, stl->facet_start[i].vertex[j].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, stl->facet_start[i].vertex[j].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, stl->facet_start[i].vertex[j].z); } } stl->stats.size.x = stl->stats.max.x - stl->stats.min.x; stl->stats.size.y = stl->stats.max.y - stl->stats.min.y; stl->stats.size.z = stl->stats.max.z - stl->stats.min.z; stl->stats.bounding_diameter = sqrt( stl->stats.size.x * stl->stats.size.x + stl->stats.size.y * stl->stats.size.y + stl->stats.size.z * stl->stats.size.z ); } void stl_mirror_xy(stl_file *stl) { int i; int j; float temp_size; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].z *= -1.0; } } temp_size = stl->stats.min.z; stl->stats.min.z = stl->stats.max.z; stl->stats.max.z = temp_size; stl->stats.min.z *= -1.0; stl->stats.max.z *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_yz(stl_file *stl) { int i; int j; float temp_size; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x *= -1.0; } } temp_size = stl->stats.min.x; stl->stats.min.x = stl->stats.max.x; stl->stats.max.x = temp_size; stl->stats.min.x *= -1.0; stl->stats.max.x *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_xz(stl_file *stl) { int i; int j; float temp_size; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].y *= -1.0; } } temp_size = stl->stats.min.y; stl->stats.min.y = stl->stats.max.y; stl->stats.max.y = temp_size; stl->stats.min.y *= -1.0; stl->stats.max.y *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } static float get_volume(stl_file *stl) { long i; stl_vertex p0; stl_vertex p; stl_normal n; float height; float area; float volume = 0.0; if (stl->error) return 0; /* Choose a point, any point as the reference */ p0.x = stl->facet_start[0].vertex[0].x; p0.y = stl->facet_start[0].vertex[0].y; p0.z = stl->facet_start[0].vertex[0].z; for(i = 0; i < stl->stats.number_of_facets; i++) { p.x = stl->facet_start[i].vertex[0].x - p0.x; p.y = stl->facet_start[i].vertex[0].y - p0.y; p.z = stl->facet_start[i].vertex[0].z - p0.z; /* Do dot product to get distance from point to plane */ n = stl->facet_start[i].normal; height = (n.x * p.x) + (n.y * p.y) + (n.z * p.z); area = get_area(&stl->facet_start[i]); volume += (area * height) / 3.0; } return volume; } void stl_calculate_volume(stl_file *stl) { if (stl->error) return; stl->stats.volume = get_volume(stl); if(stl->stats.volume < 0.0) { stl_reverse_all_facets(stl); stl->stats.volume = -stl->stats.volume; } } static float get_area(stl_facet *facet) { double cross[3][3]; float sum[3]; float n[3]; float area; int i; /* cast to double before calculating cross product because large coordinates can result in overflowing product (bad area is responsible for bad volume and bad facets reversal) */ for(i = 0; i < 3; i++) { cross[i][0]=(((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].z) - ((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].y)); cross[i][1]=(((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].x) - ((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].z)); cross[i][2]=(((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].y) - ((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].x)); } sum[0] = cross[0][0] + cross[1][0] + cross[2][0]; sum[1] = cross[0][1] + cross[1][1] + cross[2][1]; sum[2] = cross[0][2] + cross[1][2] + cross[2][2]; /* This should already be done. But just in case, let's do it again */ stl_calculate_normal(n, facet); stl_normalize_vector(n); area = 0.5 * (n[0] * sum[0] + n[1] * sum[1] + n[2] * sum[2]); return area; } void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag) { int i; int last_edges_fixed = 0; if (stl->error) return; if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { if (verbose_flag) printf("Checking exact...\n"); exact_flag = 1; stl_check_facets_exact(stl); stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); } if(nearby_flag || fixall_flag) { if(!tolerance_flag) { tolerance = stl->stats.shortest_edge; } if(!increment_flag) { increment = stl->stats.bounding_diameter / 10000.0; } if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { for(i = 0; i < iterations; i++) { if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { if (verbose_flag) printf("\ Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(stl, tolerance); if (verbose_flag) printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed); last_edges_fixed = stl->stats.edges_fixed; tolerance += increment; } else { if (verbose_flag) printf("\ All facets connected. No further nearby check necessary.\n"); break; } } } else { if (verbose_flag) printf("All facets connected. No nearby check necessary.\n"); } } if(remove_unconnected_flag || fixall_flag || fill_holes_flag) { if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { if (verbose_flag) printf("Removing unconnected facets...\n"); stl_remove_unconnected_facets(stl); } else if (verbose_flag) printf("No unconnected need to be removed.\n"); } if(fill_holes_flag || fixall_flag) { if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { if (verbose_flag) printf("Filling holes...\n"); stl_fill_holes(stl); } else if (verbose_flag) printf("No holes need to be filled.\n"); } if(reverse_all_flag) { if (verbose_flag) printf("Reversing all facets...\n"); stl_reverse_all_facets(stl); } if(normal_directions_flag || fixall_flag) { if (verbose_flag) printf("Checking normal directions...\n"); stl_fix_normal_directions(stl); } if(normal_values_flag || fixall_flag) { if (verbose_flag) printf("Checking normal values...\n"); stl_fix_normal_values(stl); } /* Always calculate the volume. It shouldn't take too long */ if (verbose_flag) printf("Calculating volume...\n"); stl_calculate_volume(stl); if(exact_flag) { if (verbose_flag) printf("Verifying neighbors...\n"); stl_verify_neighbors(stl); } } Slic3r-1.2.9/xs/src/clipper.cpp000066400000000000000000004215041254023100400162420ustar00rootroot00000000000000/******************************************************************************* * * * Author : Angus Johnson * * Version : 6.2.9 * * Date : 16 February 2015 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * * http://www.boost.org/LICENSE_1_0.txt * * * * Attributions: * * The code in this library is an extension of Bala Vatti's clipping algorithm: * * "A generic solution to polygon clipping" * * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * * http://portal.acm.org/citation.cfm?id=129906 * * * * Computer graphics and geometric modeling: implementation and algorithms * * By Max K. Agoston * * Springer; 1 edition (January 4, 2005) * * http://books.google.com/books?q=vatti+clipping+agoston * * * * See also: * * "Polygon Offsetting by Computing Winding Numbers" * * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * * September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ /******************************************************************************* * * * This is a translation of the Delphi Clipper library and the naming style * * used has retained a Delphi flavour. * * * *******************************************************************************/ #include "clipper.hpp" #include #include #include #include #include #include #include #include namespace ClipperLib { static double const pi = 3.141592653589793238; static double const two_pi = pi *2; static double const def_arc_tolerance = 0.25; enum Direction { dRightToLeft, dLeftToRight }; static int const Unassigned = -1; //edge not currently 'owning' a solution static int const Skip = -2; //edge that would otherwise close a path #define HORIZONTAL (-1.0E+40) #define TOLERANCE (1.0e-20) #define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) struct TEdge { IntPoint Bot; IntPoint Curr; IntPoint Top; IntPoint Delta; double Dx; PolyType PolyTyp; EdgeSide Side; int WindDelta; //1 or -1 depending on winding direction int WindCnt; int WindCnt2; //winding count of the opposite polytype int OutIdx; TEdge *Next; TEdge *Prev; TEdge *NextInLML; TEdge *NextInAEL; TEdge *PrevInAEL; TEdge *NextInSEL; TEdge *PrevInSEL; }; struct IntersectNode { TEdge *Edge1; TEdge *Edge2; IntPoint Pt; }; struct LocalMinimum { cInt Y; TEdge *LeftBound; TEdge *RightBound; }; struct OutPt; struct OutRec { int Idx; bool IsHole; bool IsOpen; OutRec *FirstLeft; //see comments in clipper.pas PolyNode *PolyNd; OutPt *Pts; OutPt *BottomPt; }; struct OutPt { int Idx; IntPoint Pt; OutPt *Next; OutPt *Prev; }; struct Join { OutPt *OutPt1; OutPt *OutPt2; IntPoint OffPt; }; struct LocMinSorter { inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) { return locMin2.Y < locMin1.Y; } }; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ inline cInt Round(double val) { if ((val < 0)) return static_cast(val - 0.5); else return static_cast(val + 0.5); } //------------------------------------------------------------------------------ inline cInt Abs(cInt val) { return val < 0 ? -val : val; } //------------------------------------------------------------------------------ // PolyTree methods ... //------------------------------------------------------------------------------ void PolyTree::Clear() { for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) delete AllNodes[i]; AllNodes.resize(0); Childs.resize(0); } //------------------------------------------------------------------------------ PolyNode* PolyTree::GetFirst() const { if (!Childs.empty()) return Childs[0]; else return 0; } //------------------------------------------------------------------------------ int PolyTree::Total() const { int result = (int)AllNodes.size(); //with negative offsets, ignore the hidden outer polygon ... if (result > 0 && Childs[0] != AllNodes[0]) result--; return result; } //------------------------------------------------------------------------------ // PolyNode methods ... //------------------------------------------------------------------------------ PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) { } //------------------------------------------------------------------------------ int PolyNode::ChildCount() const { return (int)Childs.size(); } //------------------------------------------------------------------------------ void PolyNode::AddChild(PolyNode& child) { unsigned cnt = (unsigned)Childs.size(); Childs.push_back(&child); child.Parent = this; child.Index = cnt; } //------------------------------------------------------------------------------ PolyNode* PolyNode::GetNext() const { if (!Childs.empty()) return Childs[0]; else return GetNextSiblingUp(); } //------------------------------------------------------------------------------ PolyNode* PolyNode::GetNextSiblingUp() const { if (!Parent) //protects against PolyTree.GetNextSiblingUp() return 0; else if (Index == Parent->Childs.size() - 1) return Parent->GetNextSiblingUp(); else return Parent->Childs[Index + 1]; } //------------------------------------------------------------------------------ bool PolyNode::IsHole() const { bool result = true; PolyNode* node = Parent; while (node) { result = !result; node = node->Parent; } return result; } //------------------------------------------------------------------------------ bool PolyNode::IsOpen() const { return m_IsOpen; } //------------------------------------------------------------------------------ #ifndef use_int32 //------------------------------------------------------------------------------ // Int128 class (enables safe math on signed 64bit integers) // eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 // Int128 val2((long64)9223372036854775807); // Int128 val3 = val1 * val2; // val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) //------------------------------------------------------------------------------ class Int128 { public: ulong64 lo; long64 hi; Int128(long64 _lo = 0) { lo = (ulong64)_lo; if (_lo < 0) hi = -1; else hi = 0; } Int128(const Int128 &val): lo(val.lo), hi(val.hi){} Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} Int128& operator = (const long64 &val) { lo = (ulong64)val; if (val < 0) hi = -1; else hi = 0; return *this; } bool operator == (const Int128 &val) const {return (hi == val.hi && lo == val.lo);} bool operator != (const Int128 &val) const { return !(*this == val);} bool operator > (const Int128 &val) const { if (hi != val.hi) return hi > val.hi; else return lo > val.lo; } bool operator < (const Int128 &val) const { if (hi != val.hi) return hi < val.hi; else return lo < val.lo; } bool operator >= (const Int128 &val) const { return !(*this < val);} bool operator <= (const Int128 &val) const { return !(*this > val);} Int128& operator += (const Int128 &rhs) { hi += rhs.hi; lo += rhs.lo; if (lo < rhs.lo) hi++; return *this; } Int128 operator + (const Int128 &rhs) const { Int128 result(*this); result+= rhs; return result; } Int128& operator -= (const Int128 &rhs) { *this += -rhs; return *this; } Int128 operator - (const Int128 &rhs) const { Int128 result(*this); result -= rhs; return result; } Int128 operator-() const //unary negation { if (lo == 0) return Int128(-hi, 0); else return Int128(~hi, ~lo + 1); } operator double() const { const double shift64 = 18446744073709551616.0; //2^64 if (hi < 0) { if (lo == 0) return (double)hi * shift64; else return -(double)(~lo + ~hi * shift64); } else return (double)(lo + hi * shift64); } }; //------------------------------------------------------------------------------ Int128 Int128Mul (long64 lhs, long64 rhs) { bool negate = (lhs < 0) != (rhs < 0); if (lhs < 0) lhs = -lhs; ulong64 int1Hi = ulong64(lhs) >> 32; ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); if (rhs < 0) rhs = -rhs; ulong64 int2Hi = ulong64(rhs) >> 32; ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); //nb: see comments in clipper.pas ulong64 a = int1Hi * int2Hi; ulong64 b = int1Lo * int2Lo; ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; Int128 tmp; tmp.hi = long64(a + (c >> 32)); tmp.lo = long64(c << 32); tmp.lo += long64(b); if (tmp.lo < b) tmp.hi++; if (negate) tmp = -tmp; return tmp; }; #endif //------------------------------------------------------------------------------ // Miscellaneous global functions //------------------------------------------------------------------------------ bool Orientation(const Path &poly) { return Area(poly) >= 0; } //------------------------------------------------------------------------------ double Area(const Path &poly) { int size = (int)poly.size(); if (size < 3) return 0; double a = 0; for (int i = 0, j = size -1; i < size; ++i) { a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); j = i; } return -a * 0.5; } //------------------------------------------------------------------------------ double Area(const OutRec &outRec) { OutPt *op = outRec.Pts; if (!op) return 0; double a = 0; do { a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); op = op->Next; } while (op != outRec.Pts); return a * 0.5; } //------------------------------------------------------------------------------ bool PointIsVertex(const IntPoint &Pt, OutPt *pp) { OutPt *pp2 = pp; do { if (pp2->Pt == Pt) return true; pp2 = pp2->Next; } while (pp2 != pp); return false; } //------------------------------------------------------------------------------ //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf int PointInPolygon(const IntPoint &pt, const Path &path) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; size_t cnt = path.size(); if (cnt < 3) return 0; IntPoint ip = path[0]; for(size_t i = 1; i <= cnt; ++i) { IntPoint ipNext = (i == cnt ? path[0] : path[i]); if (ipNext.Y == pt.Y) { if ((ipNext.X == pt.X) || (ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; } if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) { if (ip.X >= pt.X) { if (ipNext.X > pt.X) result = 1 - result; else { double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); if (!d) return -1; if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; } } else { if (ipNext.X > pt.X) { double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); if (!d) return -1; if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; } } } ip = ipNext; } return result; } //------------------------------------------------------------------------------ int PointInPolygon (const IntPoint &pt, OutPt *op) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; OutPt* startOp = op; for(;;) { if (op->Next->Pt.Y == pt.Y) { if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; } if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) { if (op->Pt.X >= pt.X) { if (op->Next->Pt.X > pt.X) result = 1 - result; else { double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); if (!d) return -1; if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; } } else { if (op->Next->Pt.X > pt.X) { double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); if (!d) return -1; if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; } } } op = op->Next; if (startOp == op) break; } return result; } //------------------------------------------------------------------------------ bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) { OutPt* op = OutPt1; do { //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon int res = PointInPolygon(op->Pt, OutPt2); if (res >= 0) return res > 0; op = op->Next; } while (op != OutPt1); return true; } //---------------------------------------------------------------------- bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { #ifndef use_int32 if (UseFullInt64Range) return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y); else #endif return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; } //------------------------------------------------------------------------------ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, bool UseFullInt64Range) { #ifndef use_int32 if (UseFullInt64Range) return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); else #endif return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); } //------------------------------------------------------------------------------ bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) { #ifndef use_int32 if (UseFullInt64Range) return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); else #endif return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); } //------------------------------------------------------------------------------ inline bool IsHorizontal(TEdge &e) { return e.Delta.Y == 0; } //------------------------------------------------------------------------------ inline double GetDx(const IntPoint pt1, const IntPoint pt2) { return (pt1.Y == pt2.Y) ? HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); } //--------------------------------------------------------------------------- inline void SetDx(TEdge &e) { e.Delta.X = (e.Top.X - e.Bot.X); e.Delta.Y = (e.Top.Y - e.Bot.Y); if (e.Delta.Y == 0) e.Dx = HORIZONTAL; else e.Dx = (double)(e.Delta.X) / e.Delta.Y; } //--------------------------------------------------------------------------- inline void SwapSides(TEdge &Edge1, TEdge &Edge2) { EdgeSide Side = Edge1.Side; Edge1.Side = Edge2.Side; Edge2.Side = Side; } //------------------------------------------------------------------------------ inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) { int OutIdx = Edge1.OutIdx; Edge1.OutIdx = Edge2.OutIdx; Edge2.OutIdx = OutIdx; } //------------------------------------------------------------------------------ inline cInt TopX(TEdge &edge, const cInt currentY) { return ( currentY == edge.Top.Y ) ? edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); } //------------------------------------------------------------------------------ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { #ifdef use_xyz ip.Z = 0; #endif double b1, b2; if (Edge1.Dx == Edge2.Dx) { ip.Y = Edge1.Curr.Y; ip.X = TopX(Edge1, ip.Y); return; } else if (Edge1.Delta.X == 0) { ip.X = Edge1.Bot.X; if (IsHorizontal(Edge2)) ip.Y = Edge2.Bot.Y; else { b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); ip.Y = Round(ip.X / Edge2.Dx + b2); } } else if (Edge2.Delta.X == 0) { ip.X = Edge2.Bot.X; if (IsHorizontal(Edge1)) ip.Y = Edge1.Bot.Y; else { b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); ip.Y = Round(ip.X / Edge1.Dx + b1); } } else { b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); ip.Y = Round(q); if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ip.X = Round(Edge1.Dx * q + b1); else ip.X = Round(Edge2.Dx * q + b2); } if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) { if (Edge1.Top.Y > Edge2.Top.Y) ip.Y = Edge1.Top.Y; else ip.Y = Edge2.Top.Y; if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ip.X = TopX(Edge1, ip.Y); else ip.X = TopX(Edge2, ip.Y); } //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... if (ip.Y > Edge1.Curr.Y) { ip.Y = Edge1.Curr.Y; //use the more vertical edge to derive X ... if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) ip.X = TopX(Edge2, ip.Y); else ip.X = TopX(Edge1, ip.Y); } } //------------------------------------------------------------------------------ void ReversePolyPtLinks(OutPt *pp) { if (!pp) return; OutPt *pp1, *pp2; pp1 = pp; do { pp2 = pp1->Next; pp1->Next = pp1->Prev; pp1->Prev = pp2; pp1 = pp2; } while( pp1 != pp ); } //------------------------------------------------------------------------------ void DisposeOutPts(OutPt*& pp) { if (pp == 0) return; pp->Prev->Next = 0; while( pp ) { OutPt *tmpPp = pp; pp = pp->Next; delete tmpPp; } } //------------------------------------------------------------------------------ inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) { std::memset(e, 0, sizeof(TEdge)); e->Next = eNext; e->Prev = ePrev; e->Curr = Pt; e->OutIdx = Unassigned; } //------------------------------------------------------------------------------ void InitEdge2(TEdge& e, PolyType Pt) { if (e.Curr.Y >= e.Next->Curr.Y) { e.Bot = e.Curr; e.Top = e.Next->Curr; } else { e.Top = e.Curr; e.Bot = e.Next->Curr; } SetDx(e); e.PolyTyp = Pt; } //------------------------------------------------------------------------------ TEdge* RemoveEdge(TEdge* e) { //removes e from double_linked_list (but without removing from memory) e->Prev->Next = e->Next; e->Next->Prev = e->Prev; TEdge* result = e->Next; e->Prev = 0; //flag as removed (see ClipperBase.Clear) return result; } //------------------------------------------------------------------------------ inline void ReverseHorizontal(TEdge &e) { //swap horizontal edges' Top and Bottom x's so they follow the natural //progression of the bounds - ie so their xbots will align with the //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] std::swap(e.Top.X, e.Bot.X); #ifdef use_xyz std::swap(e.Top.Z, e.Bot.Z); #endif } //------------------------------------------------------------------------------ void SwapPoints(IntPoint &pt1, IntPoint &pt2) { IntPoint tmp = pt1; pt1 = pt2; pt2 = tmp; } //------------------------------------------------------------------------------ bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { //precondition: segments are Collinear. if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) { if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; return pt1.X < pt2.X; } else { if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; return pt1.Y > pt2.Y; } } //------------------------------------------------------------------------------ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) { OutPt *p = btmPt1->Prev; while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); p = btmPt1->Next; while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); p = btmPt2->Prev; while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); p = btmPt2->Next; while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); } //------------------------------------------------------------------------------ OutPt* GetBottomPt(OutPt *pp) { OutPt* dups = 0; OutPt* p = pp->Next; while (p != pp) { if (p->Pt.Y > pp->Pt.Y) { pp = p; dups = 0; } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) { if (p->Pt.X < pp->Pt.X) { dups = 0; pp = p; } else { if (p->Next != pp && p->Prev != pp) dups = p; } } p = p->Next; } if (dups) { //there appears to be at least 2 vertices at BottomPt so ... while (dups != p) { if (!FirstIsBottomPt(p, dups)) pp = dups; dups = dups->Next; while (dups->Pt != pp->Pt) dups = dups->Next; } } return pp; } //------------------------------------------------------------------------------ bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3) { if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false; else if (pt1.X != pt3.X) return (pt2.X > pt1.X) == (pt2.X < pt3.X); else return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); } //------------------------------------------------------------------------------ bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { if (seg1a > seg1b) std::swap(seg1a, seg1b); if (seg2a > seg2b) std::swap(seg2a, seg2b); return (seg1a < seg2b) && (seg2a < seg1b); } //------------------------------------------------------------------------------ // ClipperBase class methods ... //------------------------------------------------------------------------------ ClipperBase::ClipperBase() //constructor { m_CurrentLM = m_MinimaList.begin(); //begin() == end() here m_UseFullRange = false; } //------------------------------------------------------------------------------ ClipperBase::~ClipperBase() //destructor { Clear(); } //------------------------------------------------------------------------------ void RangeTest(const IntPoint& Pt, bool& useFullRange) { if (useFullRange) { if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) throw "Coordinate outside allowed range"; } else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) { useFullRange = true; RangeTest(Pt, useFullRange); } } //------------------------------------------------------------------------------ TEdge* FindNextLocMin(TEdge* E) { for (;;) { while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; while (IsHorizontal(*E->Prev)) E = E->Prev; TEdge* E2 = E; while (IsHorizontal(*E)) E = E->Next; if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. if (E2->Prev->Bot.X < E->Bot.X) E = E2; break; } return E; } //------------------------------------------------------------------------------ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) { TEdge *Result = E; TEdge *Horz = 0; if (E->OutIdx == Skip) { //if edges still remain in the current bound beyond the skip edge then //create another LocMin and call ProcessBound once more if (NextIsForward) { while (E->Top.Y == E->Next->Bot.Y) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; } else { while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } if (E == Result) { if (NextIsForward) Result = E->Next; else Result = E->Prev; } else { //there are more edges in the bound beyond result starting with E if (NextIsForward) E = Result->Next; else E = Result->Prev; MinimaList::value_type locMin; locMin.Y = E->Bot.Y; locMin.LeftBound = 0; locMin.RightBound = E; E->WindDelta = 0; Result = ProcessBound(E, NextIsForward); m_MinimaList.push_back(locMin); } return Result; } TEdge *EStart; if (IsHorizontal(*E)) { //We need to be careful with open paths because this may not be a //true local minima (ie E may be following a skip edge). //Also, consecutive horz. edges may start heading left before going right. if (NextIsForward) EStart = E->Prev; else EStart = E->Next; if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge { if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) ReverseHorizontal(*E); } else if (EStart->Bot.X != E->Bot.X) ReverseHorizontal(*E); } EStart = E; if (NextIsForward) { while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) Result = Result->Next; if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) { //nb: at the top of a bound, horizontals are added to the bound //only when the preceding edge attaches to the horizontal's left vertex //unless a Skip edge is encountered when that becomes the top divide Horz = Result; while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; } while (E != Result) { E->NextInLML = E->Next; if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); E = E->Next; } if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); Result = Result->Next; //move to the edge just beyond current bound } else { while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) Result = Result->Prev; if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) { Horz = Result; while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; if (Horz->Next->Top.X == Result->Prev->Top.X || Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; } while (E != Result) { E->NextInLML = E->Prev; if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) ReverseHorizontal(*E); E = E->Prev; } if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) ReverseHorizontal(*E); Result = Result->Prev; //move to the edge just beyond current bound } return Result; } //------------------------------------------------------------------------------ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { #ifdef use_lines if (!Closed && PolyTyp == ptClip) throw clipperException("AddPath: Open paths must be subject."); #else if (!Closed) throw clipperException("AddPath: Open paths have been disabled."); #endif int highI = (int)pg.size() -1; if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; //create a new edge array ... TEdge *edges = new TEdge [highI +1]; bool IsFlat = true; //1. Basic (first) edge initialization ... try { edges[1].Curr = pg[1]; RangeTest(pg[0], m_UseFullRange); RangeTest(pg[highI], m_UseFullRange); InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); for (int i = highI - 1; i >= 1; --i) { RangeTest(pg[i], m_UseFullRange); InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); } } catch(...) { delete [] edges; throw; //range test fails } TEdge *eStart = &edges[0]; //2. Remove duplicate vertices, and (when closed) collinear edges ... TEdge *E = eStart, *eLoopStop = eStart; for (;;) { //nb: allows matching start and end points when not Closed ... if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) { if (E == E->Next) break; if (E == eStart) eStart = E->Next; E = RemoveEdge(E); eLoopStop = E; continue; } if (E->Prev == E->Next) break; //only two vertices else if (Closed && SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && (!m_PreserveCollinear || !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) { //Collinear edges are allowed for open paths but in closed paths //the default is to merge adjacent collinear edges into a single edge. //However, if the PreserveCollinear property is enabled, only overlapping //collinear edges (ie spikes) will be removed from closed paths. if (E == eStart) eStart = E->Next; E = RemoveEdge(E); E = E->Prev; eLoopStop = E; continue; } E = E->Next; if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; } if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) { delete [] edges; return false; } if (!Closed) { m_HasOpenPaths = true; eStart->Prev->OutIdx = Skip; } //3. Do second stage of edge initialization ... E = eStart; do { InitEdge2(*E, PolyTyp); E = E->Next; if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; } while (E != eStart); //4. Finally, add edge bounds to LocalMinima list ... //Totally flat paths must be handled differently when adding them //to LocalMinima list to avoid endless loops etc ... if (IsFlat) { if (Closed) { delete [] edges; return false; } E->Prev->OutIdx = Skip; MinimaList::value_type locMin; locMin.Y = E->Bot.Y; locMin.LeftBound = 0; locMin.RightBound = E; locMin.RightBound->Side = esRight; locMin.RightBound->WindDelta = 0; for (;;) { if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); if (E->Next->OutIdx == Skip) break; E->NextInLML = E->Next; E = E->Next; } m_MinimaList.push_back(locMin); m_edges.push_back(edges); return true; } m_edges.push_back(edges); bool leftBoundIsForward; TEdge* EMin = 0; //workaround to avoid an endless loop in the while loop below when //open paths have matching start and end points ... if (E->Prev->Bot == E->Prev->Top) E = E->Next; for (;;) { E = FindNextLocMin(E); if (E == EMin) break; else if (!EMin) EMin = E; //E and E.Prev now share a local minima (left aligned if horizontal). //Compare their slopes to find which starts which bound ... MinimaList::value_type locMin; locMin.Y = E->Bot.Y; if (E->Dx < E->Prev->Dx) { locMin.LeftBound = E->Prev; locMin.RightBound = E; leftBoundIsForward = false; //Q.nextInLML = Q.prev } else { locMin.LeftBound = E; locMin.RightBound = E->Prev; leftBoundIsForward = true; //Q.nextInLML = Q.next } locMin.LeftBound->Side = esLeft; locMin.RightBound->Side = esRight; if (!Closed) locMin.LeftBound->WindDelta = 0; else if (locMin.LeftBound->Next == locMin.RightBound) locMin.LeftBound->WindDelta = -1; else locMin.LeftBound->WindDelta = 1; locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; E = ProcessBound(locMin.LeftBound, leftBoundIsForward); if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); if (locMin.LeftBound->OutIdx == Skip) locMin.LeftBound = 0; else if (locMin.RightBound->OutIdx == Skip) locMin.RightBound = 0; m_MinimaList.push_back(locMin); if (!leftBoundIsForward) E = E2; } return true; } //------------------------------------------------------------------------------ bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) { bool result = false; for (Paths::size_type i = 0; i < ppg.size(); ++i) if (AddPath(ppg[i], PolyTyp, Closed)) result = true; return result; } //------------------------------------------------------------------------------ void ClipperBase::Clear() { DisposeLocalMinimaList(); for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) { //for each edge array in turn, find the first used edge and //check for and remove any hiddenPts in each edge in the array. TEdge* edges = m_edges[i]; delete [] edges; } m_edges.clear(); m_UseFullRange = false; m_HasOpenPaths = false; } //------------------------------------------------------------------------------ void ClipperBase::Reset() { m_CurrentLM = m_MinimaList.begin(); if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); //reset all edges ... for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) { TEdge* e = lm->LeftBound; if (e) { e->Curr = e->Bot; e->Side = esLeft; e->OutIdx = Unassigned; } e = lm->RightBound; if (e) { e->Curr = e->Bot; e->Side = esRight; e->OutIdx = Unassigned; } } } //------------------------------------------------------------------------------ void ClipperBase::DisposeLocalMinimaList() { m_MinimaList.clear(); m_CurrentLM = m_MinimaList.begin(); } //------------------------------------------------------------------------------ void ClipperBase::PopLocalMinima() { if (m_CurrentLM == m_MinimaList.end()) return; ++m_CurrentLM; } //------------------------------------------------------------------------------ IntRect ClipperBase::GetBounds() { IntRect result; MinimaList::iterator lm = m_MinimaList.begin(); if (lm == m_MinimaList.end()) { result.left = result.top = result.right = result.bottom = 0; return result; } result.left = lm->LeftBound->Bot.X; result.top = lm->LeftBound->Bot.Y; result.right = lm->LeftBound->Bot.X; result.bottom = lm->LeftBound->Bot.Y; while (lm != m_MinimaList.end()) { result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); TEdge* e = lm->LeftBound; for (;;) { TEdge* bottomE = e; while (e->NextInLML) { if (e->Bot.X < result.left) result.left = e->Bot.X; if (e->Bot.X > result.right) result.right = e->Bot.X; e = e->NextInLML; } result.left = std::min(result.left, e->Bot.X); result.right = std::max(result.right, e->Bot.X); result.left = std::min(result.left, e->Top.X); result.right = std::max(result.right, e->Top.X); result.top = std::min(result.top, e->Top.Y); if (bottomE == lm->LeftBound) e = lm->RightBound; else break; } ++lm; } return result; } //------------------------------------------------------------------------------ // TClipper methods ... //------------------------------------------------------------------------------ Clipper::Clipper(int initOptions) : ClipperBase() //constructor { m_ActiveEdges = 0; m_SortedEdges = 0; m_ExecuteLocked = false; m_UseFullRange = false; m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); m_HasOpenPaths = false; #ifdef use_xyz m_ZFill = 0; #endif } //------------------------------------------------------------------------------ Clipper::~Clipper() //destructor { Clear(); } //------------------------------------------------------------------------------ #ifdef use_xyz void Clipper::ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; } //------------------------------------------------------------------------------ #endif void Clipper::Reset() { ClipperBase::Reset(); m_Scanbeam = ScanbeamList(); m_Maxima = MaximaList(); m_ActiveEdges = 0; m_SortedEdges = 0; for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) InsertScanbeam(lm->Y); } //------------------------------------------------------------------------------ bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) { return Execute(clipType, solution, fillType, fillType); } //------------------------------------------------------------------------------ bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) { return Execute(clipType, polytree, fillType, fillType); } //------------------------------------------------------------------------------ bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, PolyFillType clipFillType) { if( m_ExecuteLocked ) return false; if (m_HasOpenPaths) throw clipperException("Error: PolyTree struct is needed for open path clipping."); m_ExecuteLocked = true; solution.resize(0); m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; m_UsingPolyTree = false; bool succeeded = ExecuteInternal(); if (succeeded) BuildResult(solution); DisposeAllOutRecs(); m_ExecuteLocked = false; return succeeded; } //------------------------------------------------------------------------------ bool Clipper::Execute(ClipType clipType, PolyTree& polytree, PolyFillType subjFillType, PolyFillType clipFillType) { if( m_ExecuteLocked ) return false; m_ExecuteLocked = true; m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; m_UsingPolyTree = true; bool succeeded = ExecuteInternal(); if (succeeded) BuildResult2(polytree); DisposeAllOutRecs(); m_ExecuteLocked = false; return succeeded; } //------------------------------------------------------------------------------ void Clipper::FixHoleLinkage(OutRec &outrec) { //skip OutRecs that (a) contain outermost polygons or //(b) already have the correct owner/child linkage ... if (!outrec.FirstLeft || (outrec.IsHole != outrec.FirstLeft->IsHole && outrec.FirstLeft->Pts)) return; OutRec* orfl = outrec.FirstLeft; while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) orfl = orfl->FirstLeft; outrec.FirstLeft = orfl; } //------------------------------------------------------------------------------ bool Clipper::ExecuteInternal() { bool succeeded = true; try { Reset(); if (m_CurrentLM == m_MinimaList.end()) return true; cInt botY = PopScanbeam(); do { InsertLocalMinimaIntoAEL(botY); ProcessHorizontals(); ClearGhostJoins(); if (m_Scanbeam.empty()) break; cInt topY = PopScanbeam(); succeeded = ProcessIntersections(topY); if (!succeeded) break; ProcessEdgesAtTopOfScanbeam(topY); botY = topY; } while (!m_Scanbeam.empty() || m_CurrentLM != m_MinimaList.end()); } catch(...) { succeeded = false; } if (succeeded) { //fix orientations ... for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec *outRec = m_PolyOuts[i]; if (!outRec->Pts || outRec->IsOpen) continue; if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) ReversePolyPtLinks(outRec->Pts); } if (!m_Joins.empty()) JoinCommonEdges(); //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec *outRec = m_PolyOuts[i]; if (!outRec->Pts) continue; if (outRec->IsOpen) FixupOutPolyline(*outRec); else FixupOutPolygon(*outRec); } if (m_StrictSimple) DoSimplePolygons(); } ClearJoins(); ClearGhostJoins(); return succeeded; } //------------------------------------------------------------------------------ void Clipper::InsertScanbeam(const cInt Y) { m_Scanbeam.push(Y); } //------------------------------------------------------------------------------ cInt Clipper::PopScanbeam() { const cInt Y = m_Scanbeam.top(); m_Scanbeam.pop(); while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. return Y; } //------------------------------------------------------------------------------ void Clipper::DisposeAllOutRecs(){ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) DisposeOutRec(i); m_PolyOuts.clear(); } //------------------------------------------------------------------------------ void Clipper::DisposeOutRec(PolyOutList::size_type index) { OutRec *outRec = m_PolyOuts[index]; if (outRec->Pts) DisposeOutPts(outRec->Pts); delete outRec; m_PolyOuts[index] = 0; } //------------------------------------------------------------------------------ void Clipper::SetWindingCount(TEdge &edge) { TEdge *e = edge.PrevInAEL; //find the edge of the same polytype that immediately preceeds 'edge' in AEL while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; if (!e) { edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); edge.WindCnt2 = 0; e = m_ActiveEdges; //ie get ready to calc WindCnt2 } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) { edge.WindCnt = 1; edge.WindCnt2 = e->WindCnt2; e = e->NextInAEL; //ie get ready to calc WindCnt2 } else if (IsEvenOddFillType(edge)) { //EvenOdd filling ... if (edge.WindDelta == 0) { //are we inside a subj polygon ... bool Inside = true; TEdge *e2 = e->PrevInAEL; while (e2) { if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) Inside = !Inside; e2 = e2->PrevInAEL; } edge.WindCnt = (Inside ? 0 : 1); } else { edge.WindCnt = edge.WindDelta; } edge.WindCnt2 = e->WindCnt2; e = e->NextInAEL; //ie get ready to calc WindCnt2 } else { //nonZero, Positive or Negative filling ... if (e->WindCnt * e->WindDelta < 0) { //prev edge is 'decreasing' WindCount (WC) toward zero //so we're outside the previous polygon ... if (Abs(e->WindCnt) > 1) { //outside prev poly but still inside another. //when reversing direction of prev poly use the same WC if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; //otherwise continue to 'decrease' WC ... else edge.WindCnt = e->WindCnt + edge.WindDelta; } else //now outside all polys of same polytype so set own WC ... edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); } else { //prev edge is 'increasing' WindCount (WC) away from zero //so we're inside the previous polygon ... if (edge.WindDelta == 0) edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); //if wind direction is reversing prev then use same WC else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; //otherwise add to WC ... else edge.WindCnt = e->WindCnt + edge.WindDelta; } edge.WindCnt2 = e->WindCnt2; e = e->NextInAEL; //ie get ready to calc WindCnt2 } //update WindCnt2 ... if (IsEvenOddAltFillType(edge)) { //EvenOdd filling ... while (e != &edge) { if (e->WindDelta != 0) edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); e = e->NextInAEL; } } else { //nonZero, Positive or Negative filling ... while ( e != &edge ) { edge.WindCnt2 += e->WindDelta; e = e->NextInAEL; } } } //------------------------------------------------------------------------------ bool Clipper::IsEvenOddFillType(const TEdge& edge) const { if (edge.PolyTyp == ptSubject) return m_SubjFillType == pftEvenOdd; else return m_ClipFillType == pftEvenOdd; } //------------------------------------------------------------------------------ bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const { if (edge.PolyTyp == ptSubject) return m_ClipFillType == pftEvenOdd; else return m_SubjFillType == pftEvenOdd; } //------------------------------------------------------------------------------ bool Clipper::IsContributing(const TEdge& edge) const { PolyFillType pft, pft2; if (edge.PolyTyp == ptSubject) { pft = m_SubjFillType; pft2 = m_ClipFillType; } else { pft = m_ClipFillType; pft2 = m_SubjFillType; } switch(pft) { case pftEvenOdd: //return false if a subj line has been flagged as inside a subj polygon if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; break; case pftNonZero: if (Abs(edge.WindCnt) != 1) return false; break; case pftPositive: if (edge.WindCnt != 1) return false; break; default: //pftNegative if (edge.WindCnt != -1) return false; } switch(m_ClipType) { case ctIntersection: switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 != 0); case pftPositive: return (edge.WindCnt2 > 0); default: return (edge.WindCnt2 < 0); } break; case ctUnion: switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 == 0); case pftPositive: return (edge.WindCnt2 <= 0); default: return (edge.WindCnt2 >= 0); } break; case ctDifference: if (edge.PolyTyp == ptSubject) switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 == 0); case pftPositive: return (edge.WindCnt2 <= 0); default: return (edge.WindCnt2 >= 0); } else switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 != 0); case pftPositive: return (edge.WindCnt2 > 0); default: return (edge.WindCnt2 < 0); } break; case ctXor: if (edge.WindDelta == 0) //XOr always contributing unless open switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 == 0); case pftPositive: return (edge.WindCnt2 <= 0); default: return (edge.WindCnt2 >= 0); } else return true; break; default: return true; } } //------------------------------------------------------------------------------ OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { OutPt* result; TEdge *e, *prevE; if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) { result = AddOutPt(e1, Pt); e2->OutIdx = e1->OutIdx; e1->Side = esLeft; e2->Side = esRight; e = e1; if (e->PrevInAEL == e2) prevE = e2->PrevInAEL; else prevE = e->PrevInAEL; } else { result = AddOutPt(e2, Pt); e1->OutIdx = e2->OutIdx; e1->Side = esRight; e2->Side = esLeft; e = e2; if (e->PrevInAEL == e1) prevE = e1->PrevInAEL; else prevE = e->PrevInAEL; } if (prevE && prevE->OutIdx >= 0 && (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && SlopesEqual(*e, *prevE, m_UseFullRange) && (e->WindDelta != 0) && (prevE->WindDelta != 0)) { OutPt* outPt = AddOutPt(prevE, Pt); AddJoin(result, outPt, e->Top); } return result; } //------------------------------------------------------------------------------ void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { AddOutPt( e1, Pt ); if (e2->WindDelta == 0) AddOutPt(e2, Pt); if( e1->OutIdx == e2->OutIdx ) { e1->OutIdx = Unassigned; e2->OutIdx = Unassigned; } else if (e1->OutIdx < e2->OutIdx) AppendPolygon(e1, e2); else AppendPolygon(e2, e1); } //------------------------------------------------------------------------------ void Clipper::AddEdgeToSEL(TEdge *edge) { //SEL pointers in PEdge are reused to build a list of horizontal edges. //However, we don't need to worry about order with horizontal edge processing. if( !m_SortedEdges ) { m_SortedEdges = edge; edge->PrevInSEL = 0; edge->NextInSEL = 0; } else { edge->NextInSEL = m_SortedEdges; edge->PrevInSEL = 0; m_SortedEdges->PrevInSEL = edge; m_SortedEdges = edge; } } //------------------------------------------------------------------------------ void Clipper::CopyAELToSEL() { TEdge* e = m_ActiveEdges; m_SortedEdges = e; while ( e ) { e->PrevInSEL = e->PrevInAEL; e->NextInSEL = e->NextInAEL; e = e->NextInAEL; } } //------------------------------------------------------------------------------ void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) { Join* j = new Join; j->OutPt1 = op1; j->OutPt2 = op2; j->OffPt = OffPt; m_Joins.push_back(j); } //------------------------------------------------------------------------------ void Clipper::ClearJoins() { for (JoinList::size_type i = 0; i < m_Joins.size(); i++) delete m_Joins[i]; m_Joins.resize(0); } //------------------------------------------------------------------------------ void Clipper::ClearGhostJoins() { for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) delete m_GhostJoins[i]; m_GhostJoins.resize(0); } //------------------------------------------------------------------------------ void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) { Join* j = new Join; j->OutPt1 = op; j->OutPt2 = 0; j->OffPt = OffPt; m_GhostJoins.push_back(j); } //------------------------------------------------------------------------------ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { while (m_CurrentLM != m_MinimaList.end() && (m_CurrentLM->Y == botY)) { TEdge* lb = m_CurrentLM->LeftBound; TEdge* rb = m_CurrentLM->RightBound; PopLocalMinima(); OutPt *Op1 = 0; if (!lb) { //nb: don't insert LB into either AEL or SEL InsertEdgeIntoAEL(rb, 0); SetWindingCount(*rb); if (IsContributing(*rb)) Op1 = AddOutPt(rb, rb->Bot); } else if (!rb) { InsertEdgeIntoAEL(lb, 0); SetWindingCount(*lb); if (IsContributing(*lb)) Op1 = AddOutPt(lb, lb->Bot); InsertScanbeam(lb->Top.Y); } else { InsertEdgeIntoAEL(lb, 0); InsertEdgeIntoAEL(rb, lb); SetWindingCount( *lb ); rb->WindCnt = lb->WindCnt; rb->WindCnt2 = lb->WindCnt2; if (IsContributing(*lb)) Op1 = AddLocalMinPoly(lb, rb, lb->Bot); InsertScanbeam(lb->Top.Y); } if (rb) { if(IsHorizontal(*rb)) AddEdgeToSEL(rb); else InsertScanbeam( rb->Top.Y ); } if (!lb || !rb) continue; //if any output polygons share an edge, they'll need joining later ... if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) { for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) { Join* jr = m_GhostJoins[i]; //if the horizontal Rb and a 'ghost' horizontal overlap, then convert //the 'ghost' join to a real join ready for later ... if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) AddJoin(jr->OutPt1, Op1, jr->OffPt); } } if (lb->OutIdx >= 0 && lb->PrevInAEL && lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 && SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) { OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); AddJoin(Op1, Op2, lb->Top); } if(lb->NextInAEL != rb) { if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) { OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); AddJoin(Op1, Op2, rb->Top); } TEdge* e = lb->NextInAEL; if (e) { while( e != rb ) { //nb: For calculating winding counts etc, IntersectEdges() assumes //that param1 will be to the Right of param2 ABOVE the intersection ... IntersectEdges(rb , e , lb->Curr); //order important here e = e->NextInAEL; } } } } } //------------------------------------------------------------------------------ void Clipper::DeleteFromAEL(TEdge *e) { TEdge* AelPrev = e->PrevInAEL; TEdge* AelNext = e->NextInAEL; if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted if( AelPrev ) AelPrev->NextInAEL = AelNext; else m_ActiveEdges = AelNext; if( AelNext ) AelNext->PrevInAEL = AelPrev; e->NextInAEL = 0; e->PrevInAEL = 0; } //------------------------------------------------------------------------------ void Clipper::DeleteFromSEL(TEdge *e) { TEdge* SelPrev = e->PrevInSEL; TEdge* SelNext = e->NextInSEL; if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted if( SelPrev ) SelPrev->NextInSEL = SelNext; else m_SortedEdges = SelNext; if( SelNext ) SelNext->PrevInSEL = SelPrev; e->NextInSEL = 0; e->PrevInSEL = 0; } //------------------------------------------------------------------------------ #ifdef use_xyz void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) { if (pt.Z != 0 || !m_ZFill) return; else if (pt == e1.Bot) pt.Z = e1.Bot.Z; else if (pt == e1.Top) pt.Z = e1.Top.Z; else if (pt == e2.Bot) pt.Z = e2.Bot.Z; else if (pt == e2.Top) pt.Z = e2.Top.Z; else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); } //------------------------------------------------------------------------------ #endif void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) { bool e1Contributing = ( e1->OutIdx >= 0 ); bool e2Contributing = ( e2->OutIdx >= 0 ); #ifdef use_xyz SetZ(Pt, *e1, *e2); #endif #ifdef use_lines //if either edge is on an OPEN path ... if (e1->WindDelta == 0 || e2->WindDelta == 0) { //ignore subject-subject open path intersections UNLESS they //are both open paths, AND they are both 'contributing maximas' ... if (e1->WindDelta == 0 && e2->WindDelta == 0) return; //if intersecting a subj line with a subj poly ... else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) { if (e1->WindDelta == 0) { if (e2Contributing) { AddOutPt(e1, Pt); if (e1Contributing) e1->OutIdx = Unassigned; } } else { if (e1Contributing) { AddOutPt(e2, Pt); if (e2Contributing) e2->OutIdx = Unassigned; } } } else if (e1->PolyTyp != e2->PolyTyp) { //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && (m_ClipType != ctUnion || e2->WindCnt2 == 0)) { AddOutPt(e1, Pt); if (e1Contributing) e1->OutIdx = Unassigned; } else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && (m_ClipType != ctUnion || e1->WindCnt2 == 0)) { AddOutPt(e2, Pt); if (e2Contributing) e2->OutIdx = Unassigned; } } return; } #endif //update winding counts... //assumes that e1 will be to the Right of e2 ABOVE the intersection if ( e1->PolyTyp == e2->PolyTyp ) { if ( IsEvenOddFillType( *e1) ) { int oldE1WindCnt = e1->WindCnt; e1->WindCnt = e2->WindCnt; e2->WindCnt = oldE1WindCnt; } else { if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; else e1->WindCnt += e2->WindDelta; if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; else e2->WindCnt -= e1->WindDelta; } } else { if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; } PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; if (e1->PolyTyp == ptSubject) { e1FillType = m_SubjFillType; e1FillType2 = m_ClipFillType; } else { e1FillType = m_ClipFillType; e1FillType2 = m_SubjFillType; } if (e2->PolyTyp == ptSubject) { e2FillType = m_SubjFillType; e2FillType2 = m_ClipFillType; } else { e2FillType = m_ClipFillType; e2FillType2 = m_SubjFillType; } cInt e1Wc, e2Wc; switch (e1FillType) { case pftPositive: e1Wc = e1->WindCnt; break; case pftNegative: e1Wc = -e1->WindCnt; break; default: e1Wc = Abs(e1->WindCnt); } switch(e2FillType) { case pftPositive: e2Wc = e2->WindCnt; break; case pftNegative: e2Wc = -e2->WindCnt; break; default: e2Wc = Abs(e2->WindCnt); } if ( e1Contributing && e2Contributing ) { if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) { AddLocalMaxPoly(e1, e2, Pt); } else { AddOutPt(e1, Pt); AddOutPt(e2, Pt); SwapSides( *e1 , *e2 ); SwapPolyIndexes( *e1 , *e2 ); } } else if ( e1Contributing ) { if (e2Wc == 0 || e2Wc == 1) { AddOutPt(e1, Pt); SwapSides(*e1, *e2); SwapPolyIndexes(*e1, *e2); } } else if ( e2Contributing ) { if (e1Wc == 0 || e1Wc == 1) { AddOutPt(e2, Pt); SwapSides(*e1, *e2); SwapPolyIndexes(*e1, *e2); } } else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { //neither edge is currently contributing ... cInt e1Wc2, e2Wc2; switch (e1FillType2) { case pftPositive: e1Wc2 = e1->WindCnt2; break; case pftNegative : e1Wc2 = -e1->WindCnt2; break; default: e1Wc2 = Abs(e1->WindCnt2); } switch (e2FillType2) { case pftPositive: e2Wc2 = e2->WindCnt2; break; case pftNegative: e2Wc2 = -e2->WindCnt2; break; default: e2Wc2 = Abs(e2->WindCnt2); } if (e1->PolyTyp != e2->PolyTyp) { AddLocalMinPoly(e1, e2, Pt); } else if (e1Wc == 1 && e2Wc == 1) switch( m_ClipType ) { case ctIntersection: if (e1Wc2 > 0 && e2Wc2 > 0) AddLocalMinPoly(e1, e2, Pt); break; case ctUnion: if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) AddLocalMinPoly(e1, e2, Pt); break; case ctDifference: if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) AddLocalMinPoly(e1, e2, Pt); break; case ctXor: AddLocalMinPoly(e1, e2, Pt); } else SwapSides( *e1, *e2 ); } } //------------------------------------------------------------------------------ void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { bool IsHole = false; TEdge *e2 = e->PrevInAEL; while (e2) { if (e2->OutIdx >= 0 && e2->WindDelta != 0) { IsHole = !IsHole; if (! outrec->FirstLeft) outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; } e2 = e2->PrevInAEL; } if (IsHole) outrec->IsHole = true; } //------------------------------------------------------------------------------ OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) { //work out which polygon fragment has the correct hole state ... if (!outRec1->BottomPt) outRec1->BottomPt = GetBottomPt(outRec1->Pts); if (!outRec2->BottomPt) outRec2->BottomPt = GetBottomPt(outRec2->Pts); OutPt *OutPt1 = outRec1->BottomPt; OutPt *OutPt2 = outRec2->BottomPt; if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; else if (OutPt1->Next == OutPt1) return outRec2; else if (OutPt2->Next == OutPt2) return outRec1; else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; else return outRec2; } //------------------------------------------------------------------------------ bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) { do { outRec1 = outRec1->FirstLeft; if (outRec1 == outRec2) return true; } while (outRec1); return false; } //------------------------------------------------------------------------------ OutRec* Clipper::GetOutRec(int Idx) { OutRec* outrec = m_PolyOuts[Idx]; while (outrec != m_PolyOuts[outrec->Idx]) outrec = m_PolyOuts[outrec->Idx]; return outrec; } //------------------------------------------------------------------------------ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { //get the start and ends of both output polygons ... OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; OutRec *holeStateRec; if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); //get the start and ends of both output polygons and //join e2 poly onto e1 poly and delete pointers to e2 ... OutPt* p1_lft = outRec1->Pts; OutPt* p1_rt = p1_lft->Prev; OutPt* p2_lft = outRec2->Pts; OutPt* p2_rt = p2_lft->Prev; EdgeSide Side; //join e2 poly onto e1 poly and delete pointers to e2 ... if( e1->Side == esLeft ) { if( e2->Side == esLeft ) { //z y x a b c ReversePolyPtLinks(p2_lft); p2_lft->Next = p1_lft; p1_lft->Prev = p2_lft; p1_rt->Next = p2_rt; p2_rt->Prev = p1_rt; outRec1->Pts = p2_rt; } else { //x y z a b c p2_rt->Next = p1_lft; p1_lft->Prev = p2_rt; p2_lft->Prev = p1_rt; p1_rt->Next = p2_lft; outRec1->Pts = p2_lft; } Side = esLeft; } else { if( e2->Side == esRight ) { //a b c z y x ReversePolyPtLinks(p2_lft); p1_rt->Next = p2_rt; p2_rt->Prev = p1_rt; p2_lft->Next = p1_lft; p1_lft->Prev = p2_lft; } else { //a b c x y z p1_rt->Next = p2_lft; p2_lft->Prev = p1_rt; p1_lft->Prev = p2_rt; p2_rt->Next = p1_lft; } Side = esRight; } outRec1->BottomPt = 0; if (holeStateRec == outRec2) { if (outRec2->FirstLeft != outRec1) outRec1->FirstLeft = outRec2->FirstLeft; outRec1->IsHole = outRec2->IsHole; } outRec2->Pts = 0; outRec2->BottomPt = 0; outRec2->FirstLeft = outRec1; int OKIdx = e1->OutIdx; int ObsoleteIdx = e2->OutIdx; e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly e2->OutIdx = Unassigned; TEdge* e = m_ActiveEdges; while( e ) { if( e->OutIdx == ObsoleteIdx ) { e->OutIdx = OKIdx; e->Side = Side; break; } e = e->NextInAEL; } outRec2->Idx = outRec1->Idx; } //------------------------------------------------------------------------------ OutRec* Clipper::CreateOutRec() { OutRec* result = new OutRec; result->IsHole = false; result->IsOpen = false; result->FirstLeft = 0; result->Pts = 0; result->BottomPt = 0; result->PolyNd = 0; m_PolyOuts.push_back(result); result->Idx = (int)m_PolyOuts.size()-1; return result; } //------------------------------------------------------------------------------ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { if( e->OutIdx < 0 ) { OutRec *outRec = CreateOutRec(); outRec->IsOpen = (e->WindDelta == 0); OutPt* newOp = new OutPt; outRec->Pts = newOp; newOp->Idx = outRec->Idx; newOp->Pt = pt; newOp->Next = newOp; newOp->Prev = newOp; if (!outRec->IsOpen) SetHoleState(e, outRec); e->OutIdx = outRec->Idx; return newOp; } else { OutRec *outRec = m_PolyOuts[e->OutIdx]; //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' OutPt* op = outRec->Pts; bool ToFront = (e->Side == esLeft); if (ToFront && (pt == op->Pt)) return op; else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; OutPt* newOp = new OutPt; newOp->Idx = outRec->Idx; newOp->Pt = pt; newOp->Next = op; newOp->Prev = op->Prev; newOp->Prev->Next = newOp; op->Prev = newOp; if (ToFront) outRec->Pts = newOp; return newOp; } } //------------------------------------------------------------------------------ OutPt* Clipper::GetLastOutPt(TEdge *e) { OutRec *outRec = m_PolyOuts[e->OutIdx]; if (e->Side == esLeft) return outRec->Pts; else return outRec->Pts->Prev; } //------------------------------------------------------------------------------ void Clipper::ProcessHorizontals() { TEdge* horzEdge = m_SortedEdges; while(horzEdge) { DeleteFromSEL(horzEdge); ProcessHorizontal(horzEdge); horzEdge = m_SortedEdges; } } //------------------------------------------------------------------------------ inline bool IsMinima(TEdge *e) { return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); } //------------------------------------------------------------------------------ inline bool IsMaxima(TEdge *e, const cInt Y) { return e && e->Top.Y == Y && !e->NextInLML; } //------------------------------------------------------------------------------ inline bool IsIntermediate(TEdge *e, const cInt Y) { return e->Top.Y == Y && e->NextInLML; } //------------------------------------------------------------------------------ TEdge *GetMaximaPair(TEdge *e) { TEdge* result = 0; if ((e->Next->Top == e->Top) && !e->Next->NextInLML) result = e->Next; else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) result = e->Prev; if (result && (result->OutIdx == Skip || //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; return result; } //------------------------------------------------------------------------------ void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) { //check that one or other edge hasn't already been removed from AEL ... if (Edge1->NextInAEL == Edge1->PrevInAEL || Edge2->NextInAEL == Edge2->PrevInAEL) return; if( Edge1->NextInAEL == Edge2 ) { TEdge* Next = Edge2->NextInAEL; if( Next ) Next->PrevInAEL = Edge1; TEdge* Prev = Edge1->PrevInAEL; if( Prev ) Prev->NextInAEL = Edge2; Edge2->PrevInAEL = Prev; Edge2->NextInAEL = Edge1; Edge1->PrevInAEL = Edge2; Edge1->NextInAEL = Next; } else if( Edge2->NextInAEL == Edge1 ) { TEdge* Next = Edge1->NextInAEL; if( Next ) Next->PrevInAEL = Edge2; TEdge* Prev = Edge2->PrevInAEL; if( Prev ) Prev->NextInAEL = Edge1; Edge1->PrevInAEL = Prev; Edge1->NextInAEL = Edge2; Edge2->PrevInAEL = Edge1; Edge2->NextInAEL = Next; } else { TEdge* Next = Edge1->NextInAEL; TEdge* Prev = Edge1->PrevInAEL; Edge1->NextInAEL = Edge2->NextInAEL; if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; Edge1->PrevInAEL = Edge2->PrevInAEL; if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; Edge2->NextInAEL = Next; if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; Edge2->PrevInAEL = Prev; if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; } if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; } //------------------------------------------------------------------------------ void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) { if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; if( Edge1->NextInSEL == Edge2 ) { TEdge* Next = Edge2->NextInSEL; if( Next ) Next->PrevInSEL = Edge1; TEdge* Prev = Edge1->PrevInSEL; if( Prev ) Prev->NextInSEL = Edge2; Edge2->PrevInSEL = Prev; Edge2->NextInSEL = Edge1; Edge1->PrevInSEL = Edge2; Edge1->NextInSEL = Next; } else if( Edge2->NextInSEL == Edge1 ) { TEdge* Next = Edge1->NextInSEL; if( Next ) Next->PrevInSEL = Edge2; TEdge* Prev = Edge2->PrevInSEL; if( Prev ) Prev->NextInSEL = Edge1; Edge1->PrevInSEL = Prev; Edge1->NextInSEL = Edge2; Edge2->PrevInSEL = Edge1; Edge2->NextInSEL = Next; } else { TEdge* Next = Edge1->NextInSEL; TEdge* Prev = Edge1->PrevInSEL; Edge1->NextInSEL = Edge2->NextInSEL; if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; Edge1->PrevInSEL = Edge2->PrevInSEL; if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; Edge2->NextInSEL = Next; if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; Edge2->PrevInSEL = Prev; if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; } if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; } //------------------------------------------------------------------------------ TEdge* GetNextInAEL(TEdge *e, Direction dir) { return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; } //------------------------------------------------------------------------------ void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) { if (HorzEdge.Bot.X < HorzEdge.Top.X) { Left = HorzEdge.Bot.X; Right = HorzEdge.Top.X; Dir = dLeftToRight; } else { Left = HorzEdge.Top.X; Right = HorzEdge.Bot.X; Dir = dRightToLeft; } } //------------------------------------------------------------------------ /******************************************************************************* * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * * Bottom of a scanbeam) are processed as if layered. The order in which HEs * * are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * * (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * * and with other non-horizontal edges [*]. Once these intersections are * * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * *******************************************************************************/ void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; cInt horzLeft, horzRight; bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); TEdge* eLastHorz = horzEdge, *eMaxPair = 0; while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) eLastHorz = eLastHorz->NextInLML; if (!eLastHorz->NextInLML) eMaxPair = GetMaximaPair(eLastHorz); MaximaList::const_iterator maxIt; MaximaList::const_reverse_iterator maxRit; if (m_Maxima.size() > 0) { //get the first maxima in range (X) ... if (dir == dLeftToRight) { maxIt = m_Maxima.begin(); while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) maxIt = m_Maxima.end(); } else { maxRit = m_Maxima.rbegin(); while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) maxRit = m_Maxima.rend(); } } OutPt* op1 = 0; for (;;) //loop through consec. horizontal edges { bool IsLastHorz = (horzEdge == eLastHorz); TEdge* e = GetNextInAEL(horzEdge, dir); while(e) { //this code block inserts extra coords into horizontal edges (in output //polygons) whereever maxima touch these horizontal edges. This helps //'simplifying' polygons (ie if the Simplify property is set). if (m_Maxima.size() > 0) { if (dir == dLeftToRight) { while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) { if (horzEdge->OutIdx >= 0 && !IsOpen) AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); maxIt++; } } else { while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) { if (horzEdge->OutIdx >= 0 && !IsOpen) AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); maxRit++; } } }; if ((dir == dLeftToRight && e->Curr.X > horzRight) || (dir == dRightToLeft && e->Curr.X < horzLeft)) break; //Also break if we've got to the end of an intermediate horizontal edge ... //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && e->Dx < horzEdge->NextInLML->Dx) break; if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times { op1 = AddOutPt(horzEdge, e->Curr); TEdge* eNextHorz = m_SortedEdges; while (eNextHorz) { if (eNextHorz->OutIdx >= 0 && HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) { OutPt* op2 = GetLastOutPt(eNextHorz); AddJoin(op2, op1, eNextHorz->Top); } eNextHorz = eNextHorz->NextInSEL; } AddGhostJoin(op1, horzEdge->Bot); } //OK, so far we're still in range of the horizontal Edge but make sure //we're at the last of consec. horizontals when matching with eMaxPair if(e == eMaxPair && IsLastHorz) { if (horzEdge->OutIdx >= 0) AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); DeleteFromAEL(horzEdge); DeleteFromAEL(eMaxPair); return; } if(dir == dLeftToRight) { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntersectEdges(horzEdge, e, Pt); } else { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntersectEdges( e, horzEdge, Pt); } TEdge* eNext = GetNextInAEL(e, dir); SwapPositionsInAEL( horzEdge, e ); e = eNext; } //end while(e) //Break out of loop if HorzEdge.NextInLML is not also horizontal ... if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; UpdateEdgeIntoAEL(horzEdge); if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); } //end for (;;) if (horzEdge->OutIdx >= 0 && !op1) { op1 = GetLastOutPt(horzEdge); TEdge* eNextHorz = m_SortedEdges; while (eNextHorz) { if (eNextHorz->OutIdx >= 0 && HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) { OutPt* op2 = GetLastOutPt(eNextHorz); AddJoin(op2, op1, eNextHorz->Top); } eNextHorz = eNextHorz->NextInSEL; } AddGhostJoin(op1, horzEdge->Top); } if (horzEdge->NextInLML) { if(horzEdge->OutIdx >= 0) { op1 = AddOutPt( horzEdge, horzEdge->Top); UpdateEdgeIntoAEL(horzEdge); if (horzEdge->WindDelta == 0) return; //nb: HorzEdge is no longer horizontal here TEdge* ePrev = horzEdge->PrevInAEL; TEdge* eNext = horzEdge->NextInAEL; if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) { OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); AddJoin(op1, op2, horzEdge->Top); } else if (eNext && eNext->Curr.X == horzEdge->Bot.X && eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) { OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); AddJoin(op1, op2, horzEdge->Top); } } else UpdateEdgeIntoAEL(horzEdge); } else { if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); DeleteFromAEL(horzEdge); } } //------------------------------------------------------------------------------ void Clipper::UpdateEdgeIntoAEL(TEdge *&e) { if( !e->NextInLML ) throw clipperException("UpdateEdgeIntoAEL: invalid call"); e->NextInLML->OutIdx = e->OutIdx; TEdge* AelPrev = e->PrevInAEL; TEdge* AelNext = e->NextInAEL; if (AelPrev) AelPrev->NextInAEL = e->NextInLML; else m_ActiveEdges = e->NextInLML; if (AelNext) AelNext->PrevInAEL = e->NextInLML; e->NextInLML->Side = e->Side; e->NextInLML->WindDelta = e->WindDelta; e->NextInLML->WindCnt = e->WindCnt; e->NextInLML->WindCnt2 = e->WindCnt2; e = e->NextInLML; e->Curr = e->Bot; e->PrevInAEL = AelPrev; e->NextInAEL = AelNext; if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); } //------------------------------------------------------------------------------ bool Clipper::ProcessIntersections(const cInt topY) { if( !m_ActiveEdges ) return true; try { BuildIntersectList(topY); size_t IlSize = m_IntersectList.size(); if (IlSize == 0) return true; if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); else return false; } catch(...) { m_SortedEdges = 0; DisposeIntersectNodes(); throw clipperException("ProcessIntersections error"); } m_SortedEdges = 0; return true; } //------------------------------------------------------------------------------ void Clipper::DisposeIntersectNodes() { for (size_t i = 0; i < m_IntersectList.size(); ++i ) delete m_IntersectList[i]; m_IntersectList.clear(); } //------------------------------------------------------------------------------ void Clipper::BuildIntersectList(const cInt topY) { if ( !m_ActiveEdges ) return; //prepare for sorting ... TEdge* e = m_ActiveEdges; m_SortedEdges = e; while( e ) { e->PrevInSEL = e->PrevInAEL; e->NextInSEL = e->NextInAEL; e->Curr.X = TopX( *e, topY ); e = e->NextInAEL; } //bubblesort ... bool isModified; do { isModified = false; e = m_SortedEdges; while( e->NextInSEL ) { TEdge *eNext = e->NextInSEL; IntPoint Pt; if(e->Curr.X > eNext->Curr.X) { IntersectPoint(*e, *eNext, Pt); IntersectNode * newNode = new IntersectNode; newNode->Edge1 = e; newNode->Edge2 = eNext; newNode->Pt = Pt; m_IntersectList.push_back(newNode); SwapPositionsInSEL(e, eNext); isModified = true; } else e = eNext; } if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; else break; } while ( isModified ); m_SortedEdges = 0; //important } //------------------------------------------------------------------------------ void Clipper::ProcessIntersectList() { for (size_t i = 0; i < m_IntersectList.size(); ++i) { IntersectNode* iNode = m_IntersectList[i]; { IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); } delete iNode; } m_IntersectList.clear(); } //------------------------------------------------------------------------------ bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) { return node2->Pt.Y < node1->Pt.Y; } //------------------------------------------------------------------------------ inline bool EdgesAdjacent(const IntersectNode &inode) { return (inode.Edge1->NextInSEL == inode.Edge2) || (inode.Edge1->PrevInSEL == inode.Edge2); } //------------------------------------------------------------------------------ bool Clipper::FixupIntersectionOrder() { //pre-condition: intersections are sorted Bottom-most first. //Now it's crucial that intersections are made only between adjacent edges, //so to ensure this the order of intersections may need adjusting ... CopyAELToSEL(); std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); size_t cnt = m_IntersectList.size(); for (size_t i = 0; i < cnt; ++i) { if (!EdgesAdjacent(*m_IntersectList[i])) { size_t j = i + 1; while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; if (j == cnt) return false; std::swap(m_IntersectList[i], m_IntersectList[j]); } SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); } return true; } //------------------------------------------------------------------------------ void Clipper::DoMaxima(TEdge *e) { TEdge* eMaxPair = GetMaximaPair(e); if (!eMaxPair) { if (e->OutIdx >= 0) AddOutPt(e, e->Top); DeleteFromAEL(e); return; } TEdge* eNext = e->NextInAEL; while(eNext && eNext != eMaxPair) { IntersectEdges(e, eNext, e->Top); SwapPositionsInAEL(e, eNext); eNext = e->NextInAEL; } if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) { DeleteFromAEL(e); DeleteFromAEL(eMaxPair); } else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) { if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); DeleteFromAEL(e); DeleteFromAEL(eMaxPair); } #ifdef use_lines else if (e->WindDelta == 0) { if (e->OutIdx >= 0) { AddOutPt(e, e->Top); e->OutIdx = Unassigned; } DeleteFromAEL(e); if (eMaxPair->OutIdx >= 0) { AddOutPt(eMaxPair, e->Top); eMaxPair->OutIdx = Unassigned; } DeleteFromAEL(eMaxPair); } #endif else throw clipperException("DoMaxima error"); } //------------------------------------------------------------------------------ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { TEdge* e = m_ActiveEdges; while( e ) { //1. process maxima, treating them as if they're 'bent' horizontal edges, // but exclude maxima with horizontal edges. nb: e can't be a horizontal. bool IsMaximaEdge = IsMaxima(e, topY); if(IsMaximaEdge) { TEdge* eMaxPair = GetMaximaPair(e); IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); } if(IsMaximaEdge) { if (m_StrictSimple) m_Maxima.push_back(e->Top.X); TEdge* ePrev = e->PrevInAEL; DoMaxima(e); if( !ePrev ) e = m_ActiveEdges; else e = ePrev->NextInAEL; } else { //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) { UpdateEdgeIntoAEL(e); if (e->OutIdx >= 0) AddOutPt(e, e->Bot); AddEdgeToSEL(e); } else { e->Curr.X = TopX( *e, topY ); e->Curr.Y = topY; } //When StrictlySimple and 'e' is being touched by another edge, then //make sure both edges have a vertex here ... if (m_StrictSimple) { TEdge* ePrev = e->PrevInAEL; if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) { IntPoint pt = e->Curr; #ifdef use_xyz SetZ(pt, *ePrev, *e); #endif OutPt* op = AddOutPt(ePrev, pt); OutPt* op2 = AddOutPt(e, pt); AddJoin(op, op2, pt); //StrictlySimple (type-3) join } } e = e->NextInAEL; } } //3. Process horizontals at the Top of the scanbeam ... m_Maxima.sort(); ProcessHorizontals(); m_Maxima.clear(); //4. Promote intermediate vertices ... e = m_ActiveEdges; while(e) { if(IsIntermediate(e, topY)) { OutPt* op = 0; if( e->OutIdx >= 0 ) op = AddOutPt(e, e->Top); UpdateEdgeIntoAEL(e); //if output polygons share an edge, they'll need joining later ... TEdge* ePrev = e->PrevInAEL; TEdge* eNext = e->NextInAEL; if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y && op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && SlopesEqual(*e, *ePrev, m_UseFullRange) && (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { OutPt* op2 = AddOutPt(ePrev, e->Bot); AddJoin(op, op2, e->Top); } else if (eNext && eNext->Curr.X == e->Bot.X && eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && SlopesEqual(*e, *eNext, m_UseFullRange) && (e->WindDelta != 0) && (eNext->WindDelta != 0)) { OutPt* op2 = AddOutPt(eNext, e->Bot); AddJoin(op, op2, e->Top); } } e = e->NextInAEL; } } //------------------------------------------------------------------------------ void Clipper::FixupOutPolyline(OutRec &outrec) { OutPt *pp = outrec.Pts; OutPt *lastPP = pp->Prev; while (pp != lastPP) { pp = pp->Next; if (pp->Pt == pp->Prev->Pt) { if (pp == lastPP) lastPP = pp->Prev; OutPt *tmpPP = pp->Prev; tmpPP->Next = pp->Next; pp->Next->Prev = tmpPP; delete pp; pp = tmpPP; } } if (pp == pp->Prev) { DisposeOutPts(pp); outrec.Pts = 0; return; } } //------------------------------------------------------------------------------ void Clipper::FixupOutPolygon(OutRec &outrec) { //FixupOutPolygon() - removes duplicate points and simplifies consecutive //parallel edges by removing the middle vertex. OutPt *lastOK = 0; outrec.BottomPt = 0; OutPt *pp = outrec.Pts; bool preserveCol = m_PreserveCollinear || m_StrictSimple; for (;;) { if (pp->Prev == pp || pp->Prev == pp->Next) { DisposeOutPts(pp); outrec.Pts = 0; return; } //test for duplicate points and collinear edges ... if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) { lastOK = 0; OutPt *tmp = pp; pp->Prev->Next = pp->Next; pp->Next->Prev = pp->Prev; pp = pp->Prev; delete tmp; } else if (pp == lastOK) break; else { if (!lastOK) lastOK = pp; pp = pp->Next; } } outrec.Pts = pp; } //------------------------------------------------------------------------------ int PointCount(OutPt *Pts) { if (!Pts) return 0; int result = 0; OutPt* p = Pts; do { result++; p = p->Next; } while (p != Pts); return result; } //------------------------------------------------------------------------------ void Clipper::BuildResult(Paths &polys) { polys.reserve(m_PolyOuts.size()); for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { if (!m_PolyOuts[i]->Pts) continue; Path pg; OutPt* p = m_PolyOuts[i]->Pts->Prev; int cnt = PointCount(p); if (cnt < 2) continue; pg.reserve(cnt); for (int i = 0; i < cnt; ++i) { pg.push_back(p->Pt); p = p->Prev; } polys.push_back(pg); } } //------------------------------------------------------------------------------ void Clipper::BuildResult2(PolyTree& polytree) { polytree.Clear(); polytree.AllNodes.reserve(m_PolyOuts.size()); //add each output polygon/contour to polytree ... for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { OutRec* outRec = m_PolyOuts[i]; int cnt = PointCount(outRec->Pts); if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; FixHoleLinkage(*outRec); PolyNode* pn = new PolyNode(); //nb: polytree takes ownership of all the PolyNodes polytree.AllNodes.push_back(pn); outRec->PolyNd = pn; pn->Parent = 0; pn->Index = 0; pn->Contour.reserve(cnt); OutPt *op = outRec->Pts->Prev; for (int j = 0; j < cnt; j++) { pn->Contour.push_back(op->Pt); op = op->Prev; } } //fixup PolyNode links etc ... polytree.Childs.reserve(m_PolyOuts.size()); for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { OutRec* outRec = m_PolyOuts[i]; if (!outRec->PolyNd) continue; if (outRec->IsOpen) { outRec->PolyNd->m_IsOpen = true; polytree.AddChild(*outRec->PolyNd); } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); else polytree.AddChild(*outRec->PolyNd); } } //------------------------------------------------------------------------------ void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) { //just swap the contents (because fIntersectNodes is a single-linked-list) IntersectNode inode = int1; //gets a copy of Int1 int1.Edge1 = int2.Edge1; int1.Edge2 = int2.Edge2; int1.Pt = int2.Pt; int2.Edge1 = inode.Edge1; int2.Edge2 = inode.Edge2; int2.Pt = inode.Pt; } //------------------------------------------------------------------------------ inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { if (e2.Curr.X == e1.Curr.X) { if (e2.Top.Y > e1.Top.Y) return e2.Top.X < TopX(e1, e2.Top.Y); else return e1.Top.X > TopX(e2, e1.Top.Y); } else return e2.Curr.X < e1.Curr.X; } //------------------------------------------------------------------------------ bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, cInt& Left, cInt& Right) { if (a1 < a2) { if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} else {Left = std::max(a1,b2); Right = std::min(a2,b1);} } else { if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} else {Left = std::max(a2,b2); Right = std::min(a1,b1);} } return Left < Right; } //------------------------------------------------------------------------------ inline void UpdateOutPtIdxs(OutRec& outrec) { OutPt* op = outrec.Pts; do { op->Idx = outrec.Idx; op = op->Prev; } while(op != outrec.Pts); } //------------------------------------------------------------------------------ void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) { if(!m_ActiveEdges) { edge->PrevInAEL = 0; edge->NextInAEL = 0; m_ActiveEdges = edge; } else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) { edge->PrevInAEL = 0; edge->NextInAEL = m_ActiveEdges; m_ActiveEdges->PrevInAEL = edge; m_ActiveEdges = edge; } else { if(!startEdge) startEdge = m_ActiveEdges; while(startEdge->NextInAEL && !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) startEdge = startEdge->NextInAEL; edge->NextInAEL = startEdge->NextInAEL; if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; edge->PrevInAEL = startEdge; startEdge->NextInAEL = edge; } } //---------------------------------------------------------------------- OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) { OutPt* result = new OutPt; result->Pt = outPt->Pt; result->Idx = outPt->Idx; if (InsertAfter) { result->Next = outPt->Next; result->Prev = outPt; outPt->Next->Prev = result; outPt->Next = result; } else { result->Prev = outPt->Prev; result->Next = outPt; outPt->Prev->Next = result; outPt->Prev = result; } return result; } //------------------------------------------------------------------------------ bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint Pt, bool DiscardLeft) { Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); if (Dir1 == Dir2) return false; //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) //So, to facilitate this while inserting Op1b and Op2b ... //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) if (Dir1 == dLeftToRight) { while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) op1 = op1->Next; if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; op1b = DupOutPt(op1, !DiscardLeft); if (op1b->Pt != Pt) { op1 = op1b; op1->Pt = Pt; op1b = DupOutPt(op1, !DiscardLeft); } } else { while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) op1 = op1->Next; if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; op1b = DupOutPt(op1, DiscardLeft); if (op1b->Pt != Pt) { op1 = op1b; op1->Pt = Pt; op1b = DupOutPt(op1, DiscardLeft); } } if (Dir2 == dLeftToRight) { while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) op2 = op2->Next; if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; op2b = DupOutPt(op2, !DiscardLeft); if (op2b->Pt != Pt) { op2 = op2b; op2->Pt = Pt; op2b = DupOutPt(op2, !DiscardLeft); }; } else { while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) op2 = op2->Next; if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; op2b = DupOutPt(op2, DiscardLeft); if (op2b->Pt != Pt) { op2 = op2b; op2->Pt = Pt; op2b = DupOutPt(op2, DiscardLeft); }; }; if ((Dir1 == dLeftToRight) == DiscardLeft) { op1->Prev = op2; op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; } else { op1->Next = op2; op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; } return true; } //------------------------------------------------------------------------------ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) { OutPt *op1 = j->OutPt1, *op1b; OutPt *op2 = j->OutPt2, *op2b; //There are 3 kinds of joins for output polygons ... //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same //location at the Bottom of the overlapping segment (& Join.OffPt is above). //3. StrictSimple joins where edges touch but are not collinear and where //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && (j->OffPt == j->OutPt2->Pt)) { //Strictly Simple join ... if (outRec1 != outRec2) return false; op1b = j->OutPt1->Next; while (op1b != op1 && (op1b->Pt == j->OffPt)) op1b = op1b->Next; bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); op2b = j->OutPt2->Next; while (op2b != op2 && (op2b->Pt == j->OffPt)) op2b = op2b->Next; bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); if (reverse1 == reverse2) return false; if (reverse1) { op1b = DupOutPt(op1, false); op2b = DupOutPt(op2, true); op1->Prev = op2; op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } else { op1b = DupOutPt(op1, true); op2b = DupOutPt(op2, false); op1->Next = op2; op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } } else if (isHorizontal) { //treat horizontal joins differently to non-horizontal joins since with //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt //may be anywhere along the horizontal edge. op1b = op1; while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) op1 = op1->Prev; while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) op1b = op1b->Next; if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' op2b = op2; while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) op2 = op2->Prev; while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) op2b = op2b->Next; if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' cInt Left, Right; //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) return false; //DiscardLeftSide: when overlapping edges are joined, a spike will created //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up //on the discard Side as either may still be needed for other joins ... IntPoint Pt; bool DiscardLeftSide; if (op1->Pt.X >= Left && op1->Pt.X <= Right) { Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); } else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) { Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) { Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; } else { Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); } j->OutPt1 = op1; j->OutPt2 = op2; return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { //nb: For non-horizontal joins ... // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y // 2. Jr.OutPt1.Pt > Jr.OffPt.Y //make sure the polygons are correctly oriented ... op1b = op1->Next; while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); if (Reverse1) { op1b = op1->Prev; while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; if ((op1b->Pt.Y > op1->Pt.Y) || !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; }; op2b = op2->Next; while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); if (Reverse2) { op2b = op2->Prev; while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; if ((op2b->Pt.Y > op2->Pt.Y) || !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; } if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; if (Reverse1) { op1b = DupOutPt(op1, false); op2b = DupOutPt(op2, true); op1->Prev = op2; op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } else { op1b = DupOutPt(op1, true); op2b = DupOutPt(op2, false); op1->Next = op2; op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } } } //---------------------------------------------------------------------- static OutRec* ParseFirstLeft(OutRec* FirstLeft) { while (FirstLeft && !FirstLeft->Pts) FirstLeft = FirstLeft->FirstLeft; return FirstLeft; } //------------------------------------------------------------------------------ void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { //tests if NewOutRec contains the polygon before reassigning FirstLeft for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec* outRec = m_PolyOuts[i]; if (!outRec->Pts || !outRec->FirstLeft) continue; OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); if (firstLeft == OldOutRec) { if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) outRec->FirstLeft = NewOutRec; } } } //---------------------------------------------------------------------- void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) { //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec* outRec = m_PolyOuts[i]; if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; } } //---------------------------------------------------------------------- void Clipper::JoinCommonEdges() { for (JoinList::size_type i = 0; i < m_Joins.size(); i++) { Join* join = m_Joins[i]; OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); if (!outRec1->Pts || !outRec2->Pts) continue; if (outRec1->IsOpen || outRec2->IsOpen) continue; //get the polygon fragment with the correct hole state (FirstLeft) //before calling JoinPoints() ... OutRec *holeStateRec; if (outRec1 == outRec2) holeStateRec = outRec1; else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); if (!JoinPoints(join, outRec1, outRec2)) continue; if (outRec1 == outRec2) { //instead of joining two polygons, we've just created a new one by //splitting one polygon into two. outRec1->Pts = join->OutPt1; outRec1->BottomPt = 0; outRec2 = CreateOutRec(); outRec2->Pts = join->OutPt2; //update all OutRec2.Pts Idx's ... UpdateOutPtIdxs(*outRec2); //We now need to check every OutRec.FirstLeft pointer. If it points //to OutRec1 it may need to point to OutRec2 instead ... if (m_UsingPolyTree) for (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++) { OutRec* oRec = m_PolyOuts[j]; if (!oRec->Pts || ParseFirstLeft(oRec->FirstLeft) != outRec1 || oRec->IsHole == outRec1->IsHole) continue; if (Poly2ContainsPoly1(oRec->Pts, join->OutPt2)) oRec->FirstLeft = outRec2; } if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { //outRec2 is contained by outRec1 ... outRec2->IsHole = !outRec1->IsHole; outRec2->FirstLeft = outRec1; //fixup FirstLeft pointers that may need reassigning to OutRec1 if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) ReversePolyPtLinks(outRec2->Pts); } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { //outRec1 is contained by outRec2 ... outRec2->IsHole = outRec1->IsHole; outRec1->IsHole = !outRec2->IsHole; outRec2->FirstLeft = outRec1->FirstLeft; outRec1->FirstLeft = outRec2; //fixup FirstLeft pointers that may need reassigning to OutRec1 if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) ReversePolyPtLinks(outRec1->Pts); } else { //the 2 polygons are completely separate ... outRec2->IsHole = outRec1->IsHole; outRec2->FirstLeft = outRec1->FirstLeft; //fixup FirstLeft pointers that may need reassigning to OutRec2 if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); } } else { //joined 2 polygons together ... outRec2->Pts = 0; outRec2->BottomPt = 0; outRec2->Idx = outRec1->Idx; outRec1->IsHole = holeStateRec->IsHole; if (holeStateRec == outRec2) outRec1->FirstLeft = outRec2->FirstLeft; outRec2->FirstLeft = outRec1; //fixup FirstLeft pointers that may need reassigning to OutRec1 if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); } } } //------------------------------------------------------------------------------ // ClipperOffset support functions ... //------------------------------------------------------------------------------ DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { if(pt2.X == pt1.X && pt2.Y == pt1.Y) return DoublePoint(0, 0); double Dx = (double)(pt2.X - pt1.X); double dy = (double)(pt2.Y - pt1.Y); double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); Dx *= f; dy *= f; return DoublePoint(dy, -Dx); } //------------------------------------------------------------------------------ // ClipperOffset class //------------------------------------------------------------------------------ ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) { this->MiterLimit = miterLimit; this->ArcTolerance = arcTolerance; m_lowest.X = -1; } //------------------------------------------------------------------------------ ClipperOffset::~ClipperOffset() { Clear(); } //------------------------------------------------------------------------------ void ClipperOffset::Clear() { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) delete m_polyNodes.Childs[i]; m_polyNodes.Childs.clear(); m_lowest.X = -1; } //------------------------------------------------------------------------------ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) { int highI = (int)path.size() - 1; if (highI < 0) return; PolyNode* newNode = new PolyNode(); newNode->m_jointype = joinType; newNode->m_endtype = endType; //strip duplicate points from path and also get index to the lowest point ... if (endType == etClosedLine || endType == etClosedPolygon) while (highI > 0 && path[0] == path[highI]) highI--; newNode->Contour.reserve(highI + 1); newNode->Contour.push_back(path[0]); int j = 0, k = 0; for (int i = 1; i <= highI; i++) if (newNode->Contour[j] != path[i]) { j++; newNode->Contour.push_back(path[i]); if (path[i].Y > newNode->Contour[k].Y || (path[i].Y == newNode->Contour[k].Y && path[i].X < newNode->Contour[k].X)) k = j; } if (endType == etClosedPolygon && j < 2) { delete newNode; return; } m_polyNodes.AddChild(*newNode); //if this path's lowest pt is lower than all the others then update m_lowest if (endType != etClosedPolygon) return; if (m_lowest.X < 0) m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); else { IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; if (newNode->Contour[k].Y > ip.Y || (newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X)) m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); } } //------------------------------------------------------------------------------ void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) { for (Paths::size_type i = 0; i < paths.size(); ++i) AddPath(paths[i], joinType, endType); } //------------------------------------------------------------------------------ void ClipperOffset::FixOrientations() { //fixup orientations of all closed paths if the orientation of the //closed path with the lowermost vertex is wrong ... if (m_lowest.X >= 0 && !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedPolygon || (node.m_endtype == etClosedLine && Orientation(node.Contour))) ReversePath(node.Contour); } } else { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) ReversePath(node.Contour); } } } //------------------------------------------------------------------------------ void ClipperOffset::Execute(Paths& solution, double delta) { solution.clear(); FixOrientations(); DoOffset(delta); //now clean up 'corners' ... Clipper clpr; clpr.AddPaths(m_destPolys, ptSubject, true); if (delta > 0) { clpr.Execute(ctUnion, solution, pftPositive, pftPositive); } else { IntRect r = clpr.GetBounds(); Path outer(4); outer[0] = IntPoint(r.left - 10, r.bottom + 10); outer[1] = IntPoint(r.right + 10, r.bottom + 10); outer[2] = IntPoint(r.right + 10, r.top - 10); outer[3] = IntPoint(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); if (solution.size() > 0) solution.erase(solution.begin()); } } //------------------------------------------------------------------------------ void ClipperOffset::Execute(PolyTree& solution, double delta) { solution.Clear(); FixOrientations(); DoOffset(delta); //now clean up 'corners' ... Clipper clpr; clpr.AddPaths(m_destPolys, ptSubject, true); if (delta > 0) { clpr.Execute(ctUnion, solution, pftPositive, pftPositive); } else { IntRect r = clpr.GetBounds(); Path outer(4); outer[0] = IntPoint(r.left - 10, r.bottom + 10); outer[1] = IntPoint(r.right + 10, r.bottom + 10); outer[2] = IntPoint(r.right + 10, r.top - 10); outer[3] = IntPoint(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); //remove the outer PolyNode rectangle ... if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) { PolyNode* outerNode = solution.Childs[0]; solution.Childs.reserve(outerNode->ChildCount()); solution.Childs[0] = outerNode->Childs[0]; solution.Childs[0]->Parent = outerNode->Parent; for (int i = 1; i < outerNode->ChildCount(); ++i) solution.AddChild(*outerNode->Childs[i]); } else solution.Clear(); } } //------------------------------------------------------------------------------ void ClipperOffset::DoOffset(double delta) { m_destPolys.clear(); m_delta = delta; //if Zero offset, just copy any CLOSED polygons to m_p and return ... if (NEAR_ZERO(delta)) { m_destPolys.reserve(m_polyNodes.ChildCount()); for (int i = 0; i < m_polyNodes.ChildCount(); i++) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedPolygon) m_destPolys.push_back(node.Contour); } return; } //see offset_triginometry3.svg in the documentation folder ... if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); else m_miterLim = 0.5; double y; if (ArcTolerance <= 0.0) y = def_arc_tolerance; else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) y = std::fabs(delta) * def_arc_tolerance; else y = ArcTolerance; //see offset_triginometry2.svg in the documentation folder ... double steps = pi / std::acos(1 - y / std::fabs(delta)); if (steps > std::fabs(delta) * pi) steps = std::fabs(delta) * pi; //ie excessive precision check m_sin = std::sin(two_pi / steps); m_cos = std::cos(two_pi / steps); m_StepsPerRad = steps / two_pi; if (delta < 0.0) m_sin = -m_sin; m_destPolys.reserve(m_polyNodes.ChildCount() * 2); for (int i = 0; i < m_polyNodes.ChildCount(); i++) { PolyNode& node = *m_polyNodes.Childs[i]; m_srcPoly = node.Contour; int len = (int)m_srcPoly.size(); if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) continue; m_destPoly.clear(); if (len == 1) { if (node.m_jointype == jtRound) { double X = 1.0, Y = 0.0; for (cInt j = 1; j <= steps; j++) { m_destPoly.push_back(IntPoint( Round(m_srcPoly[0].X + X * delta), Round(m_srcPoly[0].Y + Y * delta))); double X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } } else { double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { m_destPoly.push_back(IntPoint( Round(m_srcPoly[0].X + X * delta), Round(m_srcPoly[0].Y + Y * delta))); if (X < 0) X = 1; else if (Y < 0) Y = 1; else X = -1; } } m_destPolys.push_back(m_destPoly); continue; } //build m_normals ... m_normals.clear(); m_normals.reserve(len); for (int j = 0; j < len - 1; ++j) m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); else m_normals.push_back(DoublePoint(m_normals[len - 2])); if (node.m_endtype == etClosedPolygon) { int k = len - 1; for (int j = 0; j < len; ++j) OffsetPoint(j, k, node.m_jointype); m_destPolys.push_back(m_destPoly); } else if (node.m_endtype == etClosedLine) { int k = len - 1; for (int j = 0; j < len; ++j) OffsetPoint(j, k, node.m_jointype); m_destPolys.push_back(m_destPoly); m_destPoly.clear(); //re-build m_normals ... DoublePoint n = m_normals[len -1]; for (int j = len - 1; j > 0; j--) m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); m_normals[0] = DoublePoint(-n.X, -n.Y); k = 0; for (int j = len - 1; j >= 0; j--) OffsetPoint(j, k, node.m_jointype); m_destPolys.push_back(m_destPoly); } else { int k = 0; for (int j = 1; j < len - 1; ++j) OffsetPoint(j, k, node.m_jointype); IntPoint pt1; if (node.m_endtype == etOpenButt) { int j = len - 1; pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); m_destPoly.push_back(pt1); pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); m_destPoly.push_back(pt1); } else { int j = len - 1; k = len - 2; m_sinA = 0; m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); if (node.m_endtype == etOpenSquare) DoSquare(j, k); else DoRound(j, k); } //re-build m_normals ... for (int j = len - 1; j > 0; j--) m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); k = len - 1; for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); if (node.m_endtype == etOpenButt) { pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); m_destPoly.push_back(pt1); pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); m_destPoly.push_back(pt1); } else { k = 1; m_sinA = 0; if (node.m_endtype == etOpenSquare) DoSquare(0, 1); else DoRound(0, 1); } m_destPolys.push_back(m_destPoly); } } } //------------------------------------------------------------------------------ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) { //cross product ... m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); if (std::fabs(m_sinA * m_delta) < 1.0) { //dot product ... double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); if (cosA > 0) // angle => 0 degrees { m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); return; } //else angle => 180 degrees } else if (m_sinA > 1.0) m_sinA = 1.0; else if (m_sinA < -1.0) m_sinA = -1.0; if (m_sinA * m_delta < 0) { m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); m_destPoly.push_back(m_srcPoly[j]); m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); } else switch (jointype) { case jtMiter: { double r = 1 + (m_normals[j].X * m_normals[k].X + m_normals[j].Y * m_normals[k].Y); if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); break; } case jtSquare: DoSquare(j, k); break; case jtRound: DoRound(j, k); break; } k = j; } //------------------------------------------------------------------------------ void ClipperOffset::DoSquare(int j, int k) { double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); } //------------------------------------------------------------------------------ void ClipperOffset::DoMiter(int j, int k, double r) { double q = m_delta / r; m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); } //------------------------------------------------------------------------------ void ClipperOffset::DoRound(int j, int k) { double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); double X = m_normals[k].X, Y = m_normals[k].Y, X2; for (int i = 0; i < steps; ++i) { m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + X * m_delta), Round(m_srcPoly[j].Y + Y * m_delta))); X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + m_normals[j].X * m_delta), Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); } //------------------------------------------------------------------------------ // Miscellaneous public functions //------------------------------------------------------------------------------ void Clipper::DoSimplePolygons() { PolyOutList::size_type i = 0; while (i < m_PolyOuts.size()) { OutRec* outrec = m_PolyOuts[i++]; OutPt* op = outrec->Pts; if (!op || outrec->IsOpen) continue; do //for each Pt in Polygon until duplicate found do ... { OutPt* op2 = op->Next; while (op2 != outrec->Pts) { if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) { //split the polygon into two ... OutPt* op3 = op->Prev; OutPt* op4 = op2->Prev; op->Prev = op4; op4->Next = op; op2->Prev = op3; op3->Next = op2; outrec->Pts = op; OutRec* outrec2 = CreateOutRec(); outrec2->Pts = op2; UpdateOutPtIdxs(*outrec2); if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) { //OutRec2 is contained by OutRec1 ... outrec2->IsHole = !outrec->IsHole; outrec2->FirstLeft = outrec; if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) { //OutRec1 is contained by OutRec2 ... outrec2->IsHole = outrec->IsHole; outrec->IsHole = !outrec2->IsHole; outrec2->FirstLeft = outrec->FirstLeft; outrec->FirstLeft = outrec2; if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); } else { //the 2 polygons are separate ... outrec2->IsHole = outrec->IsHole; outrec2->FirstLeft = outrec->FirstLeft; if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); } op2 = op; //ie get ready for the Next iteration } op2 = op2->Next; } op = op->Next; } while (op != outrec->Pts); } } //------------------------------------------------------------------------------ void ReversePath(Path& p) { std::reverse(p.begin(), p.end()); } //------------------------------------------------------------------------------ void ReversePaths(Paths& p) { for (Paths::size_type i = 0; i < p.size(); ++i) ReversePath(p[i]); } //------------------------------------------------------------------------------ void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) { Clipper c; c.StrictlySimple(true); c.AddPath(in_poly, ptSubject, true); c.Execute(ctUnion, out_polys, fillType, fillType); } //------------------------------------------------------------------------------ void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) { Clipper c; c.StrictlySimple(true); c.AddPaths(in_polys, ptSubject, true); c.Execute(ctUnion, out_polys, fillType, fillType); } //------------------------------------------------------------------------------ void SimplifyPolygons(Paths &polys, PolyFillType fillType) { SimplifyPolygons(polys, polys, fillType); } //------------------------------------------------------------------------------ inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) { double Dx = ((double)pt1.X - pt2.X); double dy = ((double)pt1.Y - pt2.Y); return (Dx*Dx + dy*dy); } //------------------------------------------------------------------------------ double DistanceFromLineSqrd( const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) { //The equation of a line in general form (Ax + By + C = 0) //given 2 points (x¹,y¹) & (x²,y²) is ... //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) //see http://en.wikipedia.org/wiki/Perpendicular_distance double A = double(ln1.Y - ln2.Y); double B = double(ln2.X - ln1.X); double C = A * ln1.X + B * ln1.Y; C = A * pt.X + B * pt.Y - C; return (C * C) / (A * A + B * B); } //--------------------------------------------------------------------------- bool SlopesNearCollinear(const IntPoint& pt1, const IntPoint& pt2, const IntPoint& pt3, double distSqrd) { //this function is more accurate when the point that's geometrically //between the other 2 points is the one that's tested for distance. //ie makes it more likely to pick up 'spikes' ... if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) { if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; else return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; } else { if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; else return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; } } //------------------------------------------------------------------------------ bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { double Dx = (double)pt1.X - pt2.X; double dy = (double)pt1.Y - pt2.Y; return ((Dx * Dx) + (dy * dy) <= distSqrd); } //------------------------------------------------------------------------------ OutPt* ExcludeOp(OutPt* op) { OutPt* result = op->Prev; result->Next = op->Next; op->Next->Prev = result; result->Idx = 0; return result; } //------------------------------------------------------------------------------ void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) { //distance = proximity in units/pixels below which vertices //will be stripped. Default ~= sqrt(2). size_t size = in_poly.size(); if (size == 0) { out_poly.clear(); return; } OutPt* outPts = new OutPt[size]; for (size_t i = 0; i < size; ++i) { outPts[i].Pt = in_poly[i]; outPts[i].Next = &outPts[(i + 1) % size]; outPts[i].Next->Prev = &outPts[i]; outPts[i].Idx = 0; } double distSqrd = distance * distance; OutPt* op = &outPts[0]; while (op->Idx == 0 && op->Next != op->Prev) { if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) { op = ExcludeOp(op); size--; } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) { ExcludeOp(op->Next); op = ExcludeOp(op); size -= 2; } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) { op = ExcludeOp(op); size--; } else { op->Idx = 1; op = op->Next; } } if (size < 3) size = 0; out_poly.resize(size); for (size_t i = 0; i < size; ++i) { out_poly[i] = op->Pt; op = op->Next; } delete [] outPts; } //------------------------------------------------------------------------------ void CleanPolygon(Path& poly, double distance) { CleanPolygon(poly, poly, distance); } //------------------------------------------------------------------------------ void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) { for (Paths::size_type i = 0; i < in_polys.size(); ++i) CleanPolygon(in_polys[i], out_polys[i], distance); } //------------------------------------------------------------------------------ void CleanPolygons(Paths& polys, double distance) { CleanPolygons(polys, polys, distance); } //------------------------------------------------------------------------------ void Minkowski(const Path& poly, const Path& path, Paths& solution, bool isSum, bool isClosed) { int delta = (isClosed ? 1 : 0); size_t polyCnt = poly.size(); size_t pathCnt = path.size(); Paths pp; pp.reserve(pathCnt); if (isSum) for (size_t i = 0; i < pathCnt; ++i) { Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); pp.push_back(p); } else for (size_t i = 0; i < pathCnt; ++i) { Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); pp.push_back(p); } solution.clear(); solution.reserve((pathCnt + delta) * (polyCnt + 1)); for (size_t i = 0; i < pathCnt - 1 + delta; ++i) for (size_t j = 0; j < polyCnt; ++j) { Path quad; quad.reserve(4); quad.push_back(pp[i % pathCnt][j % polyCnt]); quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); if (!Orientation(quad)) ReversePath(quad); solution.push_back(quad); } } //------------------------------------------------------------------------------ void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) { Minkowski(pattern, path, solution, true, pathIsClosed); Clipper c; c.AddPaths(solution, ptSubject, true); c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ void TranslatePath(const Path& input, Path& output, const IntPoint delta) { //precondition: input != output output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); } //------------------------------------------------------------------------------ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) { Clipper c; for (size_t i = 0; i < paths.size(); ++i) { Paths tmp; Minkowski(pattern, paths[i], tmp, true, pathIsClosed); c.AddPaths(tmp, ptSubject, true); if (pathIsClosed) { Path tmp2; TranslatePath(paths[i], tmp2, pattern[0]); c.AddPath(tmp2, ptClip, true); } } c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) { Minkowski(poly1, poly2, solution, false, true); Clipper c; c.AddPaths(solution, ptSubject, true); c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ enum NodeType {ntAny, ntOpen, ntClosed}; void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) { bool match = true; if (nodetype == ntClosed) match = !polynode.IsOpen(); else if (nodetype == ntOpen) return; if (!polynode.Contour.empty() && match) paths.push_back(polynode.Contour); for (int i = 0; i < polynode.ChildCount(); ++i) AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); } //------------------------------------------------------------------------------ void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) { paths.resize(0); paths.reserve(polytree.Total()); AddPolyNodeToPaths(polytree, ntAny, paths); } //------------------------------------------------------------------------------ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) { paths.resize(0); paths.reserve(polytree.Total()); AddPolyNodeToPaths(polytree, ntClosed, paths); } //------------------------------------------------------------------------------ void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) { paths.resize(0); paths.reserve(polytree.Total()); //Open paths are top level only, so ... for (int i = 0; i < polytree.ChildCount(); ++i) if (polytree.Childs[i]->IsOpen()) paths.push_back(polytree.Childs[i]->Contour); } //------------------------------------------------------------------------------ std::ostream& operator <<(std::ostream &s, const IntPoint &p) { s << "(" << p.X << "," << p.Y << ")"; return s; } //------------------------------------------------------------------------------ std::ostream& operator <<(std::ostream &s, const Path &p) { if (p.empty()) return s; Path::size_type last = p.size() -1; for (Path::size_type i = 0; i < last; i++) s << "(" << p[i].X << "," << p[i].Y << "), "; s << "(" << p[last].X << "," << p[last].Y << ")\n"; return s; } //------------------------------------------------------------------------------ std::ostream& operator <<(std::ostream &s, const Paths &p) { for (Paths::size_type i = 0; i < p.size(); i++) s << p[i]; s << "\n"; return s; } //------------------------------------------------------------------------------ } //ClipperLib namespace Slic3r-1.2.9/xs/src/clipper.hpp000066400000000000000000000356471254023100400162600ustar00rootroot00000000000000/******************************************************************************* * * * Author : Angus Johnson * * Version : 6.2.9 * * Date : 16 February 2015 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * * http://www.boost.org/LICENSE_1_0.txt * * * * Attributions: * * The code in this library is an extension of Bala Vatti's clipping algorithm: * * "A generic solution to polygon clipping" * * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * * http://portal.acm.org/citation.cfm?id=129906 * * * * Computer graphics and geometric modeling: implementation and algorithms * * By Max K. Agoston * * Springer; 1 edition (January 4, 2005) * * http://books.google.com/books?q=vatti+clipping+agoston * * * * See also: * * "Polygon Offsetting by Computing Winding Numbers" * * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * * September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ #ifndef clipper_hpp #define clipper_hpp #define CLIPPER_VERSION "6.2.6" //use_int32: When enabled 32bit ints are used instead of 64bit ints. This //improve performance but coordinate values are limited to the range +/- 46340 //#define use_int32 //use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. //#define use_xyz //use_lines: Enables line clipping. Adds a very minor cost to performance. #define use_lines //use_deprecated: Enables temporary support for the obsolete functions //#define use_deprecated #include #include #include #include #include #include #include #include #include namespace ClipperLib { enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; enum PolyType { ptSubject, ptClip }; //By far the most widely used winding rules for polygon filling are //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) //see http://glprogramming.com/red/chapter11.html enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; #ifdef use_int32 typedef int cInt; static cInt const loRange = 0x7FFF; static cInt const hiRange = 0x7FFF; #else typedef signed long long cInt; static cInt const loRange = 0x3FFFFFFF; static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; typedef signed long long long64; //used by Int128 class typedef unsigned long long ulong64; #endif struct IntPoint { cInt X; cInt Y; #ifdef use_xyz cInt Z; IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; #else IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; #endif friend inline bool operator== (const IntPoint& a, const IntPoint& b) { return a.X == b.X && a.Y == b.Y; } friend inline bool operator!= (const IntPoint& a, const IntPoint& b) { return a.X != b.X || a.Y != b.Y; } }; //------------------------------------------------------------------------------ typedef std::vector< IntPoint > Path; typedef std::vector< Path > Paths; inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} std::ostream& operator <<(std::ostream &s, const IntPoint &p); std::ostream& operator <<(std::ostream &s, const Path &p); std::ostream& operator <<(std::ostream &s, const Paths &p); struct DoublePoint { double X; double Y; DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} }; //------------------------------------------------------------------------------ #ifdef use_xyz typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); #endif enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; enum JoinType {jtSquare, jtRound, jtMiter}; enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; class PolyNode; typedef std::vector< PolyNode* > PolyNodes; class PolyNode { public: PolyNode(); virtual ~PolyNode(){}; Path Contour; PolyNodes Childs; PolyNode* Parent; PolyNode* GetNext() const; bool IsHole() const; bool IsOpen() const; int ChildCount() const; private: unsigned Index; //node index in Parent.Childs bool m_IsOpen; JoinType m_jointype; EndType m_endtype; PolyNode* GetNextSiblingUp() const; void AddChild(PolyNode& child); friend class Clipper; //to access Index friend class ClipperOffset; }; class PolyTree: public PolyNode { public: ~PolyTree(){Clear();}; PolyNode* GetFirst() const; void Clear(); int Total() const; private: PolyNodes AllNodes; friend class Clipper; //to access AllNodes }; bool Orientation(const Path &poly); double Area(const Path &poly); int PointInPolygon(const IntPoint &pt, const Path &path); void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); void CleanPolygon(Path& poly, double distance = 1.415); void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); void CleanPolygons(Paths& polys, double distance = 1.415); void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); void ReversePath(Path& p); void ReversePaths(Paths& p); struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; //enums that are used internally ... enum EdgeSide { esLeft = 1, esRight = 2}; //forward declarations (for stuff used internally) ... struct TEdge; struct IntersectNode; struct LocalMinimum; struct OutPt; struct OutRec; struct Join; typedef std::vector < OutRec* > PolyOutList; typedef std::vector < TEdge* > EdgeList; typedef std::vector < Join* > JoinList; typedef std::vector < IntersectNode* > IntersectList; //------------------------------------------------------------------------------ //ClipperBase is the ancestor to the Clipper class. It should not be //instantiated directly. This class simply abstracts the conversion of sets of //polygon coordinates into edge objects that are stored in a LocalMinima list. class ClipperBase { public: ClipperBase(); virtual ~ClipperBase(); bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); virtual void Clear(); IntRect GetBounds(); bool PreserveCollinear() {return m_PreserveCollinear;}; void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; protected: void DisposeLocalMinimaList(); TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); void PopLocalMinima(); virtual void Reset(); TEdge* ProcessBound(TEdge* E, bool IsClockwise); TEdge* DescendToMin(TEdge *&E); void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); typedef std::vector MinimaList; MinimaList::iterator m_CurrentLM; MinimaList m_MinimaList; bool m_UseFullRange; EdgeList m_edges; bool m_PreserveCollinear; bool m_HasOpenPaths; }; //------------------------------------------------------------------------------ class Clipper : public virtual ClipperBase { public: Clipper(int initOptions = 0); ~Clipper(); bool Execute(ClipType clipType, Paths &solution, PolyFillType fillType = pftEvenOdd); bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, PolyFillType clipFillType); bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType = pftEvenOdd); bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType, PolyFillType clipFillType); bool ReverseSolution() { return m_ReverseOutput; }; void ReverseSolution(bool value) {m_ReverseOutput = value;}; bool StrictlySimple() {return m_StrictSimple;}; void StrictlySimple(bool value) {m_StrictSimple = value;}; //set the callback function for z value filling on intersections (otherwise Z is 0) #ifdef use_xyz void ZFillFunction(ZFillCallback zFillFunc); #endif protected: void Reset(); virtual bool ExecuteInternal(); private: PolyOutList m_PolyOuts; JoinList m_Joins; JoinList m_GhostJoins; IntersectList m_IntersectList; ClipType m_ClipType; typedef std::priority_queue ScanbeamList; ScanbeamList m_Scanbeam; typedef std::list MaximaList; MaximaList m_Maxima; TEdge *m_ActiveEdges; TEdge *m_SortedEdges; bool m_ExecuteLocked; PolyFillType m_ClipFillType; PolyFillType m_SubjFillType; bool m_ReverseOutput; bool m_UsingPolyTree; bool m_StrictSimple; #ifdef use_xyz ZFillCallback m_ZFill; //custom callback #endif void SetWindingCount(TEdge& edge); bool IsEvenOddFillType(const TEdge& edge) const; bool IsEvenOddAltFillType(const TEdge& edge) const; void InsertScanbeam(const cInt Y); cInt PopScanbeam(); void InsertLocalMinimaIntoAEL(const cInt botY); void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); void AddEdgeToSEL(TEdge *edge); void CopyAELToSEL(); void DeleteFromSEL(TEdge *e); void DeleteFromAEL(TEdge *e); void UpdateEdgeIntoAEL(TEdge *&e); void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); bool IsContributing(const TEdge& edge) const; bool IsTopHorz(const cInt XPos); void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); void DoMaxima(TEdge *e); void ProcessHorizontals(); void ProcessHorizontal(TEdge *horzEdge); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); void AppendPolygon(TEdge *e1, TEdge *e2); void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); OutPt* GetLastOutPt(TEdge *e); void DisposeAllOutRecs(); void DisposeOutRec(PolyOutList::size_type index); bool ProcessIntersections(const cInt topY); void BuildIntersectList(const cInt topY); void ProcessIntersectList(); void ProcessEdgesAtTopOfScanbeam(const cInt topY); void BuildResult(Paths& polys); void BuildResult2(PolyTree& polytree); void SetHoleState(TEdge *e, OutRec *outrec); void DisposeIntersectNodes(); bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); void FixupOutPolyline(OutRec &outrec); bool IsHole(TEdge *e); bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); void FixHoleLinkage(OutRec &outrec); void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); void ClearJoins(); void ClearGhostJoins(); void AddGhostJoin(OutPt *op, const IntPoint offPt); bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); void JoinCommonEdges(); void DoSimplePolygons(); void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); #ifdef use_xyz void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif }; //------------------------------------------------------------------------------ class ClipperOffset { public: ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); ~ClipperOffset(); void AddPath(const Path& path, JoinType joinType, EndType endType); void AddPaths(const Paths& paths, JoinType joinType, EndType endType); void Execute(Paths& solution, double delta); void Execute(PolyTree& solution, double delta); void Clear(); double MiterLimit; double ArcTolerance; private: Paths m_destPolys; Path m_srcPoly; Path m_destPoly; std::vector m_normals; double m_delta, m_sinA, m_sin, m_cos; double m_miterLim, m_StepsPerRad; IntPoint m_lowest; PolyNode m_polyNodes; void FixOrientations(); void DoOffset(double delta); void OffsetPoint(int j, int& k, JoinType jointype); void DoSquare(int j, int k); void DoMiter(int j, int k, double r); void DoRound(int j, int k); }; //------------------------------------------------------------------------------ class clipperException : public std::exception { public: clipperException(const char* description): m_descr(description) {} virtual ~clipperException() throw() {} virtual const char* what() const throw() {return m_descr.c_str();} private: std::string m_descr; }; //------------------------------------------------------------------------------ } //ClipperLib namespace #endif //clipper_hpp Slic3r-1.2.9/xs/src/libslic3r/000077500000000000000000000000001254023100400157605ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/libslic3r/BoundingBox.cpp000066400000000000000000000165051254023100400207110ustar00rootroot00000000000000#include "BoundingBox.hpp" #include namespace Slic3r { template BoundingBoxBase::BoundingBoxBase(const std::vector &points) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); typename std::vector::const_iterator it = points.begin(); this->min.x = this->max.x = it->x; this->min.y = this->max.y = it->y; for (++it; it != points.end(); ++it) { this->min.x = std::min(it->x, this->min.x); this->min.y = std::min(it->y, this->min.y); this->max.x = std::max(it->x, this->max.x); this->max.y = std::max(it->y, this->max.y); } this->defined = true; } template BoundingBoxBase::BoundingBoxBase(const std::vector &points); template BoundingBoxBase::BoundingBoxBase(const std::vector &points); template BoundingBox3Base::BoundingBox3Base(const std::vector &points) : BoundingBoxBase(points) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector::const_iterator it = points.begin(); this->min.z = this->max.z = it->z; for (++it; it != points.end(); ++it) { this->min.z = std::min(it->z, this->min.z); this->max.z = std::max(it->z, this->max.z); } } template BoundingBox3Base::BoundingBox3Base(const std::vector &points); BoundingBox::BoundingBox(const Lines &lines) { Points points; for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { points.push_back(line->a); points.push_back(line->b); } *this = BoundingBox(points); } void BoundingBox::polygon(Polygon* polygon) const { polygon->points.clear(); polygon->points.resize(4); polygon->points[0].x = this->min.x; polygon->points[0].y = this->min.y; polygon->points[1].x = this->max.x; polygon->points[1].y = this->min.y; polygon->points[2].x = this->max.x; polygon->points[2].y = this->max.y; polygon->points[3].x = this->min.x; polygon->points[3].y = this->max.y; } Polygon BoundingBox::polygon() const { Polygon p; this->polygon(&p); return p; } template void BoundingBoxBase::scale(double factor) { this->min.scale(factor); this->max.scale(factor); } template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::merge(const PointClass &point) { if (this->defined) { this->min.x = std::min(point.x, this->min.x); this->min.y = std::min(point.y, this->min.y); this->max.x = std::max(point.x, this->max.x); this->max.y = std::max(point.y, this->max.y); } else { this->min = this->max = point; this->defined = true; } } template void BoundingBoxBase::merge(const Point &point); template void BoundingBoxBase::merge(const Pointf &point); template void BoundingBoxBase::merge(const std::vector &points) { this->merge(BoundingBoxBase(points)); } template void BoundingBoxBase::merge(const Points &points); template void BoundingBoxBase::merge(const Pointfs &points); template void BoundingBoxBase::merge(const BoundingBoxBase &bb) { if (this->defined) { this->min.x = std::min(bb.min.x, this->min.x); this->min.y = std::min(bb.min.y, this->min.y); this->max.x = std::max(bb.max.x, this->max.x); this->max.y = std::max(bb.max.y, this->max.y); } else { this->min = bb.min; this->max = bb.max; this->defined = true; } } template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBox3Base::merge(const PointClass &point) { if (this->defined) { this->min.z = std::min(point.z, this->min.z); this->max.z = std::max(point.z, this->max.z); } BoundingBoxBase::merge(point); } template void BoundingBox3Base::merge(const Pointf3 &point); template void BoundingBox3Base::merge(const std::vector &points) { this->merge(BoundingBox3Base(points)); } template void BoundingBox3Base::merge(const Pointf3s &points); template void BoundingBox3Base::merge(const BoundingBox3Base &bb) { if (this->defined) { this->min.z = std::min(bb.min.z, this->min.z); this->max.z = std::max(bb.max.z, this->max.z); } BoundingBoxBase::merge(bb); } template void BoundingBox3Base::merge(const BoundingBox3Base &bb); template PointClass BoundingBoxBase::size() const { return PointClass(this->max.x - this->min.x, this->max.y - this->min.y); } template Point BoundingBoxBase::size() const; template Pointf BoundingBoxBase::size() const; template PointClass BoundingBox3Base::size() const { return PointClass(this->max.x - this->min.x, this->max.y - this->min.y, this->max.z - this->min.z); } template Pointf3 BoundingBox3Base::size() const; template void BoundingBoxBase::translate(coordf_t x, coordf_t y) { this->min.translate(x, y); this->max.translate(x, y); } template void BoundingBoxBase::translate(coordf_t x, coordf_t y); template void BoundingBoxBase::translate(coordf_t x, coordf_t y); template void BoundingBox3Base::translate(coordf_t x, coordf_t y, coordf_t z) { this->min.translate(x, y, z); this->max.translate(x, y, z); } template void BoundingBox3Base::translate(coordf_t x, coordf_t y, coordf_t z); template void BoundingBoxBase::offset(coordf_t delta) { this->min.translate(-delta, -delta); this->max.translate(delta, delta); } template void BoundingBoxBase::offset(coordf_t delta); template void BoundingBoxBase::offset(coordf_t delta); template void BoundingBox3Base::offset(coordf_t delta) { this->min.translate(-delta, -delta, -delta); this->max.translate(delta, delta, delta); } template void BoundingBox3Base::offset(coordf_t delta); template PointClass BoundingBoxBase::center() const { return PointClass( (this->max.x + this->min.x)/2, (this->max.y + this->min.y)/2 ); } template Point BoundingBoxBase::center() const; template Pointf BoundingBoxBase::center() const; template PointClass BoundingBox3Base::center() const { return PointClass( (this->max.x + this->min.x)/2, (this->max.y + this->min.y)/2, (this->max.z + this->min.z)/2 ); } template Pointf3 BoundingBox3Base::center() const; #ifdef SLIC3RXS REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); #endif } Slic3r-1.2.9/xs/src/libslic3r/BoundingBox.hpp000066400000000000000000000041271254023100400207130ustar00rootroot00000000000000#ifndef slic3r_BoundingBox_hpp_ #define slic3r_BoundingBox_hpp_ #include #include "Point.hpp" #include "Polygon.hpp" namespace Slic3r { typedef Point Size; typedef Point3 Size3; typedef Pointf Sizef; typedef Pointf3 Sizef3; template class BoundingBoxBase { public: PointClass min; PointClass max; bool defined; BoundingBoxBase() : defined(false) {}; BoundingBoxBase(const std::vector &points); void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBoxBase &bb); void scale(double factor); PointClass size() const; void translate(coordf_t x, coordf_t y); void offset(coordf_t delta); PointClass center() const; }; template class BoundingBox3Base : public BoundingBoxBase { public: BoundingBox3Base() : BoundingBoxBase() {}; BoundingBox3Base(const std::vector &points); void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBox3Base &bb); PointClass size() const; void translate(coordf_t x, coordf_t y, coordf_t z); void offset(coordf_t delta); PointClass center() const; }; class BoundingBox : public BoundingBoxBase { public: void polygon(Polygon* polygon) const; Polygon polygon() const; BoundingBox() : BoundingBoxBase() {}; BoundingBox(const Points &points) : BoundingBoxBase(points) {}; BoundingBox(const Lines &lines); }; /* class BoundingBox3 : public BoundingBox3Base {}; */ class BoundingBoxf : public BoundingBoxBase { public: BoundingBoxf() : BoundingBoxBase() {}; BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}; }; class BoundingBoxf3 : public BoundingBox3Base { public: BoundingBoxf3() : BoundingBox3Base() {}; BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/BridgeDetector.cpp000066400000000000000000000304671254023100400213640ustar00rootroot00000000000000#include "BridgeDetector.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include namespace Slic3r { class BridgeDirectionComparator { public: std::map dir_coverage, dir_avg_length; // angle => score BridgeDirectionComparator(double _extrusion_width) : extrusion_width(_extrusion_width) {}; // the best direction is the one causing most lines to be bridged (thus most coverage) // and shortest max line length bool operator() (double a, double b) { double coverage_diff = this->dir_coverage[a] - this->dir_coverage[b]; if (fabs(coverage_diff) < this->extrusion_width) { return (this->dir_avg_length[b] > this->dir_avg_length[a]); } else { return (coverage_diff > 0); } }; private: double extrusion_width; }; BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width) : expolygon(_expolygon), lower_slices(_lower_slices), extrusion_width(_extrusion_width), angle(-1), resolution(PI/36.0) { /* outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors */ Polygons grown; offset((Polygons)this->expolygon, &grown, this->extrusion_width); // detect what edges lie on lower slices by turning bridge contour and holes // into polylines and then clipping them with each lower slice's contour intersection(grown, this->lower_slices.contours(), &this->_edges); #ifdef SLIC3R_DEBUG printf(" bridge has %zu support(s)\n", this->_edges.size()); #endif // detect anchors as intersection between our bridge expolygon and the lower slices // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges intersection(grown, this->lower_slices, &this->_anchors, true); /* if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("bridge.svg", expolygons => [ $self->expolygon ], red_expolygons => $self->lower_slices, polylines => $self->_edges, ); } */ } bool BridgeDetector::detect_angle() { if (this->_edges.empty() || this->_anchors.empty()) return false; /* 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. */ Polygons clip_area; offset(this->expolygon, &clip_area, +this->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 std::vector angles; for (int i = 0; i <= PI/this->resolution; ++i) angles.push_back(i * this->resolution); // we also test angles of each bridge contour { Polygons pp = this->expolygon; for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { Lines lines = p->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) angles.push_back(line->direction()); } } /* we also test angles of each open supporting edge (this finds the optimal angle for C-shaped supports) */ for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) { if (edge->first_point().coincides_with(edge->last_point())) continue; angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); } // remove duplicates double min_resolution = PI/180.0; // 1 degree std::sort(angles.begin(), angles.end()); for (size_t i = 1; i < angles.size(); ++i) { if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { angles.erase(angles.begin() + i); --i; } } /* compare first value with last one and remove the greatest one (PI) in case they are parallel (PI, 0) */ if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) angles.pop_back(); BridgeDirectionComparator bdcomp(this->extrusion_width); double line_increment = this->extrusion_width; bool have_coverage = false; for (std::vector::const_iterator angle = angles.begin(); angle != angles.end(); ++angle) { Polygons my_clip_area = clip_area; ExPolygons my_anchors = this->_anchors; // rotate everything - the center point doesn't matter for (Polygons::iterator it = my_clip_area.begin(); it != my_clip_area.end(); ++it) it->rotate(-*angle, Point(0,0)); for (ExPolygons::iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) it->rotate(-*angle, Point(0,0)); // generate lines in this direction BoundingBox bb; for (ExPolygons::const_iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) bb.merge((Points)*it); Lines lines; for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) lines.push_back(Line(Point(bb.min.x, y), Point(bb.max.x, y))); Lines clipped_lines; intersection(lines, my_clip_area, &clipped_lines); // remove any line not having both endpoints within anchors for (size_t i = 0; i < clipped_lines.size(); ++i) { Line &line = clipped_lines[i]; if (!Slic3r::Geometry::contains(my_anchors, line.a) || !Slic3r::Geometry::contains(my_anchors, line.b)) { clipped_lines.erase(clipped_lines.begin() + i); --i; } } std::vector lengths; double total_length = 0; for (Lines::const_iterator line = clipped_lines.begin(); line != clipped_lines.end(); ++line) { double len = line->length(); lengths.push_back(len); total_length += len; } if (total_length) have_coverage = true; // sum length of bridged lines bdcomp.dir_coverage[*angle] = total_length; /* 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 bdcomp.dir_avg_length[*angle] = !lengths.empty() ? *std::max_element(lengths.begin(), lengths.end()) : 0; } // if no direction produced coverage, then there's no bridge direction if (!have_coverage) return false; // sort directions by score std::sort(angles.begin(), angles.end(), bdcomp); this->angle = angles.front(); if (this->angle >= PI) this->angle -= PI; #ifdef SLIC3R_DEBUG printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); #endif return true; } void BridgeDetector::coverage(Polygons* coverage) const { if (this->angle == -1) return; return this->coverage(angle, coverage); } void BridgeDetector::coverage(double angle, Polygons* coverage) const { // Clone our expolygon and rotate it so that we work with vertical lines. ExPolygon expolygon = this->expolygon; expolygon.rotate(PI/2.0 - angle, Point(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. */ ExPolygons grown; offset(expolygon, &grown, this->extrusion_width/2.0); // Compute trapezoids according to a vertical orientation Polygons trapezoids; for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) it->get_trapezoids2(&trapezoids, PI/2.0); // get anchors, convert them to Polygons and rotate them too Polygons anchors; for (ExPolygons::const_iterator anchor = this->_anchors.begin(); anchor != this->_anchors.end(); ++anchor) { Polygons pp = *anchor; for (Polygons::iterator p = pp.begin(); p != pp.end(); ++p) p->rotate(PI/2.0 - angle, Point(0,0)); anchors.insert(anchors.end(), pp.begin(), pp.end()); } Polygons covered; for (Polygons::const_iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { Lines lines = trapezoid->lines(); Lines supported; intersection(lines, anchors, &supported); // not nice, we need a more robust non-numeric check for (size_t i = 0; i < supported.size(); ++i) { if (supported[i].length() < this->extrusion_width) { supported.erase(supported.begin() + i); i--; } } if (supported.size() >= 2) covered.push_back(*trapezoid); } // merge trapezoids and rotate them back Polygons _coverage; union_(covered, &_coverage); for (Polygons::iterator p = _coverage.begin(); p != _coverage.end(); ++p) p->rotate(-(PI/2.0 - angle), Point(0,0)); // intersect trapezoids with actual bridge area to remove extra margins // and append it to result intersection(_coverage, this->expolygon, coverage); /* 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, ); } */ } /* 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 */ void BridgeDetector::unsupported_edges(Polylines* unsupported) const { if (this->angle == -1) return; return this->unsupported_edges(this->angle, unsupported); } void BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const { // get bridge edges (both contour and holes) Polylines bridge_edges; { Polygons pp = this->expolygon; bridge_edges.insert(bridge_edges.end(), pp.begin(), pp.end()); // this uses split_at_first_point() } // get unsupported edges Polygons grown_lower; offset(this->lower_slices, &grown_lower, +this->extrusion_width); Polylines _unsupported; diff(bridge_edges, grown_lower, &_unsupported); /* 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) */ double angle_tolerance = PI / 180.0 * 5.0; for (Polylines::const_iterator polyline = _unsupported.begin(); polyline != _unsupported.end(); ++polyline) { Lines lines = polyline->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) unsupported->push_back(*line); } } /* 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, ); } */ } #ifdef SLIC3RXS REGISTER_CLASS(BridgeDetector, "BridgeDetector"); #endif } Slic3r-1.2.9/xs/src/libslic3r/BridgeDetector.hpp000066400000000000000000000015431254023100400213620ustar00rootroot00000000000000#ifndef slic3r_BridgeDetector_hpp_ #define slic3r_BridgeDetector_hpp_ #include #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include namespace Slic3r { class BridgeDetector { public: ExPolygon expolygon; ExPolygonCollection lower_slices; double extrusion_width; // scaled double resolution; double angle; BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); bool detect_angle(); void coverage(Polygons* coverage) const; void coverage(double angle, Polygons* coverage) const; void unsupported_edges(Polylines* unsupported) const; void unsupported_edges(double angle, Polylines* unsupported) const; private: Polylines _edges; // representing the supporting edges ExPolygons _anchors; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/ClipperUtils.cpp000066400000000000000000000557501254023100400211170ustar00rootroot00000000000000#include "ClipperUtils.hpp" #include "Geometry.hpp" namespace Slic3r { //----------------------------------------------------------- // legacy code from Clipper documentation void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons* expolygons) { size_t cnt = expolygons->size(); expolygons->resize(cnt + 1); ClipperPath_to_Slic3rMultiPoint(polynode.Contour, &(*expolygons)[cnt].contour); (*expolygons)[cnt].holes.resize(polynode.ChildCount()); for (int i = 0; i < polynode.ChildCount(); ++i) { ClipperPath_to_Slic3rMultiPoint(polynode.Childs[i]->Contour, &(*expolygons)[cnt].holes[i]); //Add outer polygons contained by (nested within) holes ... for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j) AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons); } } void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons* expolygons) { expolygons->clear(); for (int i = 0; i < polytree.ChildCount(); ++i) AddOuterPolyNodeToExPolygons(*polytree.Childs[i], expolygons); } //----------------------------------------------------------- template void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output) { output->points.clear(); for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) { output->points.push_back(Slic3r::Point( (*pit).X, (*pit).Y )); } } template void ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input, T* output) { output->clear(); for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) { typename T::value_type p; ClipperPath_to_Slic3rMultiPoint(*it, &p); output->push_back(p); } } void ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolygons* output) { // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // perform union clipper.AddPaths(input, ClipperLib::ptSubject, true); 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); } void Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path* output) { output->clear(); for (Slic3r::Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) { output->push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); } } template void Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output) { output->clear(); for (typename T::const_iterator it = input.begin(); it != input.end(); ++it) { ClipperLib::Path p; Slic3rMultiPoint_to_ClipperPath(*it, &p); output->push_back(p); } } void scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale) { for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) { for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { (*pit).X *= scale; (*pit).Y *= scale; } } } void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // read input ClipperLib::Paths input; Slic3rMultiPoints_to_ClipperPaths(polygons, &input); // scale input scaleClipperPolygons(input, scale); // perform offset ClipperLib::ClipperOffset co; if (joinType == jtRound) { co.ArcTolerance = miterLimit; } else { co.MiterLimit = miterLimit; } co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); co.Execute(*retval, (delta*scale)); // unscale output scaleClipperPolygons(*retval, 1/scale); } void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset ClipperLib::Paths output; offset(polygons, &output, delta, scale, joinType, miterLimit); // convert into ExPolygons ClipperPaths_to_Slic3rMultiPoints(output, retval); } void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // read input ClipperLib::Paths input; Slic3rMultiPoints_to_ClipperPaths(polylines, &input); // scale input scaleClipperPolygons(input, scale); // perform offset ClipperLib::ClipperOffset co; if (joinType == jtRound) { co.ArcTolerance = miterLimit; } else { co.MiterLimit = miterLimit; } co.AddPaths(input, joinType, ClipperLib::etOpenButt); co.Execute(*retval, (delta*scale)); // unscale output scaleClipperPolygons(*retval, 1/scale); } void offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset ClipperLib::Paths output; offset(polylines, &output, delta, scale, joinType, miterLimit); // convert into ExPolygons ClipperPaths_to_Slic3rMultiPoints(output, retval); } void offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset Slic3r::ExPolygons expp; offset(surface.expolygon, &expp, delta, scale, joinType, miterLimit); // clone the input surface for each expolygon we got retval->clear(); retval->reserve(expp.size()); for (ExPolygons::iterator it = expp.begin(); it != expp.end(); ++it) { Surface s = surface; // clone s.expolygon = *it; retval->push_back(s); } } void offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset ClipperLib::Paths output; offset(polygons, &output, delta, scale, joinType, miterLimit); // convert into ExPolygons ClipperPaths_to_Slic3rExPolygons(output, retval); } void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // read input ClipperLib::Paths input; Slic3rMultiPoints_to_ClipperPaths(polygons, &input); // scale input scaleClipperPolygons(input, scale); // prepare ClipperOffset object ClipperLib::ClipperOffset co; if (joinType == jtRound) { co.ArcTolerance = miterLimit; } else { co.MiterLimit = miterLimit; } // perform first offset ClipperLib::Paths output1; co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); co.Execute(output1, (delta1*scale)); // perform second offset co.Clear(); co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); co.Execute(*retval, (delta2*scale)); // unscale output scaleClipperPolygons(*retval, 1/scale); } void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset ClipperLib::Paths output; offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit); // convert into ExPolygons ClipperPaths_to_Slic3rMultiPoints(output, retval); } void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset ClipperLib::Paths output; offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit); // convert into ExPolygons ClipperPaths_to_Slic3rExPolygons(output, retval); } template void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T* retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { // read input 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); } else { safety_offset(&input_clip); } } // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // add polygons 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 bool safety_offset_) { // read input 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); clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation clipper.Execute(clipType, *retval, fillType, fillType); } void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_) { // perform operation ClipperLib::Paths output; _clipper_do(clipType, subject, clip, &output, ClipperLib::pftNonZero, safety_offset_); // convert into Polygons 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; _clipper_do(clipType, subject, clip, &polytree, ClipperLib::pftNonZero, safety_offset_); // convert into ExPolygons PolyTreeToExPolygons(polytree, retval); } void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_) { // perform operation ClipperLib::PolyTree polytree; _clipper_do(clipType, subject, clip, &polytree, ClipperLib::pftNonZero, safety_offset_); // convert into Polylines ClipperLib::Paths output; ClipperLib::PolyTreeToPaths(polytree, output); ClipperPaths_to_Slic3rMultiPoints(output, retval); } void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_) { // convert Lines to Polylines Slic3r::Polylines polylines; polylines.reserve(subject.size()); for (Slic3r::Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) polylines.push_back(*line); // perform operation _clipper(clipType, polylines, clip, &polylines, safety_offset_); // convert Polylines to Lines for (Slic3r::Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) retval->push_back(*polyline); } 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 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(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void diff(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); template void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_) { Slic3r::Polygons pp; for (Slic3r::ExPolygons::const_iterator ex = clip.begin(); ex != clip.end(); ++ex) { Slic3r::Polygons ppp = *ex; pp.insert(pp.end(), ppp.begin(), ppp.end()); } diff(subject, pp, retval, safety_offset_); } template void diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); template 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(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); template void intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) { SubjectType retval; intersection(subject, clip, &retval, safety_offset_); return !retval.empty(); } template bool intersects(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); template bool intersects(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); template bool intersects(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_); void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_) { _clipper(ClipperLib::ctXor, subject, clip, retval, safety_offset_); } template void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_) { Slic3r::Polygons p; _clipper(ClipperLib::ctUnion, subject, p, retval, safety_offset_); } template void union_(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_); template void union_(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_); void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset) { Polygons pp = subject1; pp.insert(pp.end(), subject2.begin(), subject2.end()); union_(pp, retval, safety_offset); } void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_) { Slic3r::Polygons clip; _clipper_do(ClipperLib::ctUnion, subject, clip, retval, ClipperLib::pftEvenOdd, safety_offset_); } void union_pt_chained(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_) { ClipperLib::PolyTree pt; union_pt(subject, &pt, safety_offset_); traverse_pt(pt.Childs, retval); } static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval) { /* use a nearest neighbor search to order these children TODO: supply start_near to chained_path() too? */ // collect ordering points Points ordering_points; ordering_points.reserve(nodes.size()); for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); ordering_points.push_back(p); } // perform the ordering ClipperLib::PolyNodes ordered_nodes; Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes); // push results recursively for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { // traverse the next depth traverse_pt((*it)->Childs, retval); Polygon p; ClipperPath_to_Slic3rMultiPoint((*it)->Contour, &p); retval->push_back(p); if ((*it)->IsHole()) retval->back().reverse(); // ccw } } void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool preserve_collinear) { // convert into Clipper polygons ClipperLib::Paths input_subject, output; Slic3rMultiPoints_to_ClipperPaths(subject, &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); } 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(*paths, CLIPPER_OFFSET_SCALE); // perform offset (delta = scale 1e-05) ClipperLib::ClipperOffset co; co.MiterLimit = 2; co.AddPaths(*paths, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); co.Execute(*paths, 10.0 * CLIPPER_OFFSET_SCALE); // unscale output scaleClipperPolygons(*paths, 1.0/CLIPPER_OFFSET_SCALE); } /////////////////////// #ifdef SLIC3RXS SV* polynode_children_2_perl(const ClipperLib::PolyNode& node) { AV* av = newAV(); const unsigned int len = node.ChildCount(); if (len > 0) av_extend(av, len-1); for (int i = 0; i < len; ++i) { av_store(av, i, polynode2perl(*node.Childs[i])); } return (SV*)newRV_noinc((SV*)av); } SV* polynode2perl(const ClipperLib::PolyNode& node) { HV* hv = newHV(); Slic3r::Polygon p; ClipperPath_to_Slic3rMultiPoint(node.Contour, &p); if (node.IsHole()) { (void)hv_stores( hv, "hole", Slic3r::perl_to_SV_clone_ref(p) ); } else { (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); } #endif } Slic3r-1.2.9/xs/src/libslic3r/ClipperUtils.hpp000066400000000000000000000134471254023100400211210ustar00rootroot00000000000000#ifndef slic3r_ClipperUtils_hpp_ #define slic3r_ClipperUtils_hpp_ #include #include "clipper.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Surface.hpp" // import these wherever we're included using ClipperLib::jtMiter; using ClipperLib::jtRound; using ClipperLib::jtSquare; namespace Slic3r { #define CLIPPER_OFFSET_SCALE 100000.0 //----------------------------------------------------------- // legacy code from Clipper documentation void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons& expolygons); void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons& expolygons); //----------------------------------------------------------- void Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path* output); template void Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output); template void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output); template void ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input, T* output); void ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolygons* output); void scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale); // offset Polygons void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); // offset Polylines void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); void offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); void offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); void offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); template 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, 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, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval); void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval); template void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); template void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_ = false); template void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_ = false); void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset = false); void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_ = false); 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, bool preserve_collinear = false); void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool preserve_collinear = false); void safety_offset(ClipperLib::Paths* paths); ///////////////// #ifdef SLIC3RXS SV* polynode_children_2_perl(const ClipperLib::PolyNode& node); SV* polynode2perl(const ClipperLib::PolyNode& node); #endif } #endif Slic3r-1.2.9/xs/src/libslic3r/Config.cpp000066400000000000000000000370151254023100400176770ustar00rootroot00000000000000#include "Config.hpp" namespace Slic3r { bool ConfigBase::has(const t_config_option_key opt_key) { return (this->option(opt_key, false) != NULL); } void ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) { // get list of option keys to apply t_config_option_keys opt_keys; other.keys(&opt_keys); // loop through options and apply them for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { ConfigOption* my_opt = this->option(*it, true); if (my_opt == NULL) { if (ignore_nonexistent == false) throw "Attempt to apply non-existent option"; continue; } // not the most efficient way, but easier than casting pointers to subclasses bool res = my_opt->deserialize( other.option(*it)->serialize() ); if (!res) CONFESS("Unexpected failure when deserializing serialized value"); } } bool ConfigBase::equals(ConfigBase &other) { return this->diff(other).empty(); } // this will *ignore* options not present in both configs t_config_option_keys ConfigBase::diff(ConfigBase &other) { t_config_option_keys diff; t_config_option_keys my_keys; this->keys(&my_keys); for (t_config_option_keys::const_iterator opt_key = my_keys.begin(); opt_key != my_keys.end(); ++opt_key) { if (other.has(*opt_key) && other.serialize(*opt_key) != this->serialize(*opt_key)) { diff.push_back(*opt_key); } } return diff; } std::string ConfigBase::serialize(const t_config_option_key opt_key) { ConfigOption* opt = this->option(opt_key); assert(opt != NULL); return opt->serialize(); } bool ConfigBase::set_deserialize(const t_config_option_key opt_key, std::string str) { if (this->def->count(opt_key) == 0) throw "Calling set_deserialize() on unknown option"; ConfigOptionDef* optdef = &(*this->def)[opt_key]; if (!optdef->shortcut.empty()) { for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) { if (!this->set_deserialize(*it, str)) return false; } return true; } ConfigOption* opt = this->option(opt_key, true); assert(opt != NULL); return opt->deserialize(str); } double ConfigBase::get_abs_value(const t_config_option_key opt_key) { ConfigOption* opt = this->option(opt_key, false); if (ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { // get option definition assert(this->def->count(opt_key) != 0); ConfigOptionDef* def = &(*this->def)[opt_key]; // compute absolute value over the absolute value of the base option return optv->get_abs_value(this->get_abs_value(def->ratio_over)); } else if (ConfigOptionFloat* optv = dynamic_cast(opt)) { return optv->value; } else { throw "Not a valid option type for get_abs_value()"; } } double ConfigBase::get_abs_value(const t_config_option_key opt_key, double ratio_over) { // get stored option value ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); assert(opt != NULL); // compute absolute value return opt->get_abs_value(ratio_over); } #ifdef SLIC3RXS SV* ConfigBase::as_hash() { HV* hv = newHV(); t_config_option_keys opt_keys; this->keys(&opt_keys); for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) (void)hv_store( hv, it->c_str(), it->length(), this->get(*it), 0 ); return newRV_noinc((SV*)hv); } SV* ConfigBase::get(t_config_option_key opt_key) { ConfigOption* opt = this->option(opt_key); if (opt == NULL) return &PL_sv_undef; if (ConfigOptionFloat* optv = dynamic_cast(opt)) { return newSVnv(optv->value); } else if (ConfigOptionPercent* optv = dynamic_cast(opt)) { return newSVnv(optv->value); } else if (ConfigOptionFloats* optv = dynamic_cast(opt)) { AV* av = newAV(); av_fill(av, optv->values.size()-1); for (std::vector::iterator it = optv->values.begin(); it != optv->values.end(); ++it) av_store(av, it - optv->values.begin(), newSVnv(*it)); return newRV_noinc((SV*)av); } else if (ConfigOptionInt* optv = dynamic_cast(opt)) { return newSViv(optv->value); } else if (ConfigOptionInts* optv = dynamic_cast(opt)) { AV* av = newAV(); av_fill(av, optv->values.size()-1); for (std::vector::iterator it = optv->values.begin(); it != optv->values.end(); ++it) av_store(av, it - optv->values.begin(), newSViv(*it)); return newRV_noinc((SV*)av); } else if (ConfigOptionString* optv = dynamic_cast(opt)) { // we don't serialize() because that would escape newlines return newSVpvn_utf8(optv->value.c_str(), optv->value.length(), true); } else if (ConfigOptionStrings* optv = dynamic_cast(opt)) { AV* av = newAV(); av_fill(av, optv->values.size()-1); for (std::vector::iterator it = optv->values.begin(); it != optv->values.end(); ++it) av_store(av, it - optv->values.begin(), newSVpvn_utf8(it->c_str(), it->length(), true)); return newRV_noinc((SV*)av); } else if (ConfigOptionPoint* optv = dynamic_cast(opt)) { return perl_to_SV_clone_ref(optv->point); } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { AV* av = newAV(); av_fill(av, optv->values.size()-1); for (Pointfs::iterator it = optv->values.begin(); it != optv->values.end(); ++it) av_store(av, it - optv->values.begin(), perl_to_SV_clone_ref(*it)); return newRV_noinc((SV*)av); } else if (ConfigOptionBool* optv = dynamic_cast(opt)) { return newSViv(optv->value ? 1 : 0); } else if (ConfigOptionBools* optv = dynamic_cast(opt)) { AV* av = newAV(); av_fill(av, optv->values.size()-1); for (std::vector::iterator it = optv->values.begin(); it != optv->values.end(); ++it) av_store(av, it - optv->values.begin(), newSViv(*it ? 1 : 0)); return newRV_noinc((SV*)av); } else { std::string serialized = opt->serialize(); return newSVpvn_utf8(serialized.c_str(), serialized.length(), true); } } SV* ConfigBase::get_at(t_config_option_key opt_key, size_t i) { ConfigOption* opt = this->option(opt_key); if (opt == NULL) return &PL_sv_undef; if (ConfigOptionFloats* optv = dynamic_cast(opt)) { return newSVnv(optv->get_at(i)); } else if (ConfigOptionInts* optv = dynamic_cast(opt)) { return newSViv(optv->get_at(i)); } else if (ConfigOptionStrings* optv = dynamic_cast(opt)) { // we don't serialize() because that would escape newlines std::string val = optv->get_at(i); return newSVpvn_utf8(val.c_str(), val.length(), true); } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { return perl_to_SV_clone_ref(optv->get_at(i)); } else if (ConfigOptionBools* optv = dynamic_cast(opt)) { return newSViv(optv->get_at(i) ? 1 : 0); } else { return &PL_sv_undef; } } bool ConfigBase::set(t_config_option_key opt_key, SV* value) { ConfigOption* opt = this->option(opt_key, true); if (opt == NULL) CONFESS("Trying to set non-existing option"); if (ConfigOptionFloat* optv = dynamic_cast(opt)) { if (!looks_like_number(value)) return false; optv->value = SvNV(value); } else if (ConfigOptionFloats* optv = dynamic_cast(opt)) { std::vector values; AV* av = (AV*)SvRV(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 || !looks_like_number(*elem)) return false; values.push_back(SvNV(*elem)); } optv->values = values; } else if (ConfigOptionInt* optv = dynamic_cast(opt)) { if (!looks_like_number(value)) return false; optv->value = SvIV(value); } else if (ConfigOptionInts* optv = dynamic_cast(opt)) { std::vector values; AV* av = (AV*)SvRV(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 || !looks_like_number(*elem)) return false; values.push_back(SvIV(*elem)); } optv->values = values; } else if (ConfigOptionString* optv = dynamic_cast(opt)) { optv->value = std::string(SvPV_nolen(value), SvCUR(value)); } else if (ConfigOptionStrings* optv = dynamic_cast(opt)) { optv->values.clear(); AV* av = (AV*)SvRV(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(opt)) { return optv->point.from_SV_check(value); } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { std::vector values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); Pointf point; if (elem == NULL || !point.from_SV_check(*elem)) return false; values.push_back(point); } optv->values = values; } else if (ConfigOptionBool* optv = dynamic_cast(opt)) { optv->value = SvTRUE(value); } else if (ConfigOptionBools* optv = dynamic_cast(opt)) { optv->values.clear(); AV* av = (AV*)SvRV(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 { if (!opt->deserialize( std::string(SvPV_nolen(value)) )) return false; } 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); } void ConfigBase::set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize) { if (!this->has(opt_key)) { if (deserialize) { this->set_deserialize(opt_key, value); } else { this->set(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; if (opt != NULL) delete opt; } } DynamicConfig::DynamicConfig (const DynamicConfig& other) { this->def = other.def; this->apply(other, false); } ConfigOption* DynamicConfig::option(const t_config_option_key opt_key, bool create) { if (this->options.count(opt_key) == 0) { if (create) { ConfigOptionDef* optdef = &(*this->def)[opt_key]; ConfigOption* opt; if (optdef->type == coFloat) { opt = new ConfigOptionFloat (); } else if (optdef->type == coFloats) { opt = new ConfigOptionFloats (); } else if (optdef->type == coInt) { opt = new ConfigOptionInt (); } else if (optdef->type == coInts) { opt = new ConfigOptionInts (); } else if (optdef->type == coString) { opt = new ConfigOptionString (); } else if (optdef->type == coStrings) { opt = new ConfigOptionStrings (); } else if (optdef->type == coPercent) { opt = new ConfigOptionPercent (); } else if (optdef->type == coFloatOrPercent) { opt = new ConfigOptionFloatOrPercent (); } else if (optdef->type == coPoint) { opt = new ConfigOptionPoint (); } else if (optdef->type == coPoints) { opt = new ConfigOptionPoints (); } else if (optdef->type == coBool) { opt = new ConfigOptionBool (); } else if (optdef->type == coBools) { opt = new ConfigOptionBools (); } else if (optdef->type == coEnum) { ConfigOptionEnumGeneric* optv = new ConfigOptionEnumGeneric (); optv->keys_map = &optdef->enum_keys_map; opt = static_cast(optv); } else { throw "Unknown option type"; } this->options[opt_key] = opt; return opt; } else { return NULL; } } return this->options[opt_key]; } template T* DynamicConfig::opt(const t_config_option_key opt_key, bool create) { return dynamic_cast(this->option(opt_key, create)); } template ConfigOptionInt* DynamicConfig::opt(const t_config_option_key opt_key, bool create); template ConfigOptionBool* DynamicConfig::opt(const t_config_option_key opt_key, bool create); template ConfigOptionBools* DynamicConfig::opt(const t_config_option_key opt_key, bool create); template ConfigOptionPercent* DynamicConfig::opt(const t_config_option_key opt_key, bool create); const ConfigOption* DynamicConfig::option(const t_config_option_key opt_key) const { return const_cast(this)->option(opt_key, false); } void DynamicConfig::keys(t_config_option_keys *keys) const { for (t_options_map::const_iterator it = this->options.begin(); it != this->options.end(); ++it) keys->push_back(it->first); } void DynamicConfig::erase(const t_config_option_key opt_key) { this->options.erase(opt_key); } void StaticConfig::keys(t_config_option_keys *keys) const { for (t_optiondef_map::const_iterator it = this->def->begin(); it != this->def->end(); ++it) { const ConfigOption* opt = this->option(it->first); if (opt != NULL) keys->push_back(it->first); } } const ConfigOption* StaticConfig::option(const t_config_option_key opt_key) const { return const_cast(this)->option(opt_key, false); } #ifdef SLIC3RXS bool StaticConfig::set(t_config_option_key opt_key, SV* value) { ConfigOptionDef* optdef = &(*this->def)[opt_key]; if (!optdef->shortcut.empty()) { for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) { if (!this->set(*it, value)) return false; } return true; } return static_cast(this)->set(opt_key, value); } #endif } Slic3r-1.2.9/xs/src/libslic3r/Config.hpp000066400000000000000000000370051254023100400177030ustar00rootroot00000000000000#ifndef slic3r_Config_hpp_ #define slic3r_Config_hpp_ #include #include #include #include #include #include #include #include #include #include "Point.hpp" namespace Slic3r { typedef std::string t_config_option_key; typedef std::vector t_config_option_keys; class ConfigOption { public: virtual ~ConfigOption() {}; virtual std::string serialize() const = 0; virtual bool deserialize(std::string str) = 0; virtual int getInt() const { return 0; }; virtual void setInt(int val) {}; }; class ConfigOptionVectorBase : public ConfigOption { public: virtual ~ConfigOptionVectorBase() {}; virtual std::vector vserialize() const = 0; }; template class ConfigOptionVector : public ConfigOptionVectorBase { public: virtual ~ConfigOptionVector() {}; std::vector values; T get_at(size_t i) const { try { return this->values.at(i); } catch (const std::out_of_range& oor) { return this->values.front(); } }; }; class ConfigOptionFloat : public ConfigOption { public: double value; // use double instead of float for preserving compatibility with values coming from Perl ConfigOptionFloat() : value(0) {}; operator float() const { return this->value; }; operator double() const { return this->value; }; std::string serialize() const { std::ostringstream ss; ss << this->value; return ss.str(); }; bool deserialize(std::string str) { std::istringstream iss(str); return iss >> this->value; }; }; class ConfigOptionFloats : public ConfigOptionVector { public: std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << *it; } return ss.str(); }; std::vector vserialize() const { std::vector vv; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; vv.push_back(ss.str()); } return vv; }; bool deserialize(std::string str) { this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { std::istringstream iss(item_str); double value; iss >> value; this->values.push_back(value); } return true; }; }; class ConfigOptionInt : public ConfigOption { public: int value; ConfigOptionInt() : value(0) {}; operator int() const { return this->value; }; int getInt() const { return this->value; }; void setInt(int val) { this->value = val; }; std::string serialize() const { std::ostringstream ss; ss << this->value; return ss.str(); }; bool deserialize(std::string str) { std::istringstream iss(str); return iss >> this->value; }; }; class ConfigOptionInts : public ConfigOptionVector { public: std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << *it; } return ss.str(); }; std::vector vserialize() const { std::vector vv; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; vv.push_back(ss.str()); } return vv; }; bool deserialize(std::string str) { this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { std::istringstream iss(item_str); int value; iss >> value; this->values.push_back(value); } return true; }; }; class ConfigOptionString : public ConfigOption { public: std::string value; ConfigOptionString() : value("") {}; operator std::string() const { return this->value; }; std::string serialize() const { std::string str = this->value; // s/\R/\\n/g size_t pos = 0; while ((pos = str.find("\n", pos)) != std::string::npos || (pos = str.find("\r", pos)) != std::string::npos) { str.replace(pos, 1, "\\n"); pos += 2; // length of "\\n" } return str; }; bool deserialize(std::string str) { // s/\\n/\n/g size_t pos = 0; while ((pos = str.find("\\n", pos)) != std::string::npos) { str.replace(pos, 2, "\n"); pos += 1; // length of "\n" } this->value = str; return true; }; }; // semicolon-separated strings class ConfigOptionStrings : public ConfigOptionVector { public: std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ";"; ss << *it; } return ss.str(); }; std::vector vserialize() const { return this->values; }; bool deserialize(std::string str) { this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ';')) { this->values.push_back(item_str); } return true; }; }; class ConfigOptionPercent : public ConfigOption { public: double value; ConfigOptionPercent() : value(0) {}; double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; }; std::string serialize() const { std::ostringstream ss; ss << this->value; std::string s(ss.str()); s += "%"; return s; }; bool deserialize(std::string str) { // don't try to parse the trailing % since it's optional std::istringstream iss(str); return iss >> this->value; }; }; class ConfigOptionFloatOrPercent : public ConfigOption { public: double value; bool percent; ConfigOptionFloatOrPercent() : value(0), percent(false) {}; double get_abs_value(double ratio_over) const { if (this->percent) { return ratio_over * this->value / 100; } else { return this->value; } }; std::string serialize() const { std::ostringstream ss; ss << this->value; std::string s(ss.str()); if (this->percent) s += "%"; return s; }; bool deserialize(std::string str) { this->percent = str.find_first_of("%") != std::string::npos; std::istringstream iss(str); return iss >> this->value; }; }; class ConfigOptionPoint : public ConfigOption { public: Pointf point; ConfigOptionPoint() : point(Pointf(0,0)) {}; operator Pointf() const { return this->point; }; std::string serialize() const { std::ostringstream ss; ss << this->point.x; ss << ","; ss << this->point.y; return ss.str(); }; bool deserialize(std::string str) { std::istringstream iss(str); iss >> this->point.x; iss.ignore(std::numeric_limits::max(), ','); iss.ignore(std::numeric_limits::max(), 'x'); iss >> this->point.y; return true; }; }; class ConfigOptionPoints : public ConfigOptionVector { public: std::string serialize() const { std::ostringstream ss; for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << it->x; ss << "x"; ss << it->y; } return ss.str(); }; std::vector vserialize() const { std::vector vv; for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; vv.push_back(ss.str()); } return vv; }; bool deserialize(std::string str) { this->values.clear(); std::istringstream is(str); std::string point_str; while (std::getline(is, point_str, ',')) { Pointf point; std::istringstream iss(point_str); std::string coord_str; if (std::getline(iss, coord_str, 'x')) { std::istringstream(coord_str) >> point.x; if (std::getline(iss, coord_str, 'x')) { std::istringstream(coord_str) >> point.y; } } this->values.push_back(point); } return true; }; }; class ConfigOptionBool : public ConfigOption { public: bool value; ConfigOptionBool() : value(false) {}; operator bool() const { return this->value; }; std::string serialize() const { return std::string(this->value ? "1" : "0"); }; bool deserialize(std::string str) { this->value = (str.compare("1") == 0); return true; }; }; class ConfigOptionBools : public ConfigOptionVector { public: std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << (*it ? "1" : "0"); } return ss.str(); }; std::vector vserialize() const { std::vector vv; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << (*it ? "1" : "0"); vv.push_back(ss.str()); } return vv; }; bool deserialize(std::string str) { this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { this->values.push_back(item_str.compare("1") == 0); } return true; }; }; typedef std::map t_config_enum_values; template class ConfigOptionEnum : public ConfigOption { public: T value; operator T() const { return this->value; }; std::string serialize() const { t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); for (t_config_enum_values::iterator it = enum_keys_map.begin(); it != enum_keys_map.end(); ++it) { if (it->second == static_cast(this->value)) return it->first; } return ""; }; bool deserialize(std::string str) { t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); if (enum_keys_map.count(str) == 0) return false; this->value = static_cast(enum_keys_map[str]); return true; }; static t_config_enum_values get_enum_values(); }; /* We use this one in DynamicConfig objects, otherwise it's better to use the specialized ConfigOptionEnum containers. */ class ConfigOptionEnumGeneric : public ConfigOption { public: int value; t_config_enum_values* keys_map; operator int() const { return this->value; }; std::string serialize() const { for (t_config_enum_values::iterator it = this->keys_map->begin(); it != this->keys_map->end(); ++it) { if (it->second == this->value) return it->first; } return ""; }; bool deserialize(std::string str) { if (this->keys_map->count(str) == 0) return false; this->value = (*this->keys_map)[str]; return true; }; }; enum ConfigOptionType { coFloat, coFloats, coInt, coInts, coString, coStrings, coPercent, coFloatOrPercent, coPoint, coPoints, coBool, coBools, coEnum, }; class ConfigOptionDef { public: ConfigOptionType type; std::string gui_type; std::string gui_flags; std::string label; std::string full_label; std::string category; std::string tooltip; std::string sidetext; std::string cli; t_config_option_key ratio_over; bool multiline; bool full_width; bool readonly; int height; int width; int min; int max; std::vector aliases; std::vector shortcut; std::vector enum_values; std::vector enum_labels; t_config_enum_values enum_keys_map; ConfigOptionDef() : multiline(false), full_width(false), readonly(false), height(-1), width(-1), min(INT_MIN), max(INT_MAX) {}; }; typedef std::map t_optiondef_map; class ConfigBase { public: t_optiondef_map* def; ConfigBase() : def(NULL) {}; bool has(const t_config_option_key opt_key); virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0; virtual const ConfigOption* option(const t_config_option_key opt_key) const = 0; virtual void keys(t_config_option_keys *keys) const = 0; void apply(const ConfigBase &other, bool ignore_nonexistent = false); bool equals(ConfigBase &other); t_config_option_keys diff(ConfigBase &other); std::string serialize(const t_config_option_key opt_key); bool set_deserialize(const t_config_option_key opt_key, std::string str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); double get_abs_value(const t_config_option_key opt_key); double get_abs_value(const t_config_option_key opt_key, double ratio_over); #ifdef SLIC3RXS SV* as_hash(); 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 }; class DynamicConfig : public ConfigBase { public: DynamicConfig() {}; DynamicConfig(const DynamicConfig& other); DynamicConfig& operator= (DynamicConfig other); void swap(DynamicConfig &other); ~DynamicConfig(); template 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: typedef std::map t_options_map; t_options_map options; }; class StaticConfig : public ConfigBase { public: void keys(t_config_option_keys *keys) const; virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0; const ConfigOption* option(const t_config_option_key opt_key) const; #ifdef SLIC3RXS bool set(t_config_option_key opt_key, SV* value); #endif }; } #endif Slic3r-1.2.9/xs/src/libslic3r/ExPolygon.cpp000066400000000000000000000344761254023100400204260ustar00rootroot00000000000000#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 #include namespace Slic3r { ExPolygon::operator Points() const { Points points; Polygons pp = *this; for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) points.push_back(*point); } return points; } ExPolygon::operator Polygons() const { Polygons polygons; polygons.reserve(this->holes.size() + 1); polygons.push_back(this->contour); for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { polygons.push_back(*it); } return polygons; } void ExPolygon::scale(double factor) { contour.scale(factor); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).scale(factor); } } void ExPolygon::translate(double x, double y) { contour.translate(x, y); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).translate(x, y); } } void ExPolygon::rotate(double angle, const Point ¢er) { contour.rotate(angle, center); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).rotate(angle, center); } } double ExPolygon::area() const { double a = this->contour.area(); for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { a -= -(*it).area(); // holes have negative area } return a; } bool ExPolygon::is_valid() const { if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false; for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false; } return true; } bool ExPolygon::contains(const Line &line) const { return this->contains((Polyline)line); } bool ExPolygon::contains(const Polyline &polyline) const { Polylines pl_out; diff((Polylines)polyline, *this, &pl_out); return pl_out.empty(); } bool ExPolygon::contains(const Point &point) const { if (!this->contour.contains(point)) return false; for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { if (it->contains(point)) return false; } return true; } // inclusive version of contains() that also checks whether point is on boundaries bool ExPolygon::contains_b(const Point &point) const { return this->contains(point) || this->has_boundary_point(point); } bool ExPolygon::has_boundary_point(const Point &point) const { if (this->contour.has_boundary_point(point)) return true; for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { if (h->has_boundary_point(point)) return true; } return false; } Polygons ExPolygon::simplify_p(double tolerance) const { Polygons pp; pp.reserve(this->holes.size() + 1); // contour { Polygon p = this->contour; p.points.push_back(p.points.front()); p.points = MultiPoint::_douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.push_back(p); } // holes for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { Polygon p = *it; p.points.push_back(p.points.front()); p.points = MultiPoint::_douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.push_back(p); } simplify_polygons(pp, &pp); return pp; } ExPolygons ExPolygon::simplify(double tolerance) const { Polygons pp = this->simplify_p(tolerance); ExPolygons expp; union_(pp, &expp); return expp; } void ExPolygon::simplify(double tolerance, ExPolygons &expolygons) const { ExPolygons ep = this->simplify(tolerance); expolygons.reserve(expolygons.size() + ep.size()); expolygons.insert(expolygons.end(), ep.begin(), ep.end()); } void ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const { // init helper object Slic3r::Geometry::MedialAxis ma(max_width, min_width); // populate list of segments for the Voronoi diagram ma.lines = this->contour.lines(); for (Polygons::const_iterator hole = this->holes.begin(); hole != this->holes.end(); ++hole) { Lines lines = hole->lines(); ma.lines.insert(ma.lines.end(), lines.begin(), lines.end()); } // compute the Voronoi diagram ma.build(polylines); // clip segments to our expolygon area // (do this before extending endpoints as external segments coule be extended into // expolygon, this leaving wrong things inside) intersection(*polylines, *this, 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().distance_to(polyline->points.back()) < min_width) continue; // TODO: we should *not* extend endpoints where other polylines start/end // (such as T joints, which are returned as three polylines by MedialAxis) polyline->extend_start(max_width); polyline->extend_end(max_width); } // clip again after extending endpoints to prevent them from exceeding the expolygon boundaries intersection(*polylines, *this, polylines); // remove too short polylines // (we can't do this check before endpoints extension and clipping because we don't // know how long will the endpoints be extended since it depends on polygon thickness // which is variable - extension will be <= max_width/2 on each side) for (size_t i = 0; i < polylines->size(); ++i) { if ((*polylines)[i].length() < max_width) { polylines->erase(polylines->begin() + i); --i; } } } 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 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::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(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 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 output; int res = TPPLPartition().Triangulate_MONO(&input, &output); if (res != 1) CONFESS("Triangulation failed"); // convert output polygons for (std::list::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 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 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 triangles = cdt->GetTriangles(); for (std::vector::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); } } } Lines ExPolygon::lines() const { Lines lines = this->contour.lines(); for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { Lines hole_lines = h->lines(); lines.insert(lines.end(), hole_lines.begin(), hole_lines.end()); } return lines; } #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, perl_to_SV_ref(this->contour)); for (unsigned int i = 0; i < num_holes; i++) { av_store(av, i+1, perl_to_SV_ref(this->holes[i])); } return newRV_noinc((SV*)av); } SV* ExPolygon::to_SV_pureperl() const { 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_pureperl()); for (unsigned int i = 0; i < num_holes; i++) { av_store(av, i+1, this->holes[i].to_SV_pureperl()); } return newRV_noinc((SV*)av); } void ExPolygon::from_SV(SV* expoly_sv) { AV* expoly_av = (AV*)SvRV(expoly_sv); const unsigned int num_polygons = av_len(expoly_av)+1; this->holes.resize(num_polygons-1); SV** polygon_sv = av_fetch(expoly_av, 0, 0); this->contour.from_SV(*polygon_sv); for (unsigned int i = 0; i < num_polygons-1; i++) { polygon_sv = av_fetch(expoly_av, i+1, 0); this->holes[i].from_SV(*polygon_sv); } } void ExPolygon::from_SV_check(SV* expoly_sv) { if (sv_isobject(expoly_sv) && (SvTYPE(SvRV(expoly_sv)) == SVt_PVMG)) { 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 { // a Perl arrayref was supplied this->from_SV(expoly_sv); } } #endif } Slic3r-1.2.9/xs/src/libslic3r/ExPolygon.hpp000066400000000000000000000120171254023100400204160ustar00rootroot00000000000000#ifndef slic3r_ExPolygon_hpp_ #define slic3r_ExPolygon_hpp_ #include "Polygon.hpp" #include "Polyline.hpp" #include namespace Slic3r { class ExPolygon; typedef std::vector ExPolygons; class ExPolygon { public: Polygon contour; Polygons holes; operator Points() const; operator Polygons() const; void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); double area() const; bool is_valid() const; bool contains(const Line &line) const; bool contains(const Polyline &polyline) const; bool contains(const Point &point) const; bool contains_b(const Point &point) const; bool has_boundary_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; Lines lines() const; #ifdef SLIC3RXS void from_SV(SV* poly_sv); void from_SV_check(SV* poly_sv); SV* to_AV(); SV* to_SV_pureperl() const; #endif }; } // start Boost #include namespace boost { namespace polygon { template <> struct polygon_traits { 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 { //expects stl style iterators template 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 { typedef polygon_with_holes_concept type; }; template <> struct polygon_with_holes_traits { 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 { template 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 { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> struct polygon_set_traits { 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 { template 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 Slic3r-1.2.9/xs/src/libslic3r/ExPolygonCollection.cpp000066400000000000000000000066521254023100400224350ustar00rootroot00000000000000#include "ExPolygonCollection.hpp" #include "Geometry.hpp" namespace Slic3r { ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon) { this->expolygons.push_back(expolygon); } ExPolygonCollection::operator Points() const { Points points; Polygons pp = *this; for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) points.push_back(*point); } return points; } ExPolygonCollection::operator Polygons() const { Polygons polygons; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { polygons.push_back(it->contour); for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) { polygons.push_back(*ith); } } return polygons; } ExPolygonCollection::operator ExPolygons&() { return this->expolygons; } void ExPolygonCollection::scale(double factor) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).scale(factor); } } void ExPolygonCollection::translate(double x, double y) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).translate(x, y); } } void ExPolygonCollection::rotate(double angle, const Point ¢er) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).rotate(angle, center); } } template bool ExPolygonCollection::contains(const T &item) const { for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { if (it->contains(item)) return true; } return false; } template bool ExPolygonCollection::contains(const Point &item) const; template bool ExPolygonCollection::contains(const Line &item) const; template bool ExPolygonCollection::contains(const Polyline &item) const; bool ExPolygonCollection::contains_b(const Point &point) const { for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { if (it->contains_b(point)) return true; } return false; } void ExPolygonCollection::simplify(double tolerance) { ExPolygons expp; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { it->simplify(tolerance, expp); } this->expolygons = expp; } Polygon ExPolygonCollection::convex_hull() const { Points pp; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end()); return Slic3r::Geometry::convex_hull(pp); } Lines ExPolygonCollection::lines() const { Lines lines; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { Lines ex_lines = it->lines(); lines.insert(lines.end(), ex_lines.begin(), ex_lines.end()); } return lines; } Polygons ExPolygonCollection::contours() const { Polygons contours; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { contours.push_back(it->contour); } return contours; } #ifdef SLIC3RXS REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); #endif } Slic3r-1.2.9/xs/src/libslic3r/ExPolygonCollection.hpp000066400000000000000000000017211254023100400224320ustar00rootroot00000000000000#ifndef slic3r_ExPolygonCollection_hpp_ #define slic3r_ExPolygonCollection_hpp_ #include #include "ExPolygon.hpp" #include "Line.hpp" #include "Polyline.hpp" namespace Slic3r { class ExPolygonCollection; typedef std::vector ExPolygonCollections; class ExPolygonCollection { public: ExPolygons expolygons; ExPolygonCollection() {}; ExPolygonCollection(const ExPolygon &expolygon); ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}; operator Points() const; operator Polygons() const; operator ExPolygons&(); void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); template bool contains(const T &item) const; bool contains_b(const Point &point) const; void simplify(double tolerance); Polygon convex_hull() const; Lines lines() const; Polygons contours() const; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/Extruder.cpp000066400000000000000000000074721254023100400203000ustar00rootroot00000000000000#include "Extruder.hpp" namespace Slic3r { Extruder::Extruder(int id, GCodeConfig *config) : id(id), config(config) { reset(); // cache values that are going to be called often if (config->use_volumetric_e) { this->e_per_mm3 = this->extrusion_multiplier(); } else { this->e_per_mm3 = this->extrusion_multiplier() * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI)); } this->retract_speed_mm_min = this->retract_speed() * 60; } void Extruder::reset() { this->E = 0; this->absolute_E = 0; this->retracted = 0; this->restart_extra = 0; } double Extruder::extrude(double dE) { // in case of relative E distances we always reset to 0 before any output if (this->config->use_relative_e_distances) this->E = 0; this->E += dE; this->absolute_E += dE; return dE; } /* This method makes sure the extruder is retracted by the specified amount of filament and returns the amount of filament retracted. If the extruder is already retracted by the same or a greater amount, this method is a no-op. The restart_extra argument sets the extra length to be used for unretraction. If we're actually performing a retraction, any restart_extra value supplied will overwrite the previous one if any. */ double Extruder::retract(double length, double restart_extra) { // in case of relative E distances we always reset to 0 before any output if (this->config->use_relative_e_distances) this->E = 0; double to_retract = length - this->retracted; if (to_retract > 0) { this->E -= to_retract; this->absolute_E -= to_retract; this->retracted += to_retract; this->restart_extra = restart_extra; return to_retract; } else { return 0; } } double Extruder::unretract() { double dE = this->retracted + this->restart_extra; this->extrude(dE); this->retracted = 0; this->restart_extra = 0; return dE; } double Extruder::e_per_mm(double mm3_per_mm) const { return mm3_per_mm * this->e_per_mm3; } double Extruder::extruded_volume() const { if (this->config->use_volumetric_e) { // Any current amount of retraction should not affect used filament, since // it represents empty volume in the nozzle. We add it back to E. return this->absolute_E + this->retracted; } return this->used_filament() * (this->filament_diameter() * this->filament_diameter()) * PI/4; } double Extruder::used_filament() const { if (this->config->use_volumetric_e) { return this->extruded_volume() / (this->filament_diameter() * this->filament_diameter() * PI/4); } // Any current amount of retraction should not affect used filament, since // it represents empty volume in the nozzle. We add it back to E. return this->absolute_E + this->retracted; } 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); } 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_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); } #ifdef SLIC3RXS REGISTER_CLASS(Extruder, "Extruder"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Extruder.hpp000066400000000000000000000020071254023100400202720ustar00rootroot00000000000000#ifndef slic3r_Extruder_hpp_ #define slic3r_Extruder_hpp_ #include #include "Point.hpp" #include "PrintConfig.hpp" namespace Slic3r { class Extruder { public: int id; double E; double absolute_E; double retracted; double restart_extra; double e_per_mm3; double retract_speed_mm_min; Extruder(int id, GCodeConfig *config); virtual ~Extruder() {} void reset(); double extrude(double dE); double retract(double length, double restart_extra); double unretract(); double e_per_mm(double mm3_per_mm) const; double extruded_volume() const; double used_filament() const; double filament_diameter() const; double extrusion_multiplier() const; double retract_length() const; double retract_lift() const; int retract_speed() const; double retract_restart_extra() const; double retract_length_toolchange() const; double retract_restart_extra_toolchange() const; private: GCodeConfig *config; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/ExtrusionEntity.cpp000066400000000000000000000252151254023100400216660ustar00rootroot00000000000000#include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" #include "ExPolygonCollection.hpp" #include "ClipperUtils.hpp" #include "Extruder.hpp" #include #include namespace Slic3r { ExtrusionPath* ExtrusionPath::clone() const { return new ExtrusionPath (*this); } void ExtrusionPath::reverse() { this->polyline.reverse(); } Point ExtrusionPath::first_point() const { return this->polyline.points.front(); } Point ExtrusionPath::last_point() const { return this->polyline.points.back(); } void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { // perform clipping Polylines clipped; intersection(this->polyline, collection, &clipped); return this->_inflate_collection(clipped, retval); } void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { // perform clipping Polylines clipped; diff(this->polyline, collection, &clipped); return this->_inflate_collection(clipped, retval); } void ExtrusionPath::clip_end(double distance) { this->polyline.clip_end(distance); } void ExtrusionPath::simplify(double tolerance) { this->polyline.simplify(tolerance); } double 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_infill() const { return this->role == erBridgeInfill || this->role == erInternalInfill || this->role == erSolidInfill || this->role == erTopSolidInfill; } bool ExtrusionPath::is_solid_infill() const { return this->role == erBridgeInfill || 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 { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) { ExtrusionPath* path = this->clone(); path->polyline = *it; collection->entities.push_back(path); } } #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 = 0; if (e > 0) { extruder->extrude(e * line_length); E = extruder->E; } // 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(); } Polygons ExtrusionPath::grow() const { Polygons pp; offset(this->polyline, &pp, +this->width/2); return pp; } ExtrusionLoop* ExtrusionLoop::clone() const { return new ExtrusionLoop (*this); } bool ExtrusionLoop::make_clockwise() { bool was_ccw = this->polygon().is_counter_clockwise(); if (was_ccw) this->reverse(); return was_ccw; } bool ExtrusionLoop::make_counter_clockwise() { bool was_cw = this->polygon().is_clockwise(); if (was_cw) this->reverse(); return was_cw; } void ExtrusionLoop::reverse() { for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->reverse(); std::reverse(this->paths.begin(), this->paths.end()); } Point ExtrusionLoop::first_point() const { return this->paths.front().polyline.points.front(); } Point ExtrusionLoop::last_point() const { return this->paths.back().polyline.points.back(); // which coincides with first_point(), by the way } Polygon ExtrusionLoop::polygon() const { Polygon polygon; 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); } return polygon; } 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; } bool 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 { // 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(), path+1, this->paths.end()); // 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(), this->paths.begin(), path); // 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 true; } } return false; } void ExtrusionLoop::split_at(const Point &point) { if (this->paths.empty()) return; // find the closest path and closest point belonging to that path 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; } bool ExtrusionLoop::is_perimeter() const { return this->paths.front().role == erPerimeter || this->paths.front().role == erExternalPerimeter || this->paths.front().role == erOverhangPerimeter; } bool ExtrusionLoop::is_infill() const { return this->paths.front().role == erBridgeInfill || this->paths.front().role == erInternalInfill || this->paths.front().role == erSolidInfill || this->paths.front().role == erTopSolidInfill; } bool ExtrusionLoop::is_solid_infill() const { return this->paths.front().role == erBridgeInfill || this->paths.front().role == erSolidInfill || this->paths.front().role == erTopSolidInfill; } Polygons ExtrusionLoop::grow() const { Polygons pp; for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { Polygons path_pp = path->grow(); pp.insert(pp.end(), path_pp.begin(), path_pp.end()); } return pp; } double ExtrusionLoop::min_mm3_per_mm() const { double min_mm3_per_mm = 0; for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { if (min_mm3_per_mm == 0) { min_mm3_per_mm = path->mm3_per_mm; } else { min_mm3_per_mm = fmin(min_mm3_per_mm, path->mm3_per_mm); } } return min_mm3_per_mm; } #ifdef SLIC3RXS REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); #endif } Slic3r-1.2.9/xs/src/libslic3r/ExtrusionEntity.hpp000066400000000000000000000066611254023100400216770ustar00rootroot00000000000000#ifndef slic3r_ExtrusionEntity_hpp_ #define slic3r_ExtrusionEntity_hpp_ #include #include "Polygon.hpp" #include "Polyline.hpp" namespace Slic3r { class ExPolygonCollection; class ExtrusionEntityCollection; class Extruder; /* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */ enum ExtrusionRole { erPerimeter, erExternalPerimeter, erOverhangPerimeter, erInternalInfill, erSolidInfill, erTopSolidInfill, erBridgeInfill, erGapFill, erSkirt, erSupportMaterial, erSupportMaterialInterface, }; /* Special flags describing loop */ enum ExtrusionLoopRole { elrDefault, elrContourInternalPerimeter, }; class ExtrusionEntity { public: virtual bool is_collection() const { return false; }; virtual bool is_loop() const { return false; }; virtual bool can_reverse() const { return true; }; virtual ExtrusionEntity* clone() const = 0; virtual ~ExtrusionEntity() {}; virtual void reverse() = 0; virtual Point first_point() const = 0; virtual Point last_point() const = 0; virtual Polygons grow() const = 0; virtual double min_mm3_per_mm() const = 0; }; typedef std::vector ExtrusionEntitiesPtr; class ExtrusionPath : public ExtrusionEntity { public: 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; 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_infill() const; bool is_solid_infill() 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; Polygons grow() const; double min_mm3_per_mm() const { return this->mm3_per_mm; }; private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; }; typedef std::vector ExtrusionPaths; class ExtrusionLoop : public ExtrusionEntity { public: ExtrusionPaths paths; ExtrusionLoopRole role; ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {}; bool is_loop() const { return true; }; bool can_reverse() const { return false; }; ExtrusionLoop* clone() const; bool make_clockwise(); bool make_counter_clockwise(); void reverse(); Point first_point() const; Point last_point() const; Polygon polygon() const; double length() const; bool 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; bool is_perimeter() const; bool is_infill() const; bool is_solid_infill() const; Polygons grow() const; double min_mm3_per_mm() const; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/ExtrusionEntityCollection.cpp000066400000000000000000000137421254023100400237040ustar00rootroot00000000000000#include "ExtrusionEntityCollection.hpp" #include #include #include 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 { return new ExtrusionEntityCollection(*this); } void ExtrusionEntityCollection::reverse() { for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) { // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering // and caller might rely on winding order if (!(*it)->is_loop()) (*it)->reverse(); } std::reverse(this->entities.begin(), this->entities.end()); } Point ExtrusionEntityCollection::first_point() const { return this->entities.front()->first_point(); } Point ExtrusionEntityCollection::last_point() const { return this->entities.back()->last_point(); } void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, std::vector* orig_indices) const { if (this->entities.empty()) return; this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, orig_indices); } void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, std::vector* orig_indices) const { if (this->no_sort) { *retval = *this; return; } retval->entities.reserve(this->entities.size()); retval->orig_indices.reserve(this->entities.size()); // if we're asked to return the original indices, build a map std::map indices_map; ExtrusionEntitiesPtr my_paths; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { ExtrusionEntity* entity = (*it)->clone(); my_paths.push_back(entity); if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin(); } Points endpoints; for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) { endpoints.push_back((*it)->first_point()); if (no_reverse || !(*it)->can_reverse()) { endpoints.push_back((*it)->first_point()); } else { endpoints.push_back((*it)->last_point()); } } while (!my_paths.empty()) { // find nearest point int start_index = start_near.nearest_point_index(endpoints); int path_index = start_index/2; ExtrusionEntity* entity = my_paths.at(path_index); // never reverse loops, since it's pointless for chained path and callers might depend on orientation if (start_index % 2 && !no_reverse && entity->can_reverse()) { entity->reverse(); } retval->entities.push_back(my_paths.at(path_index)); if (orig_indices != NULL) orig_indices->push_back(indices_map[entity]); my_paths.erase(my_paths.begin() + path_index); endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); start_near = retval->entities.back()->last_point(); } } Polygons ExtrusionEntityCollection::grow() const { Polygons pp; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { Polygons entity_pp = (*it)->grow(); pp.insert(pp.end(), entity_pp.begin(), entity_pp.end()); } return pp; } /* Recursively count paths and loops contained in this collection */ size_t ExtrusionEntityCollection::items_count() const { size_t count = 0; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); count += collection->items_count(); } else { ++count; } } return count; } /* Returns a single vector of pointers to all non-collection items contained in this one */ void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const { for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); ExtrusionEntityCollection contents; collection->flatten(&contents); retval->entities.insert(retval->entities.end(), contents.entities.begin(), contents.entities.end()); } else { retval->entities.push_back((*it)->clone()); } } } double ExtrusionEntityCollection::min_mm3_per_mm() const { double min_mm3_per_mm = 0; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { double mm3_per_mm = (*it)->min_mm3_per_mm(); if (min_mm3_per_mm == 0) { min_mm3_per_mm = mm3_per_mm; } else { min_mm3_per_mm = fmin(min_mm3_per_mm, mm3_per_mm); } } return min_mm3_per_mm; } #ifdef SLIC3RXS // there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); #endif } Slic3r-1.2.9/xs/src/libslic3r/ExtrusionEntityCollection.hpp000066400000000000000000000024361254023100400237070ustar00rootroot00000000000000#ifndef slic3r_ExtrusionEntityCollection_hpp_ #define slic3r_ExtrusionEntityCollection_hpp_ #include #include "ExtrusionEntity.hpp" namespace Slic3r { class ExtrusionEntityCollection : public ExtrusionEntity { public: ExtrusionEntityCollection* clone() const; ExtrusionEntitiesPtr entities; std::vector orig_indices; // handy for XS bool no_sort; ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &collection); ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other); bool is_collection() const { return true; }; bool can_reverse() const { return !this->no_sort; }; void swap (ExtrusionEntityCollection &c); void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector* orig_indices = NULL) const; void reverse(); Point first_point() const; Point last_point() const; Polygons grow() const; size_t items_count() const; void flatten(ExtrusionEntityCollection* retval) const; double min_mm3_per_mm() const; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/Flow.cpp000066400000000000000000000105431254023100400173760ustar00rootroot00000000000000#include "Flow.hpp" #include 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 (bridge_flow_ratio > 0) { // if bridge flow was requested, calculate bridge width height = 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); } 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); if (bridge) height = w; 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::spacing() const { if (this->bridge) { return this->width + BRIDGE_EXTRA_SPACING; } // rectangle with semicircles at the ends float min_flow_spacing = this->width - this->height * (1 - 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; } // rectangle with semicircles at the ends return this->width * this->height + (this->height*this->height) / 4.0 * (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 // shape: rectangle with semicircles at the ends float width = ((nozzle_diameter*nozzle_diameter) * PI + (height*height) * (4.0 - PI)) / (4.0 * height); float min = nozzle_diameter * 1.05; float max = -1; if (role == frExternalPerimeter || role == frSupportMaterial || role == frSupportMaterialInterface) { min = max = nozzle_diameter; } 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; } if (max != -1 && width > max) width = max; if (width < min) width = min; 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) { return spacing - BRIDGE_EXTRA_SPACING; } // rectangle with semicircles at the ends return spacing + OVERLAP_FACTOR * height * (1 - PI/4.0); } #ifdef SLIC3RXS REGISTER_CLASS(Flow, "Flow"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Flow.hpp000066400000000000000000000027711254023100400174070ustar00rootroot00000000000000#ifndef slic3r_Flow_hpp_ #define slic3r_Flow_hpp_ #include #include "Config.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { #define BRIDGE_EXTRA_SPACING 0.05 #define OVERLAP_FACTOR 1.0 enum FlowRole { frExternalPerimeter, frPerimeter, frInfill, frSolidInfill, frTopSolidInfill, frSupportMaterial, frSupportMaterialInterface, }; class Flow { public: float width, height, nozzle_diameter; bool bridge; 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); }; 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 _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); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/GCode.hpp000066400000000000000000000011441254023100400174520ustar00rootroot00000000000000#ifndef slic3r_GCode_hpp_ #define slic3r_GCode_hpp_ #include #include 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 Slic3r-1.2.9/xs/src/libslic3r/GCodeWriter.cpp000066400000000000000000000354061254023100400206520ustar00rootroot00000000000000#include "GCodeWriter.hpp" #include #include #include #include #define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val #define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment; #define PRECISION(val, precision) std::fixed << std::setprecision(precision) << val #define XYZF_NUM(val) PRECISION(val, 3) #define E_NUM(val) PRECISION(val, 5) namespace Slic3r { Extruder* GCodeWriter::extruder() { return this->_extruder; } std::string GCodeWriter::extrusion_axis() const { return this->_extrusion_axis; } void GCodeWriter::apply_print_config(const PrintConfig &print_config) { this->config.apply(print_config, true); this->_extrusion_axis = this->config.get_extrusion_axis(); } void GCodeWriter::set_extruders(const std::vector &extruder_ids) { for (std::vector::const_iterator i = extruder_ids.begin(); i != extruder_ids.end(); ++i) { this->extruders.insert( std::pair(*i, Extruder(*i, &this->config)) ); } /* we enable support for multiple extruder if any extruder greater than 0 is used (even if prints only uses that one) since we need to output Tx commands first extruder has index 0 */ this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0; } std::string GCodeWriter::preamble() { std::ostringstream gcode; if (FLAVOR_IS_NOT(gcfMakerWare)) { gcode << "G21 ; set units to millimeters\n"; gcode << "G90 ; use absolute coordinates\n"; } if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfTeacup)) { if (this->config.use_relative_e_distances) { gcode << "M83 ; use relative distances for extrusion\n"; } else { gcode << "M82 ; use absolute distances for extrusion\n"; } gcode << this->reset_e(true); } return gcode.str(); } std::string GCodeWriter::postamble() { std::ostringstream gcode; if (FLAVOR_IS(gcfMachinekit)) gcode << "M2 ; end of program\n"; return gcode.str(); } std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) { if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) return ""; std::string code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup)) { code = "M109"; comment = "wait for temperature to be reached"; } else { code = "M104"; comment = "set temperature"; } std::ostringstream gcode; gcode << code << " "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; } gcode << temperature; if (tool != -1 && (this->multiple_extruders || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) { gcode << " T" << tool; } gcode << " ; " << comment << "\n"; if (FLAVOR_IS(gcfTeacup) && wait) gcode << "M116 ; wait for temperature to be reached\n"; return gcode.str(); } std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait) { std::string code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup)) { if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { code = "M109"; } else { code = "M190"; } comment = "set bed temperature"; } else { code = "M140"; comment = "wait for bed temperature to be reached"; } std::ostringstream gcode; gcode << code << " "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; } gcode << temperature << " ; " << comment << "\n"; if (FLAVOR_IS(gcfTeacup) && wait) gcode << "M116 ; wait for bed temperature to be reached\n"; return gcode.str(); } std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save) { std::ostringstream gcode; if (this->_last_fan_speed != speed || dont_save) { if (!dont_save) this->_last_fan_speed = speed; if (speed == 0) { if (FLAVOR_IS(gcfTeacup)) { gcode << "M106 S0"; } else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { gcode << "M127"; } else { gcode << "M107"; } if (this->config.gcode_comments) gcode << " ; disable fan"; gcode << "\n"; } else { if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { gcode << "M126"; } else { gcode << "M106 "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; } gcode << (255.0 * speed / 100.0); } if (this->config.gcode_comments) gcode << " ; enable fan"; gcode << "\n"; } } return gcode.str(); } std::string GCodeWriter::set_acceleration(unsigned int acceleration) { if (acceleration == 0 || acceleration == this->_last_acceleration) return ""; this->_last_acceleration = acceleration; std::ostringstream gcode; gcode << "M204 S" << acceleration; if (this->config.gcode_comments) gcode << " ; adjust acceleration"; gcode << "\n"; return gcode.str(); } std::string GCodeWriter::reset_e(bool force) { if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) return ""; if (this->_extruder != NULL) { if (this->_extruder->E == 0 && !force) return ""; this->_extruder->E = 0; } if (!this->_extrusion_axis.empty() && !this->config.use_relative_e_distances) { std::ostringstream gcode; gcode << "G92 " << this->_extrusion_axis << "0"; if (this->config.gcode_comments) gcode << " ; reset extrusion distance"; gcode << "\n"; return gcode.str(); } else { return ""; } } std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) { if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) return ""; unsigned int percent = 100.0 * num / tot; if (!allow_100) percent = std::min(percent, (unsigned int)99); std::ostringstream gcode; gcode << "M73 P" << percent; if (this->config.gcode_comments) gcode << " ; update progress"; gcode << "\n"; return gcode.str(); } bool GCodeWriter::need_toolchange(unsigned int extruder_id) const { // return false if this extruder was already selected return (this->_extruder == NULL) || (this->_extruder->id != extruder_id); } std::string GCodeWriter::set_extruder(unsigned int extruder_id) { if (!this->need_toolchange(extruder_id)) return ""; return this->toolchange(extruder_id); } std::string GCodeWriter::toolchange(unsigned int extruder_id) { // set the new extruder this->_extruder = &this->extruders.find(extruder_id)->second; // return the toolchange command // if we are running a single-extruder setup, just set the extruder and return nothing std::ostringstream gcode; if (this->multiple_extruders) { if (FLAVOR_IS(gcfMakerWare)) { gcode << "M135 T"; } else if (FLAVOR_IS(gcfSailfish)) { gcode << "M108 T"; } else { gcode << "T"; } gcode << extruder_id; if (this->config.gcode_comments) gcode << " ; change extruder"; gcode << "\n"; gcode << this->reset_e(true); } return gcode.str(); } std::string GCodeWriter::set_speed(double F, const std::string &comment) { std::ostringstream gcode; gcode << "G1 F" << F; COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &comment) { this->_pos.x = point.x; this->_pos.y = point.y; std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string &comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the Z move but we only move in the XY plane and adjust the nominal Z by reducing the lift amount that will be used for unlift. */ if (!this->will_move_z(point.z)) { double nominal_z = this->_pos.z - this->_lifted; this->_lifted = this->_lifted - (point.z - nominal_z); return this->travel_to_xy(point); } /* In all the other cases, we perform an actual XYZ move and cancel the lift. */ this->_lifted = 0; this->_pos = point; std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " Z" << XYZF_NUM(point.z) << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::travel_to_z(double z, const std::string &comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the move but we only adjust the nominal Z by reducing the lift amount that will be used for unlift. */ if (!this->will_move_z(z)) { double nominal_z = this->_pos.z - this->_lifted; this->_lifted = this->_lifted - (z - nominal_z); return ""; } /* In all the other cases, we perform an actual Z move and cancel the lift. */ this->_lifted = 0; return this->_travel_to_z(z, comment); } std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) { this->_pos.z = z; std::ostringstream gcode; gcode << "G1 Z" << XYZF_NUM(z) << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); COMMENT(comment); gcode << "\n"; return gcode.str(); } bool GCodeWriter::will_move_z(double z) const { /* If target Z is lower than current Z but higher than nominal Z we don't perform an actual Z move. */ if (this->_lifted > 0) { double nominal_z = this->_pos.z - this->_lifted; if (z >= nominal_z && z <= this->_pos.z) return false; } return true; } std::string GCodeWriter::extrude_to_xy(const Pointf &point, double dE, const std::string &comment) { this->_pos.x = point.x; this->_pos.y = point.y; this->_extruder->extrude(dE); std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " " << this->_extrusion_axis << E_NUM(this->_extruder->E); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment) { this->_pos = point; this->_lifted = 0; this->_extruder->extrude(dE); std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " Z" << XYZF_NUM(point.z) << " " << this->_extrusion_axis << E_NUM(this->_extruder->E); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::retract() { return this->_retract( this->_extruder->retract_length(), this->_extruder->retract_restart_extra(), "retract" ); } std::string GCodeWriter::retract_for_toolchange() { return this->_retract( this->_extruder->retract_length_toolchange(), this->_extruder->retract_restart_extra_toolchange(), "retract for toolchange" ); } std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment) { std::ostringstream gcode; /* If firmware retraction is enabled, we use a fake value of 1 since we ignore the actual configured retract_length which might be 0, in which case the retraction logic gets skipped. */ if (this->config.use_firmware_retraction) length = 1; // If we use volumetric E values we turn lengths into volumes */ if (this->config.use_volumetric_e) { double d = this->_extruder->filament_diameter(); double area = d * d * PI/4; length = length * area; restart_extra = restart_extra * area; } double dE = this->_extruder->retract(length, restart_extra); if (dE != 0) { if (this->config.use_firmware_retraction) { if (FLAVOR_IS(gcfMachinekit)) gcode << "G22 ; retract\n"; else gcode << "G10 ; retract\n"; } else { gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E) << " F" << this->_extruder->retract_speed_mm_min; COMMENT(comment); gcode << "\n"; } } if (FLAVOR_IS(gcfMakerWare)) gcode << "M103 ; extruder off\n"; return gcode.str(); } std::string GCodeWriter::unretract() { std::ostringstream gcode; if (FLAVOR_IS(gcfMakerWare)) gcode << "M101 ; extruder on\n"; double dE = this->_extruder->unretract(); if (dE != 0) { if (this->config.use_firmware_retraction) { if (FLAVOR_IS(gcfMachinekit)) gcode << "G23 ; unretract\n"; else gcode << "G11 ; unretract\n"; gcode << this->reset_e(); } else { // use G1 instead of G0 because G0 will blend the restart with the previous travel move gcode << "G1 " << this->_extrusion_axis << E_NUM(this->_extruder->E) << " F" << this->_extruder->retract_speed_mm_min; if (this->config.gcode_comments) gcode << " ; unretract"; gcode << "\n"; } } return gcode.str(); } /* If this method is called more than once before calling unlift(), it will not perform subsequent lifts, even if Z was raised manually (i.e. with travel_to_z()) and thus _lifted was reduced. */ std::string GCodeWriter::lift() { double target_lift = this->config.retract_lift.get_at(0); if (this->_lifted == 0 && target_lift > 0) { this->_lifted = target_lift; return this->_travel_to_z(this->_pos.z + target_lift, "lift Z"); } return ""; } std::string GCodeWriter::unlift() { std::string gcode; if (this->_lifted > 0) { gcode += this->_travel_to_z(this->_pos.z - this->_lifted, "restore layer Z"); this->_lifted = 0; } return gcode; } Pointf3 GCodeWriter::get_position() const { return this->_pos; } #ifdef SLIC3RXS REGISTER_CLASS(GCodeWriter, "GCode::Writer"); #endif } Slic3r-1.2.9/xs/src/libslic3r/GCodeWriter.hpp000066400000000000000000000047371254023100400206620ustar00rootroot00000000000000#ifndef slic3r_GCodeWriter_hpp_ #define slic3r_GCodeWriter_hpp_ #include #include #include "Extruder.hpp" #include "Point.hpp" #include "PrintConfig.hpp" namespace Slic3r { class GCodeWriter { public: GCodeConfig config; std::map extruders; bool multiple_extruders; GCodeWriter() : multiple_extruders(false), _extrusion_axis("E"), _extruder(NULL), _last_acceleration(0), _last_fan_speed(0), _lifted(0) {}; Extruder* extruder(); std::string extrusion_axis() const; void apply_print_config(const PrintConfig &print_config); void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string postamble(); std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1); std::string set_bed_temperature(unsigned int temperature, bool wait = false); std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_acceleration(unsigned int acceleration); std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false); bool need_toolchange(unsigned int extruder_id) const; std::string set_extruder(unsigned int extruder_id); std::string toolchange(unsigned int extruder_id); std::string set_speed(double F, const std::string &comment = std::string()); std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string()); std::string travel_to_xyz(const Pointf3 &point, const std::string &comment = std::string()); std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; std::string extrude_to_xy(const Pointf &point, double dE, const std::string &comment = std::string()); std::string extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment = std::string()); std::string retract(); std::string retract_for_toolchange(); std::string unretract(); std::string lift(); std::string unlift(); Pointf3 get_position() const; private: std::string _extrusion_axis; Extruder* _extruder; unsigned int _last_acceleration; unsigned int _last_fan_speed; double _lifted; Pointf3 _pos; std::string _travel_to_z(double z, const std::string &comment); std::string _retract(double length, double restart_extra, const std::string &comment); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/GUI/000077500000000000000000000000001254023100400164045ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/libslic3r/GUI/3DScene.cpp000066400000000000000000000224301254023100400203350ustar00rootroot00000000000000#include "3DScene.hpp" namespace Slic3r { // caller is responsible for supplying NO lines with zero length void _3DScene::_extrusionentity_to_verts_do(const Lines &lines, const std::vector &widths, const std::vector &heights, bool closed, double top_z, const Point ©, GLVertexArray* qverts, GLVertexArray* tverts) { /* It looks like it's faster without reserving capacity... // each segment has 4 quads, thus 16 vertices; + 2 caps qverts->reserve_more(3 * 4 * (4 * lines.size() + 2)); // two triangles for each corner tverts->reserve_more(3 * 3 * 2 * (lines.size() + 1)); */ Line prev_line; Pointf prev_b1, prev_b2; Vectorf3 prev_xy_left_normal, prev_xy_right_normal; // loop once more in case of closed loops bool first_done = false; for (size_t i = 0; i <= lines.size(); ++i) { if (i == lines.size()) i = 0; const Line &line = lines.at(i); if (i == 0 && first_done && !closed) break; double len = line.length(); double unscaled_len = unscale(len); double bottom_z = top_z - heights.at(i); double middle_z = (top_z + bottom_z) / 2; double dist = widths.at(i)/2; // scaled Vectorf v = Vectorf::new_unscale(line.vector()); v.scale(1/unscaled_len); Pointf a = Pointf::new_unscale(line.a); Pointf b = Pointf::new_unscale(line.b); Pointf a1 = a; Pointf a2 = a; a1.translate(+dist*v.y, -dist*v.x); a2.translate(-dist*v.y, +dist*v.x); Pointf b1 = b; Pointf b2 = b; b1.translate(+dist*v.y, -dist*v.x); b2.translate(-dist*v.y, +dist*v.x); // calculate new XY normals Vector n = line.normal(); Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0); xy_right_normal.scale(1/unscaled_len); Vectorf3 xy_left_normal = xy_right_normal; xy_left_normal.scale(-1); if (first_done) { // if we're making a ccw turn, draw the triangles on the right side, otherwise draw them on the left side double ccw = line.b.ccw(prev_line); if (ccw > EPSILON) { // top-right vertex triangle between previous line and this one { // use the normal going to the right calculated for the previous line tverts->push_norm(prev_xy_right_normal); tverts->push_vert(prev_b1.x, prev_b1.y, middle_z); // use the normal going to the right calculated for this line tverts->push_norm(xy_right_normal); tverts->push_vert(a1.x, a1.y, middle_z); // normal going upwards tverts->push_norm(0,0,1); tverts->push_vert(a.x, a.y, top_z); } // bottom-right vertex triangle between previous line and this one { // use the normal going to the right calculated for the previous line tverts->push_norm(prev_xy_right_normal); tverts->push_vert(prev_b1.x, prev_b1.y, middle_z); // normal going downwards tverts->push_norm(0,0,-1); tverts->push_vert(a.x, a.y, bottom_z); // use the normal going to the right calculated for this line tverts->push_norm(xy_right_normal); tverts->push_vert(a1.x, a1.y, middle_z); } } else if (ccw < -EPSILON) { // top-left vertex triangle between previous line and this one { // use the normal going to the left calculated for the previous line tverts->push_norm(prev_xy_left_normal); tverts->push_vert(prev_b2.x, prev_b2.y, middle_z); // normal going upwards tverts->push_norm(0,0,1); tverts->push_vert(a.x, a.y, top_z); // use the normal going to the right calculated for this line tverts->push_norm(xy_left_normal); tverts->push_vert(a2.x, a2.y, middle_z); } // bottom-left vertex triangle between previous line and this one { // use the normal going to the left calculated for the previous line tverts->push_norm(prev_xy_left_normal); tverts->push_vert(prev_b2.x, prev_b2.y, middle_z); // use the normal going to the right calculated for this line tverts->push_norm(xy_left_normal); tverts->push_vert(a2.x, a2.y, middle_z); // normal going downwards tverts->push_norm(0,0,-1); tverts->push_vert(a.x, a.y, bottom_z); } } } // if this was the extra iteration we were only interested in the triangles if (first_done && i == 0) break; prev_line = line; prev_b1 = b1; prev_b2 = b2; prev_xy_right_normal = xy_right_normal; prev_xy_left_normal = xy_left_normal; if (!closed) { // terminate open paths with caps if (i == 0) { // normal pointing downwards qverts->push_norm(0,0,-1); qverts->push_vert(a.x, a.y, bottom_z); // normal pointing to the right qverts->push_norm(xy_right_normal); qverts->push_vert(a1.x, a1.y, middle_z); // normal pointing upwards qverts->push_norm(0,0,1); qverts->push_vert(a.x, a.y, top_z); // normal pointing to the left qverts->push_norm(xy_left_normal); qverts->push_vert(a2.x, a2.y, middle_z); } // we don't use 'else' because both cases are true if we have only one line if (i == lines.size()-1) { // normal pointing downwards qverts->push_norm(0,0,-1); qverts->push_vert(b.x, b.y, bottom_z); // normal pointing to the left qverts->push_norm(xy_left_normal); qverts->push_vert(b2.x, b2.y, middle_z); // normal pointing upwards qverts->push_norm(0,0,1); qverts->push_vert(b.x, b.y, top_z); // normal pointing to the right qverts->push_norm(xy_right_normal); qverts->push_vert(b1.x, b1.y, middle_z); } } // bottom-right face { // normal going downwards qverts->push_norm(0,0,-1); qverts->push_norm(0,0,-1); qverts->push_vert(a.x, a.y, bottom_z); qverts->push_vert(b.x, b.y, bottom_z); qverts->push_norm(xy_right_normal); qverts->push_norm(xy_right_normal); qverts->push_vert(b1.x, b1.y, middle_z); qverts->push_vert(a1.x, a1.y, middle_z); } // top-right face { qverts->push_norm(xy_right_normal); qverts->push_norm(xy_right_normal); qverts->push_vert(a1.x, a1.y, middle_z); qverts->push_vert(b1.x, b1.y, middle_z); // normal going upwards qverts->push_norm(0,0,1); qverts->push_norm(0,0,1); qverts->push_vert(b.x, b.y, top_z); qverts->push_vert(a.x, a.y, top_z); } // top-left face { qverts->push_norm(0,0,1); qverts->push_norm(0,0,1); qverts->push_vert(a.x, a.y, top_z); qverts->push_vert(b.x, b.y, top_z); qverts->push_norm(xy_left_normal); qverts->push_norm(xy_left_normal); qverts->push_vert(b2.x, b2.y, middle_z); qverts->push_vert(a2.x, a2.y, middle_z); } // bottom-left face { qverts->push_norm(xy_left_normal); qverts->push_norm(xy_left_normal); qverts->push_vert(a2.x, a2.y, middle_z); qverts->push_vert(b2.x, b2.y, middle_z); // normal going downwards qverts->push_norm(0,0,-1); qverts->push_norm(0,0,-1); qverts->push_vert(b.x, b.y, bottom_z); qverts->push_vert(a.x, a.y, bottom_z); } first_done = true; } } void GLVertexArray::load_mesh(const TriangleMesh &mesh) { this->reserve_more(3 * 3 * mesh.facets_count()); for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) { stl_facet &facet = mesh.stl.facet_start[i]; for (int j = 0; j <= 2; ++j) { this->push_norm(facet.normal.x, facet.normal.y, facet.normal.z); this->push_vert(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z); } } } #ifdef SLIC3RXS REGISTER_CLASS(GLVertexArray, "GUI::_3DScene::GLVertexArray"); #endif } Slic3r-1.2.9/xs/src/libslic3r/GUI/3DScene.hpp000066400000000000000000000026601254023100400203450ustar00rootroot00000000000000#ifndef slic3r_3DScene_hpp_ #define slic3r_3DScene_hpp_ #include #include "../Point.hpp" #include "../Line.hpp" #include "../TriangleMesh.hpp" namespace Slic3r { class GLVertexArray { public: std::vector verts, norms; void reserve(size_t len) { this->verts.reserve(len); this->norms.reserve(len); }; void reserve_more(size_t len) { len += this->verts.size(); this->reserve(len); }; void push_vert(const Pointf3 &point) { this->verts.push_back(point.x); this->verts.push_back(point.y); this->verts.push_back(point.z); }; void push_vert(float x, float y, float z) { this->verts.push_back(x); this->verts.push_back(y); this->verts.push_back(z); }; void push_norm(const Pointf3 &point) { this->norms.push_back(point.x); this->norms.push_back(point.y); this->norms.push_back(point.z); }; void push_norm(float x, float y, float z) { this->norms.push_back(x); this->norms.push_back(y); this->norms.push_back(z); }; void load_mesh(const TriangleMesh &mesh); }; class _3DScene { public: static void _extrusionentity_to_verts_do(const Lines &lines, const std::vector &widths, const std::vector &heights, bool closed, double top_z, const Point ©, GLVertexArray* qverts, GLVertexArray* tverts); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/Geometry.cpp000066400000000000000000000423061254023100400202640ustar00rootroot00000000000000#include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" #include "Line.hpp" #include "PolylineCollection.hpp" #include "clipper.hpp" #include #include #include #include #include #include #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { static bool sort_points (Point a, Point b) { return (a.x < b.x) || (a.x == b.x && a.y < b.y); } /* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */ Polygon convex_hull(Points points) { assert(points.size() >= 3); // sort input points std::sort(points.begin(), points.end(), sort_points); int n = points.size(), k = 0; Polygon hull; hull.points.resize(2*n); // Build lower hull for (int i = 0; i < n; i++) { while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; hull.points[k++] = points[i]; } // Build upper hull for (int i = n-2, t = k+1; i >= 0; i--) { while (k >= t && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; hull.points[k++] = points[i]; } hull.points.resize(k); assert( hull.points.front().coincides_with(hull.points.back()) ); hull.points.pop_back(); return hull; } Polygon convex_hull(const Polygons &polygons) { Points pp; for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { pp.insert(pp.end(), p->points.begin(), p->points.end()); } return convex_hull(pp); } /* accepts an arrayref of points and returns a list of indices according to a nearest-neighbor walk */ void chained_path(const Points &points, std::vector &retval, Point start_near) { PointConstPtrs my_points; std::map indices; my_points.reserve(points.size()); for (Points::const_iterator it = points.begin(); it != points.end(); ++it) { my_points.push_back(&*it); indices[&*it] = it - points.begin(); } retval.reserve(points.size()); while (!my_points.empty()) { Points::size_type idx = start_near.nearest_point_index(my_points); start_near = *my_points[idx]; retval.push_back(indices[ my_points[idx] ]); my_points.erase(my_points.begin() + idx); } } void chained_path(const Points &points, std::vector &retval) { if (points.empty()) return; // can't call front() on empty vector chained_path(points, retval, points.front()); } /* retval and items must be different containers */ template void chained_path_items(Points &points, T &items, T &retval) { std::vector indices; chained_path(points, indices); for (std::vector::const_iterator it = indices.begin(); it != indices.end(); ++it) retval.push_back(items[*it]); } 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; } template bool contains(const std::vector &vector, const Point &point) { for (typename std::vector::const_iterator it = vector.begin(); it != vector.end(); ++it) { if (it->contains(point)) return true; } return false; } template bool contains(const ExPolygons &vector, const Point &point); double rad2deg(double angle) { return angle / PI * 180.0; } double rad2deg_dir(double angle) { angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); if (angle < 0) angle += PI; return rad2deg(angle); } double deg2rad(double angle) { return PI * angle / 180.0; } void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { Polygons pp; for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { Polygon p = *it; p.points.push_back(p.points.front()); p.points = MultiPoint::_douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.push_back(p); } Slic3r::simplify_polygons(pp, retval); } double linint(double value, double oldmin, double oldmax, double newmin, double newmax) { return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf &bb) { // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part.x += dist; part.y += dist; Pointf area; if (bb.defined) { area = bb.size(); } else { // bogus area size, large enough not to trigger the error below area.x = part.x * total_parts; area.y = part.y * total_parts; } // this is how many cells we have available into which to put parts size_t cellw = floor((area.x + dist) / part.x); size_t cellh = floor((area.x + dist) / part.x); if (total_parts > (cellw * cellh)) CONFESS("%zu parts won't fit in your print area!\n", total_parts); // total space used by cells Pointf cells(cellw * part.x, cellh * part.y); // bounding box of total space used by cells BoundingBoxf cells_bb; cells_bb.merge(Pointf(0,0)); // min cells_bb.merge(cells); // max // center bounding box to area cells_bb.translate( -(area.x - cells.x) / 2, -(area.y - cells.y) / 2 ); // list of cells, sorted by distance from center std::vector cellsorder; // work out distance for all cells, sort into list for (size_t i = 0; i <= cellw-1; ++i) { for (size_t j = 0; j <= cellh-1; ++j) { coordf_t cx = linint(i + 0.5, 0, cellw, cells_bb.min.x, cells_bb.max.x); coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.max.y, cells_bb.min.y); coordf_t xd = fabs((area.x / 2) - cx); coordf_t yd = fabs((area.y / 2) - cy); ArrangeItem c; c.pos.x = cx; c.pos.y = cy; c.index_x = i; c.index_y = j; c.dist = xd * xd + yd * yd - fabs((cellw / 2) - (i + 0.5)); // binary insertion sort { coordf_t index = c.dist; size_t low = 0; size_t high = cellsorder.size(); while (low < high) { size_t mid = (low + ((high - low) / 2)) | 0; coordf_t midval = cellsorder[mid].index; if (midval < index) { low = mid + 1; } else if (midval > index) { high = mid; } else { cellsorder.insert(cellsorder.begin() + mid, ArrangeItemIndex(index, c)); goto ENDSORT; } } cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c)); } ENDSORT: true; } } // the extents of cells actually used by objects coordf_t lx = 0; coordf_t ty = 0; coordf_t rx = 0; coordf_t by = 0; // now find cells actually used by objects, map out the extents so we can position correctly for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder[i - 1]; coordf_t cx = c.item.index_x; coordf_t cy = c.item.index_y; if (i == 1) { lx = rx = cx; ty = by = cy; } else { if (cx > rx) rx = cx; if (cx < lx) lx = cx; if (cy > by) by = cy; if (cy < ty) ty = cy; } } // now we actually place objects into cells, positioned such that the left and bottom borders are at 0 Pointfs positions; for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder.front(); cellsorder.erase(cellsorder.begin()); coordf_t cx = c.item.index_x - lx; coordf_t cy = c.item.index_y - ty; positions.push_back(Pointf(cx * part.x, cy * part.y)); } if (bb.defined) { for (Pointfs::iterator p = positions.begin(); p != positions.end(); ++p) { p->x += bb.min.x; p->y += bb.min.y; } } return positions; } Line MedialAxis::edge_to_line(const VD::edge_type &edge) const { Line line; line.a.x = edge.vertex0()->x(); line.a.y = edge.vertex0()->y(); line.b.x = edge.vertex1()->x(); line.b.y = edge.vertex1()->y(); return line; } void MedialAxis::build(Polylines* polylines) { /* // build bounding box (we use it for clipping infinite segments) // --> we have no infinite segments this->bb = BoundingBox(this->lines); */ 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; } */ typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it inserts twice the number of the valid edges this->edges.clear(); for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { // if we only process segments representing closed loops, none if the // infinite edges (if any) would be part of our MAT anyway if (edge->is_secondary() || edge->is_infinite()) continue; this->edges.insert(&*edge); } // count valid segments for each vertex std::map< vert_t*,std::set > vertex_edges; // collects edges connected for each vertex std::set startpoints; // collects all vertices having a single starting edge for (VD::const_vertex_iterator it = this->vd.vertices().begin(); it != this->vd.vertices().end(); ++it) { vert_t* vertex = &*it; // loop through all edges originating from this vertex // starting from a random one edge_t* edge = vertex->incident_edge(); do { // if this edge was not pruned by our filter above, // add it to vertex_edges if (this->edges.count(edge) > 0) vertex_edges[vertex].insert(edge); // continue looping next edge originating from this vertex edge = edge->rot_next(); } while (edge != vertex->incident_edge()); // if there's only one edge starting at this vertex then it's an endpoint if (vertex_edges[vertex].size() == 1) { startpoints.insert(vertex); } } // prune startpoints recursively if extreme segments are not valid while (!startpoints.empty()) { // get a random entry node vert_t* v = *startpoints.begin(); // get edge starting from v assert(vertex_edges[v].size() == 1); edge_t* edge = *vertex_edges[v].begin(); if (!this->is_valid_edge(*edge)) { // if edge is not valid, erase it and its twin from edge list (void)this->edges.erase(edge); (void)this->edges.erase(edge->twin()); // decrement edge counters for the affected nodes vert_t* v1 = edge->vertex1(); (void)vertex_edges[v].erase(edge); (void)vertex_edges[v1].erase(edge->twin()); // also, check whether the end vertex is a new leaf if (vertex_edges[v1].size() == 1) { startpoints.insert(v1); } else if (vertex_edges[v1].empty()) { startpoints.erase(v1); } } // remove node from the set to prevent it from being visited again startpoints.erase(v); } // iterate through the valid edges to build polylines while (!this->edges.empty()) { edge_t &edge = **this->edges.begin(); // start a polyline Polyline polyline; polyline.points.push_back(Point( edge.vertex0()->x(), edge.vertex0()->y() )); polyline.points.push_back(Point( edge.vertex1()->x(), edge.vertex1()->y() )); // remove this edge and its twin from the available edges (void)this->edges.erase(&edge); (void)this->edges.erase(edge.twin()); // get next points this->process_edge_neighbors(edge, &polyline.points); // get previous points { Points pp; this->process_edge_neighbors(*edge.twin(), &pp); polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend()); } // append polyline to result polylines->push_back(polyline); } } void MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points) { // Since rot_next() works on the edge starting point but we want // to find neighbors on the ending point, we just swap edge with // its twin. const VD::edge_type& twin = *edge.twin(); // count neighbors for this edge std::vector neighbors; for (const VD::edge_type* neighbor = twin.rot_next(); neighbor != &twin; neighbor = neighbor->rot_next()) { if (this->edges.count(neighbor) > 0) neighbors.push_back(neighbor); } // if we have a single neighbor then we can continue recursively if (neighbors.size() == 1) { const VD::edge_type& neighbor = *neighbors.front(); points->push_back(Point( neighbor.vertex1()->x(), neighbor.vertex1()->y() )); (void)this->edges.erase(&neighbor); (void)this->edges.erase(neighbor.twin()); this->process_edge_neighbors(neighbor, points); } } bool MedialAxis::is_valid_edge(const VD::edge_type& edge) const { /* If the cells sharing this edge have a common vertex, we're not interested in this edge. Why? Because it means that the edge lies on the bisector of two contiguous input lines and it was included in the Voronoi graph because it's the locus of centers of circles tangent to both vertices. Due to the "thin" nature of our input, these edges will be very short and not part of our wanted output. */ // retrieve the original line segments which generated the edge we're checking const VD::cell_type &cell1 = *edge.cell(); const VD::cell_type &cell2 = *edge.twin()->cell(); if (!cell1.contains_segment() || !cell2.contains_segment()) return false; const Line &segment1 = this->retrieve_segment(cell1); const Line &segment2 = this->retrieve_segment(cell2); // calculate the relative angle between the two boundary segments double angle = fabs(segment2.orientation() - segment1.orientation()); // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // we're interested only in segments close to the second case (facing segments) // so we allow some tolerance. // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) if (fabs(angle - PI) > PI/5) { return false; } // each edge vertex is equidistant to both cell segments // but such distance might differ between the two vertices; // in this case it means the shape is getting narrow (like a corner) // and we might need to skip the edge since it's not really part of // our skeleton // get perpendicular distance of each edge vertex to the segment(s) double dist0 = segment1.a.distance_to(segment2.b); double dist1 = segment1.b.distance_to(segment2.a); /* Line line = this->edge_to_line(edge); double diff = fabs(dist1 - dist0); double dist_between_segments1 = segment1.a.distance_to(segment2); double dist_between_segments2 = segment1.b.distance_to(segment2); printf("w = %f/%f, dist0 = %f, dist1 = %f, diff = %f, seg1len = %f, seg2len = %f, edgelen = %f, s2s = %f / %f\n", unscale(this->max_width), unscale(this->min_width), unscale(dist0), unscale(dist1), unscale(diff), unscale(segment1.length()), unscale(segment2.length()), unscale(line.length()), unscale(dist_between_segments1), unscale(dist_between_segments2) ); */ // if this edge is the centerline for a very thin area, we might want to skip it // in case the area is too thin if (dist0 < this->min_width && dist1 < this->min_width) { //printf(" => too thin, skipping\n"); return false; } return true; } const Line& MedialAxis::retrieve_segment(const VD::cell_type& cell) const { VD::cell_type::source_index_type index = cell.source_index() - this->points.size(); return this->lines[index]; } } } Slic3r-1.2.9/xs/src/libslic3r/Geometry.hpp000066400000000000000000000041351254023100400202670ustar00rootroot00000000000000#ifndef slic3r_Geometry_hpp_ #define slic3r_Geometry_hpp_ #include "BoundingBox.hpp" #include "Polygon.hpp" #include "Polyline.hpp" #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; namespace Slic3r { namespace Geometry { Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); void chained_path(const Points &points, std::vector &retval, Point start_near); void chained_path(const Points &points, std::vector &retval); template void chained_path_items(Points &points, T &items, T &retval); bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); double rad2deg(double angle); double rad2deg_dir(double angle); double deg2rad(double angle); void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); class ArrangeItem { public: Pointf pos; size_t index_x, index_y; coordf_t dist; }; class ArrangeItemIndex { public: coordf_t index; ArrangeItem item; ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; }; double linint(double value, double oldmin, double oldmax, double newmin, double newmax); Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf &bb = BoundingBoxf()); class MedialAxis { public: Points points; Lines lines; double max_width; double min_width; MedialAxis(double _max_width, double _min_width) : max_width(_max_width), min_width(_min_width) {}; void build(Polylines* polylines); private: typedef voronoi_diagram VD; VD vd; std::set edges; Line edge_to_line(const VD::edge_type &edge) const; void process_edge_neighbors(const voronoi_diagram::edge_type& edge, Points* points); bool is_valid_edge(const voronoi_diagram::edge_type& edge) const; const Line& retrieve_segment(const voronoi_diagram::cell_type& cell) const; }; } } #endif Slic3r-1.2.9/xs/src/libslic3r/Layer.cpp000066400000000000000000000073101254023100400175410ustar00rootroot00000000000000#include "Layer.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "Print.hpp" namespace Slic3r { Layer::Layer(size_t 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(); } size_t Layer::id() const { return this->_id; } void Layer::set_id(size_t id) { this->_id = 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; } // merge all regions' slices to get islands void Layer::make_slices() { ExPolygons slices; if (this->regions.size() == 1) { // optimization: if we only have one region, take its slices slices = this->regions.front()->slices; } else { Polygons slices_p; FOREACH_LAYERREGION(this, layerm) { Polygons region_slices_p = (*layerm)->slices; slices_p.insert(slices_p.end(), region_slices_p.begin(), region_slices_p.end()); } union_(slices_p, &slices); } this->slices.expolygons.clear(); this->slices.expolygons.reserve(slices.size()); // prepare ordering points Points ordering_points; ordering_points.reserve(slices.size()); for (ExPolygons::const_iterator ex = slices.begin(); ex != slices.end(); ++ex) ordering_points.push_back(ex->contour.first_point()); // sort slices std::vector order; Slic3r::Geometry::chained_path(ordering_points, order); // populate slices vector for (std::vector::const_iterator it = order.begin(); it != order.end(); ++it) { this->slices.expolygons.push_back(slices[*it]); } } void Layer::merge_slices() { FOREACH_LAYERREGION(this, layerm) { (*layerm)->merge_slices(); } } template bool Layer::any_internal_region_slice_contains(const T &item) const { FOREACH_LAYERREGION(this, layerm) { if ((*layerm)->slices.any_internal_contains(item)) return true; } return false; } template bool Layer::any_internal_region_slice_contains(const Polyline &item) const; template bool Layer::any_bottom_region_slice_contains(const T &item) const { FOREACH_LAYERREGION(this, layerm) { if ((*layerm)->slices.any_bottom_contains(item)) return true; } return false; } template bool Layer::any_bottom_region_slice_contains(const Polyline &item) const; #ifdef SLIC3RXS REGISTER_CLASS(Layer, "Layer"); #endif SupportLayer::SupportLayer(size_t 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 } Slic3r-1.2.9/xs/src/libslic3r/Layer.hpp000066400000000000000000000070111254023100400175440ustar00rootroot00000000000000#ifndef slic3r_Layer_hpp_ #define slic3r_Layer_hpp_ #include #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" #include "ExPolygonCollection.hpp" #include "PolylineCollection.hpp" namespace Slic3r { typedef std::pair t_layer_height_range; typedef std::map 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) // (this could be just a Polygons object) ExPolygonCollection bridged; // collection of polylines representing the unsupported bridge edges PolylineCollection unsupported_bridge_edges; // ordered collection of extrusion paths/loops to build all perimeters // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection perimeters; // ordered collection of extrusion paths to fill surfaces // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills; Flow flow(FlowRole role, bool bridge = false, double width = -1) const; void merge_slices(); void prepare_fill_surfaces(); private: Layer *_layer; PrintRegion *_region; LayerRegion(Layer *layer, PrintRegion *region); ~LayerRegion(); }; typedef std::vector LayerRegionPtrs; class Layer { friend class PrintObject; public: size_t id() const; void set_id(size_t 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); void make_slices(); void merge_slices(); template bool any_internal_region_slice_contains(const T &item) const; template bool any_bottom_region_slice_contains(const T &item) const; protected: size_t _id; // sequential number of layer, 0-based PrintObject *_object; Layer(size_t 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(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z); virtual ~SupportLayer(); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/LayerRegion.cpp000066400000000000000000000056101254023100400207060ustar00rootroot00000000000000#include "Layer.hpp" #include "ClipperUtils.hpp" #include "Print.hpp" #include "Surface.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; } Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { return this->_region->flow( role, this->_layer->height, bridge, this->_layer->id() == 0, width, *this->_layer->object() ); } void LayerRegion::merge_slices() { ExPolygons expp; // without safety offset, artifacts are generated (GH #2494) union_(this->slices, &expp, true); this->slices.surfaces.clear(); this->slices.surfaces.reserve(expp.size()); for (ExPolygons::const_iterator expoly = expp.begin(); expoly != expp.end(); ++expoly) this->slices.surfaces.push_back(Surface(stInternal, *expoly)); } void LayerRegion::prepare_fill_surfaces() { /* 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 (this->region()->config.top_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stTop) { if (this->layer()->object()->config.infill_only_where_needed) { surface->surface_type = stInternalVoid; } else { surface->surface_type = stInternal; } } } } if (this->region()->config.bottom_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) surface->surface_type = stInternal; } } // turn too small internal regions into solid regions according to the user setting if (this->region()->config.fill_density.value > 0) { // scaling an area requires two calls! double min_area = scale_(scale_(this->region()->config.solid_infill_below_area.value)); for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stInternal && surface->area() <= min_area) surface->surface_type = stInternalSolid; } } } #ifdef SLIC3RXS REGISTER_CLASS(LayerRegion, "Layer::Region"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Line.cpp000066400000000000000000000106451254023100400173610ustar00rootroot00000000000000#include "Geometry.hpp" #include "Line.hpp" #include "Polyline.hpp" #include #include #include namespace Slic3r { std::string Line::wkt() const { std::ostringstream ss; ss << "LINESTRING(" << this->a.x << " " << this->a.y << "," << this->b.x << " " << this->b.y << ")"; return ss.str(); } Line::operator Lines() const { Lines lines; lines.push_back(*this); return lines; } Line::operator Polyline() const { Polyline pl; pl.points.push_back(this->a); pl.points.push_back(this->b); return pl; } void Line::scale(double factor) { this->a.scale(factor); this->b.scale(factor); } void Line::translate(double x, double y) { this->a.translate(x, y); this->b.translate(x, y); } void Line::rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); } void Line::reverse() { std::swap(this->a, this->b); } double Line::length() const { return this->a.distance_to(this->b); } Point Line::midpoint() const { return Point((this->a.x + this->b.x) / 2.0, (this->a.y + this->b.y) / 2.0); } void Line::point_at(double distance, Point* point) const { double len = this->length(); *point = this->a; if (this->a.x != this->b.x) point->x = this->a.x + (this->b.x - this->a.x) * distance / len; if (this->a.y != this->b.y) point->y = this->a.y + (this->b.y - this->a.y) * distance / len; } Point Line::point_at(double distance) const { Point p; this->point_at(distance, &p); return p; } bool Line::intersection_infinite(const Line &other, Point* point) const { Vector x = this->a.vector_to(other.a); Vector d1 = this->vector(); Vector d2 = other.vector(); double cross = d1.x * d2.y - d1.y * d2.x; if (std::fabs(cross) < EPSILON) return false; double t1 = (x.x * d2.y - x.y * d2.x)/cross; point->x = this->a.x + d1.x * t1; point->y = this->a.y + d1.y * t1; return true; } bool Line::coincides_with(const Line &line) const { return this->a.coincides_with(line.a) && this->b.coincides_with(line.b); } double Line::distance_to(const Point &point) const { return point.distance_to(*this); } double Line::atan2_() const { return atan2(this->b.y - this->a.y, this->b.x - this->a.x); } double Line::orientation() const { double angle = this->atan2_(); if (angle < 0) angle = 2*PI + angle; return angle; } double Line::direction() const { double atan2 = this->atan2_(); return (fabs(atan2 - PI) < EPSILON) ? 0 : (atan2 < 0) ? (atan2 + PI) : 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 { return Vector(this->b.x - this->a.x, this->b.y - this->a.y); } Vector Line::normal() const { return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x)); } #ifdef SLIC3RXS REGISTER_CLASS(Line, "Line"); void Line::from_SV(SV* line_sv) { AV* line_av = (AV*)SvRV(line_sv); this->a.from_SV_check(*av_fetch(line_av, 0, 0)); this->b.from_SV_check(*av_fetch(line_av, 1, 0)); } void Line::from_SV_check(SV* line_sv) { if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) { 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); } } SV* Line::to_AV() { AV* av = newAV(); av_extend(av, 1); 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_pureperl() const { AV* av = newAV(); av_extend(av, 1); av_store(av, 0, this->a.to_SV_pureperl()); av_store(av, 1, this->b.to_SV_pureperl()); return newRV_noinc((SV*)av); } #endif Pointf3 Linef3::intersect_plane(double z) const { return Pointf3( this->a.x + (this->b.x - this->a.x) * (z - this->a.z) / (this->b.z - this->a.z), this->a.y + (this->b.y - this->a.y) * (z - this->a.z) / (this->b.z - this->a.z), z ); } void Linef3::scale(double factor) { this->a.scale(factor); this->b.scale(factor); } #ifdef SLIC3RXS REGISTER_CLASS(Linef3, "Linef3"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Line.hpp000066400000000000000000000042501254023100400173610ustar00rootroot00000000000000#ifndef slic3r_Line_hpp_ #define slic3r_Line_hpp_ #include #include "Point.hpp" namespace Slic3r { class Line; class Linef3; class Polyline; typedef std::vector Lines; class Line { public: Point a; Point b; Line() {}; explicit Line(Point _a, Point _b): a(_a), b(_b) {}; std::string wkt() const; operator Lines() const; operator Polyline() const; void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); void reverse(); double length() const; Point midpoint() const; void point_at(double distance, Point* point) const; Point point_at(double distance) const; bool intersection_infinite(const Line &other, 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 orientation() const; double direction() const; Vector vector() const; Vector normal() const; #ifdef SLIC3RXS void from_SV(SV* line_sv); void from_SV_check(SV* line_sv); SV* to_AV(); SV* to_SV_pureperl() const; #endif }; class Linef { public: Pointf a; Pointf b; Linef() {}; explicit Linef(Pointf _a, Pointf _b): a(_a), b(_b) {}; }; class Linef3 { public: Pointf3 a; Pointf3 b; Linef3() {}; explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {}; Pointf3 intersect_plane(double z) const; void scale(double factor); #ifdef SLIC3RXS void from_SV(SV* line_sv); void from_SV_check(SV* line_sv); SV* to_SV_pureperl() const; #endif }; } // start Boost #include namespace boost { namespace polygon { template <> struct geometry_concept { typedef segment_concept type; }; template <> struct segment_traits { typedef coord_t coordinate_type; typedef Point point_type; static inline point_type get(const Line& line, direction_1d dir) { return dir.to_int() ? line.b : line.a; } }; } } // end Boost #endif Slic3r-1.2.9/xs/src/libslic3r/Model.cpp000066400000000000000000000467421254023100400175410ustar00rootroot00000000000000#include "Model.hpp" #include "Geometry.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, true); } 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, bool copy_volumes) { ModelObject* new_object = new ModelObject(this, other, copy_volumes); 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(); 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; } // makes sure all objects have at least one instance bool Model::add_default_instances() { bool added = false; // apply a default position to all objects not having one for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { if ((*o)->instances.empty()) { (*o)->add_instance(); added = true; } } return true; } // this returns the bounding box of the *transformed* instances BoundingBoxf3 Model::bounding_box() const { BoundingBoxf3 bb; for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { bb.merge((*o)->bounding_box()); } return bb; } void Model::center_instances_around_point(const Pointf &point) { BoundingBoxf3 bb = this->bounding_box(); Sizef3 size = bb.size(); double shift_x = -bb.min.x + point.x - size.x/2; double shift_y = -bb.min.y + point.y - size.y/2; for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { for (ModelInstancePtrs::const_iterator i = (*o)->instances.begin(); i != (*o)->instances.end(); ++i) { (*i)->offset.translate(shift_x, shift_y); } (*o)->update_bounding_box(); } } void Model::align_instances_to_origin() { BoundingBoxf3 bb = this->bounding_box(); Pointf new_center = (Pointf)bb.size(); new_center.translate(-new_center.x/2, -new_center.y/2); this->center_instances_around_point(new_center); } void Model::translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { (*o)->translate(x, y, z); } } // flattens everything to a single mesh TriangleMesh Model::mesh() const { TriangleMesh mesh; for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { mesh.merge((*o)->mesh()); } return mesh; } // flattens everything to a single mesh TriangleMesh Model::raw_mesh() const { TriangleMesh mesh; for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { mesh.merge((*o)->raw_mesh()); } return mesh; } #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, bool copy_volumes) : model(model), name(other.name), 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) { if (copy_volumes) { 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); } // this returns the bounding box of the *transformed* instances BoundingBoxf3 ModelObject::bounding_box() { if (!this->_bounding_box_valid) this->update_bounding_box(); return this->_bounding_box; } void ModelObject::invalidate_bounding_box() { this->_bounding_box_valid = false; } void ModelObject::update_bounding_box() { this->_bounding_box = this->mesh().bounding_box(); this->_bounding_box_valid = true; } // flattens all volumes and instances into a single mesh TriangleMesh ModelObject::mesh() const { TriangleMesh mesh; TriangleMesh raw_mesh = this->raw_mesh(); for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) { TriangleMesh m = raw_mesh; (*i)->transform_mesh(&m); mesh.merge(m); } return mesh; } TriangleMesh ModelObject::raw_mesh() const { TriangleMesh mesh; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { if ((*v)->modifier) continue; mesh.merge((*v)->mesh); } return mesh; } BoundingBoxf3 ModelObject::raw_bounding_box() const { BoundingBoxf3 bb; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { if ((*v)->modifier) continue; TriangleMesh mesh = (*v)->mesh; if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); this->instances.front()->transform_mesh(&mesh, true); bb.merge(mesh.bounding_box()); } return bb; } // this returns the bounding box of the *transformed* given instance BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx) const { TriangleMesh mesh = this->raw_mesh(); this->instances[instance_idx]->transform_mesh(&mesh); return mesh.bounding_box(); } void ModelObject::center_around_origin() { // calculate the displacements needed to // center this object around the origin BoundingBoxf3 bb = this->raw_mesh().bounding_box(); // first align to origin on XYZ Vectorf3 vector(-bb.min.x, -bb.min.y, -bb.min.z); // then center it on XY Sizef3 size = bb.size(); vector.x -= size.x/2; vector.y -= size.y/2; this->translate(vector); this->origin_translation.translate(vector); if (!this->instances.empty()) { for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) { // apply rotation and scaling to vector as well before translating instance, // in order to leave final position unaltered Vectorf3 v = vector.negative(); v.rotate((*i)->rotation, (*i)->offset); v.scale((*i)->scaling_factor); (*i)->offset.translate(v.x, v.y); } this->update_bounding_box(); } } void ModelObject::translate(const Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); } void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { (*v)->mesh.translate(x, y, z); } if (this->_bounding_box_valid) this->_bounding_box.translate(x, y, z); } void ModelObject::scale(const Pointf3 &versor) { for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { (*v)->mesh.scale(versor); } // reset origin translation since it doesn't make sense anymore this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } void ModelObject::rotate(float angle, const Axis &axis) { for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { (*v)->mesh.rotate(angle, axis); } this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } void ModelObject::flip(const Axis &axis) { for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { (*v)->mesh.flip(axis); } this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } size_t ModelObject::materials_count() const { std::set material_ids; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { material_ids.insert((*v)->material_id()); } return material_ids.size(); } size_t ModelObject::facets_count() const { size_t num = 0; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { if ((*v)->modifier) continue; num += (*v)->mesh.stl.stats.number_of_facets; } return num; } bool ModelObject::needed_repair() const { for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { if ((*v)->modifier) continue; if ((*v)->mesh.needed_repair()) return true; } return false; } void ModelObject::cut(coordf_t z, Model* model) const { // clone this one to duplicate instances, materials etc. ModelObject* upper = model->add_object(*this); ModelObject* lower = model->add_object(*this); upper->clear_volumes(); lower->clear_volumes(); for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { ModelVolume* volume = *v; if (volume->modifier) { // don't cut modifiers upper->add_volume(*volume); lower->add_volume(*volume); } else { TriangleMeshSlicer tms(&volume->mesh); TriangleMesh upper_mesh, lower_mesh; // TODO: shouldn't we use object bounding box instead of per-volume bb? tms.cut(z + volume->mesh.bounding_box().min.z, &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) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); } if (lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); } } } } void ModelObject::split(ModelObjectPtrs* new_objects) { if (this->volumes.size() > 1) { // We can't split meshes if there's more than one volume, because // we can't group the resulting meshes by object afterwards new_objects->push_back(this); return; } ModelVolume* volume = this->volumes.front(); TriangleMeshPtrs meshptrs = volume->mesh.split(); for (TriangleMeshPtrs::iterator mesh = meshptrs.begin(); mesh != meshptrs.end(); ++mesh) { (*mesh)->repair(); ModelObject* new_object = this->model->add_object(*this, false); ModelVolume* new_volume = new_object->add_volume(**mesh); new_volume->name = volume->name; new_volume->config = volume->config; new_volume->modifier = volume->modifier; new_volume->material_id(volume->material_id()); new_objects->push_back(new_object); delete *mesh; } return; } #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), name(other.name), mesh(other.mesh), config(other.config), 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); } void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material) { this->_material_id = material_id; (void)this->object->get_model()->add_material(material_id, material); } ModelMaterial* ModelVolume::assign_unique_material() { Model* model = this->get_object()->get_model(); // as material-id "0" is reserved by the AMF spec we start from 1 this->_material_id = 1 + model->materials.size(); // watchout for implicit cast return model->add_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) {} void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->rotate_z(this->rotation); // rotate around mesh origin mesh->scale(this->scaling_factor); // scale around mesh origin if (!dont_translate) mesh->translate(this->offset.x, this->offset.y, 0); } void ModelInstance::transform_polygon(Polygon* polygon) const { polygon->rotate(this->rotation, Point(0,0)); // rotate around polygon origin polygon->scale(this->scaling_factor); // scale around polygon origin } #ifdef SLIC3RXS REGISTER_CLASS(ModelInstance, "Model::Instance"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Model.hpp000066400000000000000000000141711254023100400175350ustar00rootroot00000000000000#ifndef slic3r_Model_hpp_ #define slic3r_Model_hpp_ #include #include "PrintConfig.hpp" #include "Layer.hpp" #include "Point.hpp" #include "TriangleMesh.hpp" #include #include #include #include namespace Slic3r { class ModelInstance; class ModelMaterial; class ModelObject; class ModelVolume; typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; typedef std::map ModelMaterialMap; typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; class Model { public: ModelMaterialMap materials; ModelObjectPtrs objects; Model(); Model(const Model &other); Model& operator= (Model other); void swap(Model &other); ~Model(); ModelObject* add_object(); ModelObject* add_object(const ModelObject &other, bool copy_volumes = true); void delete_object(size_t idx); void clear_objects(); ModelMaterial* add_material(t_model_material_id material_id); ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); ModelMaterial* get_material(t_model_material_id material_id); void delete_material(t_model_material_id material_id); void clear_materials(); // void duplicate_objects_grid(unsigned int x, unsigned int y, coordf_t distance); // void duplicate_objects(size_t copies_num, coordf_t distance, const BoundingBox &bb); // void arrange_objects(coordf_t distance, const BoundingBox &bb); // void duplicate(size_t copies_num, coordf_t distance, const BoundingBox &bb); bool has_objects_with_no_instances() const; bool add_default_instances(); BoundingBoxf3 bounding_box() const; void center_instances_around_point(const Pointf &point); void align_instances_to_origin(); void translate(coordf_t x, coordf_t y, coordf_t z); TriangleMesh mesh() const; TriangleMesh raw_mesh() const; // std::string get_material_name(t_model_material_id material_id); private: void _arrange(const std::vector &sizes, coordf_t distance, const BoundingBox &bb) const; }; class ModelMaterial { friend class Model; public: t_model_material_attributes attributes; DynamicPrintConfig config; Model* get_model() const { return this->model; }; void apply(const t_model_material_attributes &attributes); private: Model* model; ModelMaterial(Model *model); ModelMaterial(Model *model, const ModelMaterial &other); }; class ModelObject { friend class Model; public: std::string name; std::string input_file; ModelInstancePtrs instances; ModelVolumePtrs volumes; DynamicPrintConfig config; t_layer_height_ranges layer_height_ranges; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment when user expects that. */ Pointf3 origin_translation; // these should be private but we need to expose them via XS until all methods are ported BoundingBoxf3 _bounding_box; bool _bounding_box_valid; Model* get_model() const { return this->model; }; ModelVolume* add_volume(const TriangleMesh &mesh); ModelVolume* add_volume(const ModelVolume &volume); void delete_volume(size_t idx); void clear_volumes(); ModelInstance* add_instance(); ModelInstance* add_instance(const ModelInstance &instance); void delete_instance(size_t idx); void delete_last_instance(); void clear_instances(); BoundingBoxf3 bounding_box(); void invalidate_bounding_box(); TriangleMesh mesh() const; TriangleMesh raw_mesh() const; BoundingBoxf3 raw_bounding_box() const; BoundingBoxf3 instance_bounding_box(size_t instance_idx) const; void center_around_origin(); void translate(const Vectorf3 &vector); void translate(coordf_t x, coordf_t y, coordf_t z); void scale(const Pointf3 &versor); void rotate(float angle, const Axis &axis); void flip(const Axis &axis); size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; void cut(coordf_t z, Model* model) const; void split(ModelObjectPtrs* new_objects); void update_bounding_box(); // this is a private method but we expose it until we need to expose it via XS private: Model* model; ModelObject(Model *model); ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true); ModelObject& operator= (ModelObject other); void swap(ModelObject &other); ~ModelObject(); }; class ModelVolume { friend class ModelObject; public: std::string name; TriangleMesh mesh; DynamicPrintConfig config; bool modifier; ModelObject* get_object() const { return this->object; }; t_model_material_id material_id() const; void material_id(t_model_material_id material_id); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); ModelMaterial* assign_unique_material(); private: ModelObject* object; t_model_material_id _material_id; ModelVolume(ModelObject *object, const TriangleMesh &mesh); ModelVolume(ModelObject *object, const ModelVolume &other); }; class ModelInstance { friend class ModelObject; public: double rotation; // in radians around mesh center point double scaling_factor; Pointf offset; // in unscaled coordinates ModelObject* get_object() const { return this->object; }; void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; void transform_polygon(Polygon* polygon) const; private: ModelObject* object; ModelInstance(ModelObject *object); ModelInstance(ModelObject *object, const ModelInstance &other); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/MotionPlanner.cpp000066400000000000000000000334731254023100400212630ustar00rootroot00000000000000#include "BoundingBox.hpp" #include "MotionPlanner.hpp" #include // for numeric_limits #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; namespace Slic3r { MotionPlanner::MotionPlanner(const ExPolygons &islands) : islands(islands), initialized(false) {} MotionPlanner::~MotionPlanner() { for (std::vector::iterator graph = this->graphs.begin(); graph != this->graphs.end(); ++graph) delete *graph; } size_t MotionPlanner::islands_count() const { return this->islands.size(); } void MotionPlanner::initialize() { if (this->initialized) return; if (this->islands.empty()) return; // prevent initialization of empty BoundingBox ExPolygons expp; for (ExPolygons::const_iterator island = this->islands.begin(); island != this->islands.end(); ++island) { island->simplify(SCALED_EPSILON, expp); } this->islands = expp; // loop through islands in order to create inner expolygons and collect their contours this->inner.reserve(this->islands.size()); Polygons outer_holes; for (ExPolygons::const_iterator island = this->islands.begin(); island != this->islands.end(); ++island) { this->inner.push_back(ExPolygonCollection()); offset(*island, &this->inner.back().expolygons, -MP_INNER_MARGIN); outer_holes.push_back(island->contour); } // grow island contours in order to prepare holes of the outer environment // This is actually wrong because it might merge contours that are close, // thus confusing the island check in shortest_path() below //offset(outer_holes, &outer_holes, +MP_OUTER_MARGIN); // generate outer contour as bounding box of everything Points points; for (Polygons::const_iterator contour = outer_holes.begin(); contour != outer_holes.end(); ++contour) points.insert(points.end(), contour->points.begin(), contour->points.end()); BoundingBox bb(points); // grow outer contour Polygons contour; offset(bb.polygon(), &contour, +MP_OUTER_MARGIN); assert(contour.size() == 1); // make expolygon for outer environment ExPolygons outer; diff(contour, outer_holes, &outer); assert(outer.size() == 1); this->outer = outer.front(); this->graphs.resize(this->islands.size() + 1, NULL); this->initialized = true; } ExPolygonCollection MotionPlanner::get_env(size_t island_idx) const { if (island_idx == -1) { return ExPolygonCollection(this->outer); } else { return this->inner[island_idx]; } } Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) { // lazy generation of configuration space if (!this->initialized) this->initialize(); // if we have an empty configuration space, return a straight move if (this->islands.empty()) { Polyline p; p.points.push_back(from); p.points.push_back(to); return p; } // Are both points in the same island? int island_idx = -1; for (ExPolygons::const_iterator island = this->islands.begin(); island != this->islands.end(); ++island) { if (island->contains(from) && island->contains(to)) { // since both points are in the same island, is a direct move possible? // if so, we avoid generating the visibility environment if (island->contains(Line(from, to))) { Polyline p; p.points.push_back(from); p.points.push_back(to); return p; } island_idx = island - this->islands.begin(); break; } } // get environment ExPolygonCollection env = this->get_env(island_idx); if (env.expolygons.empty()) { // if this environment is empty (probably because it's too small), perform straight move // and avoid running the algorithms on empty dataset Polyline p; p.points.push_back(from); p.points.push_back(to); return p; // bye bye } // Now check whether points are inside the environment. Point inner_from = from; Point inner_to = to; bool from_is_inside, to_is_inside; if (!(from_is_inside = env.contains(from))) { // Find the closest inner point to start from. inner_from = this->nearest_env_point(env, from, to); } if (!(to_is_inside = env.contains(to))) { // Find the closest inner point to start from. inner_to = this->nearest_env_point(env, to, inner_from); } // perform actual path search MotionPlannerGraph* graph = this->init_graph(island_idx); Polyline polyline = graph->shortest_path(graph->find_node(inner_from), graph->find_node(inner_to)); polyline.points.insert(polyline.points.begin(), from); polyline.points.push_back(to); { // grow our environment slightly in order for simplify_by_visibility() // to work best by considering moves on boundaries valid as well ExPolygonCollection grown_env; offset(env, &grown_env.expolygons, +SCALED_EPSILON); // remove unnecessary vertices polyline.simplify_by_visibility(grown_env); } /* SVG svg("shortest_path.svg"); svg.draw(this->outer); svg.arrows = false; for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) { Point a = graph->nodes[it - graph->adjacency_list.begin()]; for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) { Point b = graph->nodes[n->target]; svg.draw(Line(a, b)); } } svg.arrows = true; svg.draw(from); svg.draw(inner_from, "red"); svg.draw(to); svg.draw(inner_to, "red"); svg.draw(*polyline, "red"); svg.Close(); */ return polyline; } Point MotionPlanner::nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const { /* In order to ensure that the move between 'from' and the initial env point does not violate any of the configuration space boundaries, we limit our search to the points that satisfy this condition. */ /* Assume that this method is never called when 'env' contains 'from'; so 'from' is either inside a hole or outside all contours */ // get the points of the hole containing 'from', if any Points pp; for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) { for (Polygons::const_iterator h = ex->holes.begin(); h != ex->holes.end(); ++h) { if (h->contains(from)) { pp = *h; } } if (!pp.empty()) break; } /* If 'from' is not inside a hole, it's outside of all contours, so take all contours' points */ if (pp.empty()) { for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) { Points contour_pp = ex->contour; pp.insert(pp.end(), contour_pp.begin(), contour_pp.end()); } } /* Find the candidate result and check that it doesn't cross any boundary. (We could skip all of the above polygon finding logic and directly test all points in env, but this way we probably reduce complexity). */ Polygons env_pp = env; while (pp.size() >= 2) { // find the point in pp that is closest to both 'from' and 'to' size_t result = from.nearest_waypoint_index(pp, to); if (intersects((Lines)Line(from, pp[result]), env_pp)) { // discard result pp.erase(pp.begin() + result); } else { return pp[result]; } } // if we're here, return last point if any (better than nothing) if (!pp.empty()) return pp.front(); // if we have no points at all, then we have an empty environment and we // make this method behave as a no-op (we shouldn't get here by the way) return from; } MotionPlannerGraph* MotionPlanner::init_graph(int island_idx) { if (this->graphs[island_idx + 1] == NULL) { // if this graph doesn't exist, initialize it MotionPlannerGraph* graph = this->graphs[island_idx + 1] = new MotionPlannerGraph(); /* We don't add polygon boundaries as graph edges, because we'd need to connect them to the Voronoi-generated edges by recognizing coinciding nodes. */ typedef voronoi_diagram VD; VD vd; // mapping between Voronoi vertices and graph nodes typedef std::map t_vd_vertices; t_vd_vertices vd_vertices; // get boundaries as lines ExPolygonCollection env = this->get_env(island_idx); Lines lines = env.lines(); boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); // traverse the Voronoi diagram and generate graph nodes and edges for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) { if (edge->is_infinite()) continue; const VD::vertex_type* v0 = edge->vertex0(); const VD::vertex_type* v1 = edge->vertex1(); Point p0 = Point(v0->x(), v0->y()); Point p1 = Point(v1->x(), v1->y()); // skip edge if any of its endpoints is outside our configuration space if (!env.contains_b(p0) || !env.contains_b(p1)) continue; t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0); size_t v0_idx; if (i_v0 == vd_vertices.end()) { graph->nodes.push_back(p0); vd_vertices[v0] = v0_idx = graph->nodes.size()-1; } else { v0_idx = i_v0->second; } t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1); size_t v1_idx; if (i_v1 == vd_vertices.end()) { graph->nodes.push_back(p1); vd_vertices[v1] = v1_idx = graph->nodes.size()-1; } else { v1_idx = i_v1->second; } // Euclidean distance is used as weight for the graph edge double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]); graph->add_edge(v0_idx, v1_idx, dist); } return graph; } return this->graphs[island_idx + 1]; } void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) { // extend adjacency list until this start node if (this->adjacency_list.size() < from+1) this->adjacency_list.resize(from+1); this->adjacency_list[from].push_back(neighbor(to, weight)); } size_t MotionPlannerGraph::find_node(const Point &point) const { /* for (Points::const_iterator p = this->nodes.begin(); p != this->nodes.end(); ++p) { if (p->coincides_with(point)) return p - this->nodes.begin(); } */ return point.nearest_point_index(this->nodes); } Polyline MotionPlannerGraph::shortest_path(size_t from, size_t to) { // this prevents a crash in case for some reason we got here with an empty adjacency list if (this->adjacency_list.empty()) return Polyline(); const weight_t max_weight = std::numeric_limits::infinity(); std::vector dist; std::vector previous; { // number of nodes size_t n = this->adjacency_list.size(); // initialize dist and previous dist.clear(); dist.resize(n, max_weight); dist[from] = 0; // distance from 'from' to itself previous.clear(); previous.resize(n, -1); // initialize the Q with all nodes std::set Q; for (node_t i = 0; i < n; ++i) Q.insert(i); while (!Q.empty()) { // get node in Q having the minimum dist ('from' in the first loop) node_t u; { double min_dist = -1; for (std::set::const_iterator n = Q.begin(); n != Q.end(); ++n) { if (dist[*n] < min_dist || min_dist == -1) { u = *n; min_dist = dist[*n]; } } } Q.erase(u); // stop searching if we reached our destination if (u == to) break; // Visit each edge starting from node u const std::vector &neighbors = this->adjacency_list[u]; for (std::vector::const_iterator neighbor_iter = neighbors.begin(); neighbor_iter != neighbors.end(); neighbor_iter++) { // neighbor node is v node_t v = neighbor_iter->target; // skip if we already visited this if (Q.find(v) == Q.end()) continue; // calculate total distance weight_t alt = dist[u] + neighbor_iter->weight; // if total distance through u is shorter than the previous // distance (if any) between 'from' and 'v', replace it if (alt < dist[v]) { dist[v] = alt; previous[v] = u; } } } } Polyline polyline; for (node_t vertex = to; vertex != -1; vertex = previous[vertex]) polyline.points.push_back(this->nodes[vertex]); polyline.points.push_back(this->nodes[from]); polyline.reverse(); return polyline; } #ifdef SLIC3RXS REGISTER_CLASS(MotionPlanner, "MotionPlanner"); #endif } Slic3r-1.2.9/xs/src/libslic3r/MotionPlanner.hpp000066400000000000000000000031571254023100400212640ustar00rootroot00000000000000#ifndef slic3r_MotionPlanner_hpp_ #define slic3r_MotionPlanner_hpp_ #include #include "ClipperUtils.hpp" #include "ExPolygonCollection.hpp" #include "Polyline.hpp" #include #include #include #define MP_INNER_MARGIN scale_(1.0) #define MP_OUTER_MARGIN scale_(2.0) namespace Slic3r { class MotionPlannerGraph; class MotionPlanner { public: MotionPlanner(const ExPolygons &islands); ~MotionPlanner(); Polyline shortest_path(const Point &from, const Point &to); size_t islands_count() const; private: ExPolygons islands; bool initialized; ExPolygon outer; ExPolygonCollections inner; std::vector graphs; void initialize(); MotionPlannerGraph* init_graph(int island_idx); ExPolygonCollection get_env(size_t island_idx) const; Point nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const; }; class MotionPlannerGraph { friend class MotionPlanner; private: typedef size_t node_t; typedef double weight_t; struct neighbor { node_t target; weight_t weight; neighbor(node_t arg_target, weight_t arg_weight) : target(arg_target), weight(arg_weight) { } }; typedef std::vector< std::vector > adjacency_list_t; adjacency_list_t adjacency_list; public: Points nodes; //std::map, double> edges; void add_edge(size_t from, size_t to, double weight); size_t find_node(const Point &point) const; Polyline shortest_path(size_t from, size_t to); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/MultiPoint.cpp000066400000000000000000000106031254023100400205700ustar00rootroot00000000000000#include "MultiPoint.hpp" #include "BoundingBox.hpp" namespace Slic3r { MultiPoint::operator Points() const { return this->points; } void MultiPoint::scale(double factor) { for (Points::iterator it = points.begin(); it != points.end(); ++it) { (*it).scale(factor); } } void MultiPoint::translate(double x, double y) { for (Points::iterator it = points.begin(); it != points.end(); ++it) { (*it).translate(x, y); } } void MultiPoint::translate(const Point &vector) { this->translate(vector.x, vector.y); } void MultiPoint::rotate(double angle, const Point ¢er) { for (Points::iterator it = points.begin(); it != points.end(); ++it) { (*it).rotate(angle, center); } } void MultiPoint::reverse() { std::reverse(this->points.begin(), this->points.end()); } Point MultiPoint::first_point() const { return this->points.front(); } double MultiPoint::length() const { Lines lines = this->lines(); double len = 0; for (Lines::iterator it = lines.begin(); it != lines.end(); ++it) { len += it->length(); } return len; } bool MultiPoint::is_valid() const { return this->points.size() >= 2; } int MultiPoint::find_point(const Point &point) const { for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { if (it->coincides_with(point)) return it - this->points.begin(); } return -1; // not found } bool MultiPoint::has_boundary_point(const Point &point) const { double dist = point.distance_to(point.projection_onto(*this)); return dist < SCALED_EPSILON; } BoundingBox MultiPoint::bounding_box() const { return BoundingBox(this->points); } void MultiPoint::remove_duplicate_points() { for (size_t i = 1; i < this->points.size(); ++i) { if (this->points.at(i).coincides_with(this->points.at(i-1))) { this->points.erase(this->points.begin() + i); --i; } } } Points MultiPoint::_douglas_peucker(const Points &points, const double tolerance) { Points results; double dmax = 0; size_t index = 0; Line full(points.front(), points.back()); for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) { // we use shortest distance, not perpendicular distance double d = it->distance_to(full); if (d > dmax) { index = it - points.begin(); dmax = d; } } if (dmax >= tolerance) { Points dp0; dp0.reserve(index + 1); dp0.insert(dp0.end(), points.begin(), points.begin() + index + 1); Points dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); results.reserve(results.size() + dp1.size() - 1); results.insert(results.end(), dp1.begin(), dp1.end() - 1); dp0.clear(); dp0.reserve(points.size() - index + 1); dp0.insert(dp0.end(), points.begin() + index, points.end()); dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); results.reserve(results.size() + dp1.size()); results.insert(results.end(), dp1.begin(), dp1.end()); } else { results.push_back(points.front()); results.push_back(points.back()); } return results; } #ifdef SLIC3RXS void MultiPoint::from_SV(SV* poly_sv) { AV* poly_av = (AV*)SvRV(poly_sv); const unsigned int num_points = av_len(poly_av)+1; this->points.resize(num_points); for (unsigned int i = 0; i < num_points; i++) { SV** point_sv = av_fetch(poly_av, i, 0); this->points[i].from_SV_check(*point_sv); } } void MultiPoint::from_SV_check(SV* poly_sv) { if (sv_isobject(poly_sv) && (SvTYPE(SvRV(poly_sv)) == SVt_PVMG)) { *this = *(MultiPoint*)SvIV((SV*)SvRV( poly_sv )); } else { this->from_SV(poly_sv); } } SV* MultiPoint::to_AV() { const unsigned int num_points = this->points.size(); AV* av = newAV(); if (num_points > 0) av_extend(av, num_points-1); for (unsigned int i = 0; i < num_points; i++) { av_store(av, i, perl_to_SV_ref(this->points[i])); } return newRV_noinc((SV*)av); } SV* MultiPoint::to_SV_pureperl() const { const unsigned int num_points = this->points.size(); AV* av = newAV(); if (num_points > 0) av_extend(av, num_points-1); for (unsigned int i = 0; i < num_points; i++) { av_store(av, i, this->points[i].to_SV_pureperl()); } return newRV_noinc((SV*)av); } #endif } Slic3r-1.2.9/xs/src/libslic3r/MultiPoint.hpp000066400000000000000000000022021254023100400205710ustar00rootroot00000000000000#ifndef slic3r_MultiPoint_hpp_ #define slic3r_MultiPoint_hpp_ #include #include #include #include "Line.hpp" #include "Point.hpp" namespace Slic3r { class BoundingBox; class MultiPoint { public: Points points; operator Points() const; MultiPoint() {}; explicit MultiPoint(const Points &_points): points(_points) {}; void scale(double factor); void translate(double x, double y); void translate(const Point &vector); void rotate(double angle, const Point ¢er); void reverse(); Point first_point() const; virtual Point last_point() const = 0; virtual Lines lines() const = 0; double length() const; bool is_valid() const; int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; BoundingBox bounding_box() const; void remove_duplicate_points(); static Points _douglas_peucker(const Points &points, const double tolerance); #ifdef SLIC3RXS void from_SV(SV* poly_sv); void from_SV_check(SV* poly_sv); SV* to_AV(); SV* to_SV_pureperl() const; #endif }; } #endif Slic3r-1.2.9/xs/src/libslic3r/PlaceholderParser.cpp000066400000000000000000000064461254023100400220750ustar00rootroot00000000000000#include "PlaceholderParser.hpp" #include #include #include namespace Slic3r { PlaceholderParser::PlaceholderParser() { this->_single["version"] = SLIC3R_VERSION; // TODO: port these methods to C++, then call them here // this->apply_env_variables(); this->update_timestamp(); } void PlaceholderParser::update_timestamp() { time_t rawtime; time(&rawtime); struct tm* timeinfo = localtime(&rawtime); { std::ostringstream ss; ss << (1900 + timeinfo->tm_year); ss << std::setw(2) << std::setfill('0') << (1 + timeinfo->tm_mon); ss << std::setw(2) << std::setfill('0') << timeinfo->tm_mday; ss << "-"; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_hour; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_min; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_sec; this->set("timestamp", ss.str()); } this->set("year", 1900 + timeinfo->tm_year); this->set("month", 1 + timeinfo->tm_mon); this->set("day", timeinfo->tm_mday); this->set("hour", timeinfo->tm_hour); this->set("minute", timeinfo->tm_min); this->set("second", timeinfo->tm_sec); } void PlaceholderParser::apply_config(DynamicPrintConfig &config) { // options that are set and aren't text-boxes t_config_option_keys opt_keys; for (t_optiondef_map::iterator i = config.def->begin(); i != config.def->end(); ++i) { const t_config_option_key &key = i->first; const ConfigOptionDef &def = i->second; if (config.has(key) && !def.multiline) { opt_keys.push_back(key); } } for (t_config_option_keys::iterator i = opt_keys.begin(); i != opt_keys.end(); ++i) { const t_config_option_key &key = *i; const ConfigOption* opt = config.option(key); if (const ConfigOptionVectorBase* optv = dynamic_cast(opt)) { // set placeholders for options with multiple values this->set(key, optv->vserialize()); } else if (const ConfigOptionPoint* optp = dynamic_cast(opt)) { this->_single[key] = optp->serialize(); Pointf val = *optp; this->_multiple[key + "_X"] = val.x; this->_multiple[key + "_Y"] = val.y; } else { // set single-value placeholders this->_single[key] = opt->serialize(); } } } void PlaceholderParser::set(const std::string &key, const std::string &value) { this->_single[key] = value; this->_multiple.erase(key); } void PlaceholderParser::set(const std::string &key, int value) { std::ostringstream ss; ss << value; this->set(key, ss.str()); } void PlaceholderParser::set(const std::string &key, const std::vector &values) { for (std::vector::const_iterator v = values.begin(); v != values.end(); ++v) { std::stringstream ss; ss << key << "_" << (v - values.begin()); this->_multiple[ ss.str() ] = *v; if (v == values.begin()) { this->_multiple[key] = *v; } } this->_single.erase(key); } #ifdef SLIC3RXS REGISTER_CLASS(PlaceholderParser, "GCode::PlaceholderParser"); #endif } Slic3r-1.2.9/xs/src/libslic3r/PlaceholderParser.hpp000066400000000000000000000012051254023100400220660ustar00rootroot00000000000000#ifndef slic3r_PlaceholderParser_hpp_ #define slic3r_PlaceholderParser_hpp_ #include #include #include #include #include "PrintConfig.hpp" namespace Slic3r { class PlaceholderParser { public: std::map _single; std::map _multiple; PlaceholderParser(); void update_timestamp(); void apply_config(DynamicPrintConfig &config); void set(const std::string &key, const std::string &value); void set(const std::string &key, int value); void set(const std::string &key, const std::vector &values); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/Point.cpp000066400000000000000000000302041254023100400175540ustar00rootroot00000000000000#include "Point.hpp" #include "Line.hpp" #include "MultiPoint.hpp" #include #include namespace Slic3r { Point::Point(double x, double y) { this->x = lrint(x); this->y = lrint(y); } bool Point::operator==(const Point& rhs) const { return this->coincides_with(rhs); } std::string Point::wkt() const { std::ostringstream ss; ss << "POINT(" << this->x << " " << this->y << ")"; return ss.str(); } void Point::scale(double factor) { this->x *= factor; this->y *= factor; } void Point::translate(double x, double y) { this->x += x; this->y += y; } void Point::translate(const Vector &vector) { this->translate(vector.x, vector.y); } void Point::rotate(double angle, const Point ¢er) { double cur_x = (double)this->x; double cur_y = (double)this->y; this->x = (coord_t)round( (double)center.x + cos(angle) * (cur_x - (double)center.x) - sin(angle) * (cur_y - (double)center.y) ); this->y = (coord_t)round( (double)center.y + cos(angle) * (cur_y - (double)center.y) + sin(angle) * (cur_x - (double)center.x) ); } bool Point::coincides_with(const Point &point) const { return this->x == point.x && this->y == point.y; } bool Point::coincides_with_epsilon(const Point &point) const { return std::abs(this->x - point.x) < SCALED_EPSILON && std::abs(this->y - point.y) < SCALED_EPSILON; } int Point::nearest_point_index(const Points &points) const { PointConstPtrs p; p.reserve(points.size()); for (Points::const_iterator it = points.begin(); it != points.end(); ++it) p.push_back(&*it); return this->nearest_point_index(p); } int Point::nearest_point_index(const PointConstPtrs &points) const { int idx = -1; double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) { /* If the X distance of the candidate is > than the total distance of the best previous candidate, we know we don't want it */ double d = pow(this->x - (*it)->x, 2); if (distance != -1 && d > distance) continue; /* If the Y distance of the candidate is > than the total distance of the best previous candidate, we know we don't want it */ d += pow(this->y - (*it)->y, 2); if (distance != -1 && d > distance) continue; idx = it - points.begin(); distance = d; if (distance < EPSILON) break; } return idx; } /* This method finds the point that is closest to both this point and the supplied one */ size_t Point::nearest_waypoint_index(const Points &points, const Point &dest) const { size_t idx = -1; double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough for (Points::const_iterator p = points.begin(); p != points.end(); ++p) { // distance from this to candidate double d = pow(this->x - p->x, 2) + pow(this->y - p->y, 2); // distance from candidate to dest d += pow(p->x - dest.x, 2) + pow(p->y - dest.y, 2); // if the total distance is greater than current min distance, ignore it if (distance != -1 && d > distance) continue; idx = p - points.begin(); distance = d; if (distance < EPSILON) break; } return idx; } int Point::nearest_point_index(const PointPtrs &points) const { PointConstPtrs p; p.reserve(points.size()); for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it) p.push_back(*it); return this->nearest_point_index(p); } bool Point::nearest_point(const Points &points, Point* point) const { int idx = this->nearest_point_index(points); if (idx == -1) return false; *point = points.at(idx); return true; } bool Point::nearest_waypoint(const Points &points, const Point &dest, Point* point) const { int idx = this->nearest_waypoint_index(points, dest); if (idx == -1) return false; *point = points.at(idx); return true; } double Point::distance_to(const Point &point) const { double dx = ((double)point.x - this->x); double dy = ((double)point.y - this->y); return sqrt(dx*dx + dy*dy); } /* distance to the closest point of line */ double Point::distance_to(const Line &line) const { const double dx = line.b.x - line.a.x; const double dy = line.b.y - line.a.y; const double l2 = dx*dx + dy*dy; // avoid a sqrt if (l2 == 0.0) return this->distance_to(line.a); // line.a == line.b case // Consider the line extending the segment, parameterized as line.a + t (line.b - line.a). // We find projection of this point onto the line. // It falls where t = [(this-line.a) . (line.b-line.a)] / |line.b-line.a|^2 const double t = ((this->x - line.a.x) * dx + (this->y - line.a.y) * dy) / l2; if (t < 0.0) return this->distance_to(line.a); // beyond the 'a' end of the segment else if (t > 1.0) return this->distance_to(line.b); // beyond the 'b' end of the segment Point projection( line.a.x + t * dx, line.a.y + t * dy ); return this->distance_to(projection); } double Point::perp_distance_to(const Line &line) const { if (line.a.coincides_with(line.b)) return this->distance_to(line.a); double n = (double)(line.b.x - line.a.x) * (double)(line.a.y - this->y) - (double)(line.a.x - this->x) * (double)(line.b.y - line.a.y); return std::abs(n) / line.length(); } /* Three points are a counter-clockwise turn if ccw > 0, clockwise if * ccw < 0, and collinear if ccw = 0 because ccw is a determinant that * gives the signed area of the triangle formed by p1, p2 and this point. * In other words it is the 2D cross product of p1-p2 and p1-this, i.e. * z-component of their 3D cross product. * We return double because it must be big enough to hold 2*max(|coordinate|)^2 */ double Point::ccw(const Point &p1, const Point &p2) const { return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x); } double Point::ccw(const Line &line) const { return this->ccw(line.a, line.b); } // returns the CCW angle between this-p1 and this-p2 // i.e. this assumes a CCW rotation from p1 to p2 around this double Point::ccw_angle(const Point &p1, const Point &p2) const { double angle = atan2(p1.x - this->x, p1.y - this->y) - atan2(p2.x - this->x, p2.y - this->y); // we only want to return only positive angles return angle <= 0 ? angle + 2*PI : angle; } Point Point::projection_onto(const MultiPoint &poly) const { Point running_projection = poly.first_point(); double running_min = this->distance_to(running_projection); Lines lines = poly.lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point point_temp = this->projection_onto(*line); if (this->distance_to(point_temp) < running_min) { running_projection = point_temp; running_min = this->distance_to(running_projection); } } return running_projection; } Point Point::projection_onto(const Line &line) const { if (line.a.coincides_with(line.b)) return line.a; /* (Ported from VisiLibity by Karl J. Obermeyer) The projection of point_temp onto the line determined by line_segment_temp can be represented as an affine combination expressed in the form projection of Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second. If theta is outside the interval [0,1], then one of the Line_Segment's endpoints must be closest to calling Point. */ double theta = ( (double)(line.b.x - this->x)*(double)(line.b.x - line.a.x) + (double)(line.b.y- this->y)*(double)(line.b.y - line.a.y) ) / ( (double)pow(line.b.x - line.a.x, 2) + (double)pow(line.b.y - line.a.y, 2) ); if (0.0 <= theta && theta <= 1.0) return theta * line.a + (1.0-theta) * line.b; // Else pick closest endpoint. if (this->distance_to(line.a) < this->distance_to(line.b)) { return line.a; } else { return line.b; } } Point Point::negative() const { return Point(-this->x, -this->y); } Vector Point::vector_to(const Point &point) const { return Vector(point.x - this->x, point.y - this->y); } Point operator+(const Point& point1, const Point& point2) { return Point(point1.x + point2.x, point1.y + point2.y); } Point operator*(double scalar, const Point& point2) { return Point(scalar * point2.x, scalar * point2.y); } #ifdef SLIC3RXS REGISTER_CLASS(Point, "Point"); SV* Point::to_SV_pureperl() const { AV* av = newAV(); av_fill(av, 1); av_store(av, 0, newSViv(this->x)); av_store(av, 1, newSViv(this->y)); return newRV_noinc((SV*)av); } void Point::from_SV(SV* point_sv) { AV* point_av = (AV*)SvRV(point_sv); // get a double from Perl and round it, otherwise // it would get truncated this->x = lrint(SvNV(*av_fetch(point_av, 0, 0))); this->y = lrint(SvNV(*av_fetch(point_av, 1, 0))); } void Point::from_SV_check(SV* point_sv) { if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { if (!sv_isa(point_sv, perl_class_name(this)) && !sv_isa(point_sv, perl_class_name_ref(this))) CONFESS("Not a valid %s object (got %s)", perl_class_name(this), HvNAME(SvSTASH(SvRV(point_sv)))); *this = *(Point*)SvIV((SV*)SvRV( point_sv )); } else { this->from_SV(point_sv); } } REGISTER_CLASS(Point3, "Point3"); #endif std::ostream& operator<<(std::ostream &stm, const Pointf &pointf) { return stm << pointf.x << "," << pointf.y; } void Pointf::scale(double factor) { this->x *= factor; this->y *= factor; } void Pointf::translate(double x, double y) { this->x += x; this->y += y; } void Pointf::rotate(double angle, const Pointf ¢er) { double cur_x = this->x; double cur_y = this->y; this->x = center.x + cos(angle) * (cur_x - center.x) - sin(angle) * (cur_y - center.y); this->y = center.y + cos(angle) * (cur_y - center.y) + sin(angle) * (cur_x - center.x); } Pointf Pointf::negative() const { return Pointf(-this->x, -this->y); } Vectorf Pointf::vector_to(const Pointf &point) const { return Vectorf(point.x - this->x, point.y - this->y); } #ifdef SLIC3RXS REGISTER_CLASS(Pointf, "Pointf"); SV* Pointf::to_SV_pureperl() const { AV* av = newAV(); av_fill(av, 1); av_store(av, 0, newSVnv(this->x)); av_store(av, 1, newSVnv(this->y)); return newRV_noinc((SV*)av); } bool Pointf::from_SV(SV* point_sv) { AV* point_av = (AV*)SvRV(point_sv); SV* sv_x = *av_fetch(point_av, 0, 0); SV* sv_y = *av_fetch(point_av, 1, 0); if (!looks_like_number(sv_x) || !looks_like_number(sv_y)) return false; this->x = SvNV(sv_x); this->y = SvNV(sv_y); return true; } bool Pointf::from_SV_check(SV* point_sv) { if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { if (!sv_isa(point_sv, perl_class_name(this)) && !sv_isa(point_sv, perl_class_name_ref(this))) CONFESS("Not a valid %s object (got %s)", perl_class_name(this), HvNAME(SvSTASH(SvRV(point_sv)))); *this = *(Pointf*)SvIV((SV*)SvRV( point_sv )); return true; } else { return this->from_SV(point_sv); } } #endif void Pointf3::scale(double factor) { Pointf::scale(factor); this->z *= factor; } void Pointf3::translate(const Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); } void Pointf3::translate(double x, double y, double z) { Pointf::translate(x, y); this->z += z; } double Pointf3::distance_to(const Pointf3 &point) const { double dx = ((double)point.x - this->x); double dy = ((double)point.y - this->y); double dz = ((double)point.z - this->z); return sqrt(dx*dx + dy*dy + dz*dz); } Pointf3 Pointf3::negative() const { return Pointf3(-this->x, -this->y, -this->z); } Vectorf3 Pointf3::vector_to(const Pointf3 &point) const { return Vectorf3(point.x - this->x, point.y - this->y, point.z - this->z); } #ifdef SLIC3RXS REGISTER_CLASS(Pointf3, "Pointf3"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Point.hpp000066400000000000000000000123001254023100400175560ustar00rootroot00000000000000#ifndef slic3r_Point_hpp_ #define slic3r_Point_hpp_ #include #include #include #include #include namespace Slic3r { class Line; class Linef; class MultiPoint; class Point; class Pointf; class Pointf3; typedef Point Vector; typedef Pointf Vectorf; typedef Pointf3 Vectorf3; typedef std::vector Points; typedef std::vector PointPtrs; typedef std::vector PointConstPtrs; typedef std::vector Pointfs; typedef std::vector Pointf3s; class Point { public: coord_t x; coord_t y; Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; Point(int _x, int _y): x(_x), y(_y) {}; Point(long long _x, long long _y): x(_x), y(_y) {}; // for Clipper Point(double x, double y); static Point new_scale(coordf_t x, coordf_t y) { return Point(scale_(x), scale_(y)); }; bool operator==(const Point& rhs) const; std::string wkt() const; void scale(double factor); void translate(double x, double y); void translate(const Vector &vector); void rotate(double angle, const Point ¢er); bool coincides_with(const Point &point) const; bool coincides_with_epsilon(const Point &point) const; int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointPtrs &points) const; size_t nearest_waypoint_index(const Points &points, const Point &point) const; bool nearest_point(const Points &points, Point* point) const; bool nearest_waypoint(const Points &points, const Point &dest, Point* point) const; double distance_to(const Point &point) const; double distance_to(const Line &line) const; double perp_distance_to(const Line &line) const; double ccw(const Point &p1, const Point &p2) const; double ccw(const Line &line) const; double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; Point negative() const; Vector vector_to(const Point &point) const; #ifdef SLIC3RXS void from_SV(SV* point_sv); void from_SV_check(SV* point_sv); SV* to_SV_pureperl() const; #endif }; Point operator+(const Point& point1, const Point& point2); Point operator*(double scalar, const Point& point2); class Point3 : public Point { public: coord_t z; explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {}; }; std::ostream& operator<<(std::ostream &stm, const Pointf &pointf); class Pointf { public: coordf_t x; coordf_t y; explicit Pointf(coordf_t _x = 0, coordf_t _y = 0): x(_x), y(_y) {}; static Pointf new_unscale(coord_t x, coord_t y) { return Pointf(unscale(x), unscale(y)); }; static Pointf new_unscale(const Point &p) { return Pointf(unscale(p.x), unscale(p.y)); }; void scale(double factor); void translate(double x, double y); void rotate(double angle, const Pointf ¢er); Pointf negative() const; Vectorf vector_to(const Pointf &point) const; #ifdef SLIC3RXS bool from_SV(SV* point_sv); bool from_SV_check(SV* point_sv); SV* to_SV_pureperl() const; #endif }; class Pointf3 : public Pointf { public: coordf_t z; explicit Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {}; static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) { return Pointf3(unscale(x), unscale(y), unscale(z)); }; void scale(double factor); void translate(const Vectorf3 &vector); void translate(double x, double y, double z); double distance_to(const Pointf3 &point) const; Pointf3 negative() const; Vectorf3 vector_to(const Pointf3 &point) const; }; } // start Boost #include namespace boost { namespace polygon { template <> struct geometry_concept { typedef coordinate_concept type; }; template <> struct coordinate_traits { typedef coord_t coordinate_type; typedef long double area_type; typedef long long manhattan_area_type; typedef unsigned long long unsigned_area_type; typedef long long coordinate_difference; typedef long double coordinate_distance; }; template <> struct geometry_concept { typedef point_concept type; }; template <> struct point_traits { typedef coord_t coordinate_type; static inline coordinate_type get(const Point& point, orientation_2d orient) { return (orient == HORIZONTAL) ? point.x : point.y; } }; template <> struct point_mutable_traits { typedef coord_t coordinate_type; static inline void set(Point& point, orientation_2d orient, coord_t value) { if (orient == HORIZONTAL) point.x = value; else point.y = value; } static inline Point construct(coord_t x_value, coord_t y_value) { Point retval; retval.x = x_value; retval.y = y_value; return retval; } }; } } // end Boost #endif Slic3r-1.2.9/xs/src/libslic3r/Polygon.cpp000066400000000000000000000167411254023100400201240ustar00rootroot00000000000000#include #include "ClipperUtils.hpp" #include "Polygon.hpp" #include "Polyline.hpp" namespace Slic3r { Polygon::operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } Polygon::operator Polyline() const { return this->split_at_first_point(); } Point& Polygon::operator[](Points::size_type idx) { return this->points[idx]; } const Point& Polygon::operator[](Points::size_type idx) const { return this->points[idx]; } Point Polygon::last_point() const { return this->points.front(); // last point == first point for polygons } Lines Polygon::lines() const { Lines lines; lines.reserve(this->points.size()); for (Points::const_iterator it = this->points.begin(); it != this->points.end()-1; ++it) { lines.push_back(Line(*it, *(it + 1))); } lines.push_back(Line(this->points.back(), this->points.front())); return lines; } Polyline Polygon::split_at_vertex(const Point &point) const { // find index of point for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { if (it->coincides_with(point)) { return this->split_at_index(it - this->points.begin()); } } CONFESS("Point not found"); return Polyline(); } Polyline Polygon::split_at_index(int index) const { Polyline polyline; polyline.points.reserve(this->points.size() + 1); for (Points::const_iterator it = this->points.begin() + index; it != this->points.end(); ++it) polyline.points.push_back(*it); for (Points::const_iterator it = this->points.begin(); it != this->points.begin() + index + 1; ++it) polyline.points.push_back(*it); return polyline; } Polyline Polygon::split_at_first_point() const { return this->split_at_index(0); } Points Polygon::equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); } double Polygon::area() const { ClipperLib::Path p; Slic3rMultiPoint_to_ClipperPath(*this, &p); return ClipperLib::Area(p); } bool Polygon::is_counter_clockwise() const { ClipperLib::Path p; Slic3rMultiPoint_to_ClipperPath(*this, &p); return ClipperLib::Orientation(p); } bool Polygon::is_clockwise() const { return !this->is_counter_clockwise(); } bool Polygon::make_counter_clockwise() { if (!this->is_counter_clockwise()) { this->reverse(); return true; } return false; } bool Polygon::make_clockwise() { if (this->is_counter_clockwise()) { this->reverse(); return true; } return false; } bool Polygon::is_valid() const { return this->points.size() >= 3; } bool Polygon::contains(const Point &point) const { // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html bool result = false; Points::const_iterator i = this->points.begin(); Points::const_iterator j = this->points.end() - 1; for (; i != this->points.end(); j = i++) { if ( ((i->y > point.y) != (j->y > point.y)) && ((double)point.x < (double)(j->x - i->x) * (double)(point.y - i->y) / (double)(j->y - i->y) + (double)i->x) ) result = !result; } return result; } // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() Polygons Polygon::simplify(double tolerance) const { // repeat first point at the end in order to apply Douglas-Peucker // on the whole polygon Points points = this->points; points.push_back(points.front()); Polygon p(MultiPoint::_douglas_peucker(points, tolerance)); p.points.pop_back(); Polygons pp; pp.push_back(p); simplify_polygons(pp, &pp); return pp; } void Polygon::simplify(double tolerance, Polygons &polygons) const { Polygons pp = this->simplify(tolerance); polygons.reserve(polygons.size() + pp.size()); polygons.insert(polygons.end(), pp.begin(), pp.end()); } // Only call this on convex polygons or it will return invalid results void Polygon::triangulate_convex(Polygons* polygons) const { for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) { Polygon p; p.points.reserve(3); p.points.push_back(this->points.front()); p.points.push_back(*(it-1)); p.points.push_back(*it); // this should be replaced with a more efficient call to a merge_collinear_segments() method if (p.area() > 0) polygons->push_back(p); } } // center of mass Point Polygon::centroid() const { double area_temp = this->area(); double x_temp = 0; double y_temp = 0; Polyline polyline = this->split_at_first_point(); for (Points::const_iterator point = polyline.points.begin(); point != polyline.points.end() - 1; ++point) { x_temp += (double)( point->x + (point+1)->x ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y ); y_temp += (double)( point->y + (point+1)->y ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y ); } return Point(x_temp/(6*area_temp), y_temp/(6*area_temp)); } std::string Polygon::wkt() const { std::ostringstream wkt; wkt << "POLYGON(("; for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { wkt << p->x << " " << p->y; if (p != this->points.end()-1) wkt << ","; } wkt << "))"; return wkt.str(); } // find all concave vertices (i.e. having an internal angle greater than the supplied angle) // (external = right side, thus we consider ccw orientation) Points Polygon::concave_points(double angle) const { Points points; angle = 2*PI - angle; // check whether first point forms a concave angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) points.push_back(this->points.front()); // check whether points 1..(n-1) form concave angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points.push_back(*p); } // check whether last point forms a concave angle if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) points.push_back(this->points.back()); return points; } // find all convex vertices (i.e. having an internal angle smaller than the supplied angle) // (external = right side, thus we consider ccw orientation) Points Polygon::convex_points(double angle) const { Points points; angle = 2*PI - angle; // check whether first point forms a convex angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) points.push_back(this->points.front()); // check whether points 1..(n-1) form convex angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points.push_back(*p); } // check whether last point forms a convex angle if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) points.push_back(this->points.back()); return points; } #ifdef SLIC3RXS REGISTER_CLASS(Polygon, "Polygon"); void Polygon::from_SV_check(SV* poly_sv) { if (sv_isobject(poly_sv) && !sv_isa(poly_sv, perl_class_name(this)) && !sv_isa(poly_sv, perl_class_name_ref(this))) CONFESS("Not a valid %s object", perl_class_name(this)); MultiPoint::from_SV_check(poly_sv); } #endif } Slic3r-1.2.9/xs/src/libslic3r/Polygon.hpp000066400000000000000000000102041254023100400201150ustar00rootroot00000000000000#ifndef slic3r_Polygon_hpp_ #define slic3r_Polygon_hpp_ #include #include #include #include "Line.hpp" #include "MultiPoint.hpp" #include "Polyline.hpp" namespace Slic3r { class Polygon; typedef std::vector Polygons; class Polygon : public MultiPoint { public: operator Polygons() const; operator Polyline() const; Point& operator[](Points::size_type idx); const Point& operator[](Points::size_type idx) const; Polygon() {}; explicit Polygon(const Points &points): MultiPoint(points) {}; Point last_point() const; Lines lines() const; Polyline split_at_vertex(const Point &point) const; Polyline split_at_index(int index) const; Polyline split_at_first_point() const; Points equally_spaced_points(double distance) const; double area() const; bool is_counter_clockwise() const; bool is_clockwise() const; bool make_counter_clockwise(); bool make_clockwise(); bool is_valid() const; bool contains(const Point &point) const; Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; void triangulate_convex(Polygons* polygons) const; Point centroid() const; std::string wkt() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; #ifdef SLIC3RXS void from_SV_check(SV* poly_sv); #endif }; } // start Boost #include namespace boost { namespace polygon { template <> struct geometry_concept{ typedef polygon_concept type; }; template <> struct polygon_traits { 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 Polygon& t) { return t.points.begin(); } // Get the end iterator static inline iterator_type end_points(const Polygon& t) { return t.points.end(); } // Get the number of sides of the polygon static inline std::size_t size(const Polygon& t) { return t.points.size(); } // Get the winding direction of the polygon static inline winding_direction winding(const Polygon& t) { return unknown_winding; } }; template <> struct polygon_mutable_traits { // expects stl style iterators template static inline Polygon& set_points(Polygon& polygon, iT input_begin, iT input_end) { polygon.points.clear(); while (input_begin != input_end) { polygon.points.push_back(Point()); boost::polygon::assign(polygon.points.back(), *input_begin); ++input_begin; } // skip last point since Boost will set last point = first point polygon.points.pop_back(); return polygon; } }; template <> struct geometry_concept { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> struct polygon_set_traits { typedef coord_t coordinate_type; typedef Polygons::const_iterator iterator_type; typedef Polygons operator_arg_type; static inline iterator_type begin(const Polygons& polygon_set) { return polygon_set.begin(); } static inline iterator_type end(const Polygons& polygon_set) { return polygon_set.end(); } //don't worry about these, just return false from them static inline bool clean(const Polygons& polygon_set) { return false; } static inline bool sorted(const Polygons& polygon_set) { return false; } }; template <> struct polygon_set_mutable_traits { template static inline void set(Polygons& polygons, input_iterator_type input_begin, input_iterator_type input_end) { polygons.assign(input_begin, input_end); } }; } } // end Boost #endif Slic3r-1.2.9/xs/src/libslic3r/Polyline.cpp000066400000000000000000000170031254023100400202600ustar00rootroot00000000000000#include "Polyline.hpp" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include "Line.hpp" #include "Polygon.hpp" #include namespace Slic3r { Polyline::operator Polylines() const { Polylines polylines; polylines.push_back(*this); return polylines; } Polyline::operator Line() const { if (this->points.size() > 2) CONFESS("Can't convert polyline with more than two points to a line"); return Line(this->points.front(), this->points.back()); } Point Polyline::last_point() const { return this->points.back(); } Point Polyline::leftmost_point() const { Point p = this->points.front(); for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { if (it->x < p.x) p = *it; } return p; } Lines Polyline::lines() const { Lines lines; if (this->points.size() >= 2) { lines.reserve(this->points.size() - 1); for (Points::const_iterator it = this->points.begin(); it != this->points.end()-1; ++it) { lines.push_back(Line(*it, *(it + 1))); } } return lines; } // removes the given distance from the end of the polyline void Polyline::clip_end(double distance) { while (distance > 0) { Point last_point = this->last_point(); this->points.pop_back(); if (this->points.empty()) break; double last_segment_length = last_point.distance_to(this->last_point()); if (last_segment_length <= distance) { distance -= last_segment_length; continue; } Line segment(last_point, this->last_point()); this->points.push_back(segment.point_at(distance)); distance = 0; } } // removes the given distance from the start of the polyline void Polyline::clip_start(double distance) { this->reverse(); this->clip_end(distance); if (this->points.size() >= 2) this->reverse(); } void Polyline::extend_end(double distance) { // relocate last point by extending the last segment by the specified length Line line(this->points[ this->points.size()-2 ], this->points.back()); this->points.pop_back(); this->points.push_back(line.point_at(line.length() + distance)); } void Polyline::extend_start(double distance) { // relocate first point by extending the first segment by the specified length Line line(this->points[1], this->points.front()); this->points[0] = line.point_at(line.length() + distance); } /* this method returns a collection of points picked on the polygon contour so that they are evenly spaced according to the input distance */ Points Polyline::equally_spaced_points(double distance) const { Points points; points.push_back(this->first_point()); double len = 0; for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { double segment_length = it->distance_to(*(it-1)); len += segment_length; if (len < distance) continue; if (len == distance) { points.push_back(*it); len = 0; continue; } double take = segment_length - (len - distance); // how much we take of this segment Line segment(*(it-1), *it); points.push_back(segment.point_at(take)); it--; len = -take; } return points; } void Polyline::simplify(double tolerance) { this->points = MultiPoint::_douglas_peucker(this->points, tolerance); } /* This method simplifies all *lines* contained in the supplied area */ template void Polyline::simplify_by_visibility(const T &area) { Points &pp = this->points; // find first point in area size_t s = 0; while (s < pp.size() && !area.contains(pp[s])) { ++s; } // find last point in area size_t e = pp.size()-1; while (e > 0 && !area.contains(pp[e])) { --e; } // this ugly algorithm resembles a binary search while (e > s + 1) { size_t mid = (s + e) / 2; if (area.contains(Line(pp[s], pp[mid]))) { pp.erase(pp.begin() + s + 1, pp.begin() + mid); // repeat recursively until no further simplification is possible ++s; e = pp.size()-1; } else { e = mid; } } /* // The following implementation is complete but it's not efficient at all: for (size_t s = start; s < pp.size() && !pp.empty(); ++s) { // find the farthest point to which we can build // a line that is contained in the supplied area // a binary search would be more efficient for this for (size_t e = pp.size()-1; e > (s + 1); --e) { if (area.contains(Line(pp[s], pp[e]))) { // we can suppress points between s and e pp.erase(pp.begin() + s + 1, pp.begin() + e); // repeat recursively until no further simplification is possible return this->simplify_by_visibility(area); } } } */ } template void Polyline::simplify_by_visibility(const ExPolygon &area); template void Polyline::simplify_by_visibility(const ExPolygonCollection &area); void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const { if (this->points.empty()) return; // find the line to split at size_t line_idx = 0; Point p = this->first_point(); double min = point.distance_to(p); Lines lines = this->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point p_tmp = point.projection_onto(*line); if (point.distance_to(p_tmp) < min) { p = p_tmp; min = point.distance_to(p); line_idx = line - lines.begin(); } } // create first half p1->points.clear(); for (Lines::const_iterator line = lines.begin(); line != lines.begin() + line_idx + 1; ++line) { if (!line->a.coincides_with(p)) p1->points.push_back(line->a); } // we add point instead of p because they might differ because of numerical issues // and caller might want to rely on point belonging to result polylines p1->points.push_back(point); // create second half p2->points.clear(); p2->points.push_back(point); for (Lines::const_iterator line = lines.begin() + line_idx; line != lines.end(); ++line) { p2->points.push_back(line->b); } } bool Polyline::is_straight() const { /* 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 cause the error to accumulate.) */ double dir = Line(this->first_point(), this->last_point()).direction(); Lines lines = this->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { if (!line->parallel_to(dir)) return false; } return true; } std::string Polyline::wkt() const { std::ostringstream wkt; wkt << "LINESTRING(("; for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { wkt << p->x << " " << p->y; if (p != this->points.end()-1) wkt << ","; } wkt << "))"; return wkt.str(); } #ifdef SLIC3RXS REGISTER_CLASS(Polyline, "Polyline"); void Polyline::from_SV_check(SV* poly_sv) { if (!sv_isa(poly_sv, perl_class_name(this)) && !sv_isa(poly_sv, perl_class_name_ref(this))) CONFESS("Not a valid %s object",perl_class_name(this)); MultiPoint::from_SV_check(poly_sv); } #endif } Slic3r-1.2.9/xs/src/libslic3r/Polyline.hpp000066400000000000000000000016641254023100400202730ustar00rootroot00000000000000#ifndef slic3r_Polyline_hpp_ #define slic3r_Polyline_hpp_ #include "Line.hpp" #include "MultiPoint.hpp" #include namespace Slic3r { class ExPolygon; class Polyline; typedef std::vector Polylines; class Polyline : public MultiPoint { public: operator Polylines() const; operator Line() const; Point last_point() const; Point leftmost_point() const; Lines lines() const; void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); Points equally_spaced_points(double distance) const; void simplify(double tolerance); template void simplify_by_visibility(const T &area); void split_at(const Point &point, Polyline* p1, Polyline* p2) const; bool is_straight() const; std::string wkt() const; #ifdef SLIC3RXS void from_SV_check(SV* poly_sv); #endif }; } #endif Slic3r-1.2.9/xs/src/libslic3r/PolylineCollection.cpp000066400000000000000000000034251254023100400222770ustar00rootroot00000000000000#include "PolylineCollection.hpp" namespace Slic3r { void PolylineCollection::chained_path(PolylineCollection* retval, bool no_reverse) const { if (this->polylines.empty()) return; this->chained_path_from(this->polylines.front().first_point(), retval, no_reverse); } void PolylineCollection::chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse) const { Polylines my_paths = this->polylines; Points endpoints; for (Polylines::const_iterator it = my_paths.begin(); it != my_paths.end(); ++it) { endpoints.push_back(it->first_point()); if (no_reverse) { endpoints.push_back(it->first_point()); } else { endpoints.push_back(it->last_point()); } } while (!my_paths.empty()) { // find nearest point int start_index = start_near.nearest_point_index(endpoints); int path_index = start_index/2; if (start_index % 2 && !no_reverse) { my_paths.at(path_index).reverse(); } retval->polylines.push_back(my_paths.at(path_index)); my_paths.erase(my_paths.begin() + path_index); endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); start_near = retval->polylines.back().last_point(); } } Point PolylineCollection::leftmost_point() const { if (this->polylines.empty()) CONFESS("leftmost_point() called on empty PolylineCollection"); Point p = this->polylines.front().leftmost_point(); for (Polylines::const_iterator it = this->polylines.begin() + 1; it != this->polylines.end(); ++it) { Point p2 = it->leftmost_point(); if (p2.x < p.x) p = p2; } return p; } #ifdef SLIC3RXS REGISTER_CLASS(PolylineCollection, "Polyline::Collection"); #endif } Slic3r-1.2.9/xs/src/libslic3r/PolylineCollection.hpp000066400000000000000000000006731254023100400223060ustar00rootroot00000000000000#ifndef slic3r_PolylineCollection_hpp_ #define slic3r_PolylineCollection_hpp_ #include #include "Polyline.hpp" namespace Slic3r { class PolylineCollection { public: Polylines polylines; void chained_path(PolylineCollection* retval, bool no_reverse = false) const; void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const; Point leftmost_point() const; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/Print.cpp000066400000000000000000000717051254023100400175720ustar00rootroot00000000000000#include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Flow.hpp" #include "Geometry.hpp" #include "SupportMaterial.hpp" #include namespace Slic3r { template bool PrintState::is_started(StepClass step) const { return this->started.find(step) != this->started.end(); } template bool PrintState::is_done(StepClass step) const { return this->done.find(step) != this->done.end(); } template void PrintState::set_started(StepClass step) { this->started.insert(step); } template void PrintState::set_done(StepClass step) { this->done.insert(step); } template bool PrintState::invalidate(StepClass step) { bool invalidated = this->started.erase(step) > 0; this->done.erase(step); return invalidated; } template class PrintState; template class PrintState; Print::Print() : total_used_filament(0), total_extruded_volume(0) { } Print::~Print() { clear_objects(); clear_regions(); } void Print::clear_objects() { for (int i = this->objects.size()-1; i >= 0; --i) this->delete_object(i); this->clear_regions(); } PrintObject* Print::get_object(size_t idx) { return objects.at(idx); } void Print::delete_object(size_t idx) { PrintObjectPtrs::iterator i = this->objects.begin() + idx; // before deleting object, invalidate all of its steps in order to // invalidate all of the dependent ones in Print (*i)->invalidate_all_steps(); // destroy object and remove it from our container delete *i; this->objects.erase(i); // TODO: purge unused regions } void Print::reload_object(size_t idx) { /* TODO: this method should check whether the per-object config and per-material configs have changed in such a way that regions need to be rearranged or we can just apply the diff and invalidate something. Same logic as apply_config() 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. */ // collect all current model objects ModelObjectPtrs model_objects; FOREACH_OBJECT(this, object) { model_objects.push_back((*object)->model_object()); } // remove our print objects this->clear_objects(); // re-add model objects for (ModelObjectPtrs::iterator it = model_objects.begin(); it != model_objects.end(); ++it) { this->add_model_object(*it); } } bool Print::reload_model_instances() { bool invalidated = false; FOREACH_OBJECT(this, object) { if ((*object)->reload_model_instances()) invalidated = true; } return invalidated; } void Print::clear_regions() { for (int i = this->regions.size()-1; i >= 0; --i) this->delete_region(i); } PrintRegion* Print::get_region(size_t idx) { return regions.at(idx); } PrintRegion* Print::add_region() { PrintRegion *region = new PrintRegion(this); regions.push_back(region); return region; } void Print::delete_region(size_t idx) { PrintRegionPtrs::iterator i = this->regions.begin() + idx; delete *i; this->regions.erase(i); } bool Print::invalidate_state_by_config_options(const std::vector &opt_keys) { std::set steps; std::set osteps; // this method only accepts PrintConfig option keys for (std::vector::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) { if (*opt_key == "skirts" || *opt_key == "skirt_height" || *opt_key == "skirt_distance" || *opt_key == "min_skirt_length" || *opt_key == "ooze_prevention") { steps.insert(psSkirt); } else if (*opt_key == "brim_width") { steps.insert(psBrim); steps.insert(psSkirt); } else if (*opt_key == "nozzle_diameter" || *opt_key == "resolution") { osteps.insert(posSlice); } else if (*opt_key == "avoid_crossing_perimeters" || *opt_key == "bed_shape" || *opt_key == "bed_temperature" || *opt_key == "bridge_acceleration" || *opt_key == "bridge_fan_speed" || *opt_key == "complete_objects" || *opt_key == "cooling" || *opt_key == "default_acceleration" || *opt_key == "disable_fan_first_layers" || *opt_key == "duplicate_distance" || *opt_key == "end_gcode" || *opt_key == "extruder_clearance_height" || *opt_key == "extruder_clearance_radius" || *opt_key == "extruder_offset" || *opt_key == "extrusion_axis" || *opt_key == "extrusion_multiplier" || *opt_key == "fan_always_on" || *opt_key == "fan_below_layer_time" || *opt_key == "filament_diameter" || *opt_key == "first_layer_acceleration" || *opt_key == "first_layer_bed_temperature" || *opt_key == "first_layer_speed" || *opt_key == "first_layer_temperature" || *opt_key == "gcode_arcs" || *opt_key == "gcode_comments" || *opt_key == "gcode_flavor" || *opt_key == "infill_acceleration" || *opt_key == "infill_first" || *opt_key == "layer_gcode" || *opt_key == "min_fan_speed" || *opt_key == "max_fan_speed" || *opt_key == "min_print_speed" || *opt_key == "notes" || *opt_key == "only_retract_when_crossing_perimeters" || *opt_key == "output_filename_format" || *opt_key == "perimeter_acceleration" || *opt_key == "post_process" || *opt_key == "pressure_advance" || *opt_key == "retract_before_travel" || *opt_key == "retract_layer_change" || *opt_key == "retract_length" || *opt_key == "retract_length_toolchange" || *opt_key == "retract_lift" || *opt_key == "retract_restart_extra" || *opt_key == "retract_restart_extra_toolchange" || *opt_key == "retract_speed" || *opt_key == "slowdown_below_layer_time" || *opt_key == "spiral_vase" || *opt_key == "standby_temperature_delta" || *opt_key == "start_gcode" || *opt_key == "temperature" || *opt_key == "threads" || *opt_key == "toolchange_gcode" || *opt_key == "travel_speed" || *opt_key == "use_firmware_retraction" || *opt_key == "use_relative_e_distances" || *opt_key == "vibration_limit" || *opt_key == "wipe" || *opt_key == "z_offset") { // these options only affect G-code export, so nothing to invalidate } else if (*opt_key == "first_layer_extrusion_width") { osteps.insert(posPerimeters); osteps.insert(posInfill); osteps.insert(posSupportMaterial); steps.insert(psSkirt); steps.insert(psBrim); } else { // for legacy, if we can't handle this option let's invalidate all steps return this->invalidate_all_steps(); } } bool invalidated = false; for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { if (this->invalidate_step(*step)) invalidated = true; } for (std::set::const_iterator ostep = osteps.begin(); ostep != osteps.end(); ++ostep) { FOREACH_OBJECT(this, object) { if ((*object)->invalidate_step(*ostep)) invalidated = true; } } return invalidated; } bool Print::invalidate_step(PrintStep step) { bool invalidated = this->state.invalidate(step); // propagate to dependent steps if (step == psSkirt) { this->invalidate_step(psBrim); } return invalidated; } bool Print::invalidate_all_steps() { // make a copy because when invalidating steps the iterators are not working anymore std::set steps = this->state.started; bool invalidated = false; for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { if (this->invalidate_step(*step)) invalidated = true; } return invalidated; } // returns true if an object step is done on all objects // and there's at least one object bool Print::step_done(PrintObjectStep step) const { if (this->objects.empty()) return false; FOREACH_OBJECT(this, object) { if (!(*object)->state.is_done(step)) return false; } return true; } // returns 0-based indices of used extruders std::set Print::object_extruders() const { std::set extruders; FOREACH_REGION(this, region) { // these checks reflect the same logic used in the GUI for enabling/disabling // extruder selection fields if ((*region)->config.perimeters.value > 0 || this->config.brim_width.value > 0) extruders.insert((*region)->config.perimeter_extruder - 1); if ((*region)->config.fill_density.value > 0) extruders.insert((*region)->config.infill_extruder - 1); if ((*region)->config.top_solid_layers.value > 0 || (*region)->config.bottom_solid_layers.value > 0) extruders.insert((*region)->config.solid_infill_extruder - 1); } return extruders; } // returns 0-based indices of used extruders std::set Print::support_material_extruders() const { std::set extruders; FOREACH_OBJECT(this, object) { if ((*object)->has_support_material()) { extruders.insert((*object)->config.support_material_extruder - 1); extruders.insert((*object)->config.support_material_interface_extruder - 1); } } return extruders; } // returns 0-based indices of used extruders std::set Print::extruders() const { std::set extruders = this->object_extruders(); std::set s_extruders = this->support_material_extruders(); extruders.insert(s_extruders.begin(), s_extruders.end()); return extruders; } void Print::_simplify_slices(double distance) { FOREACH_OBJECT(this, object) { FOREACH_LAYER(*object, layer) { (*layer)->slices.simplify(distance); FOREACH_LAYERREGION(*layer, layerm) { (*layerm)->slices.simplify(distance); } } } } double Print::max_allowed_layer_height() const { std::vector nozzle_diameter; std::set extruders = this->extruders(); for (std::set::const_iterator e = extruders.begin(); e != extruders.end(); ++e) { nozzle_diameter.push_back(this->config.nozzle_diameter.get_at(*e)); } return *std::max_element(nozzle_diameter.begin(), nozzle_diameter.end()); } /* Caller is responsible for supplying models whose objects don't collide and have explicit instance positions */ void Print::add_model_object(ModelObject* model_object, int idx) { DynamicPrintConfig object_config = model_object->config; // clone object_config.normalize(); // initialize print object and store it at the given position PrintObject* o; { BoundingBoxf3 bb = model_object->raw_bounding_box(); if (idx != -1) { // replacing existing object PrintObjectPtrs::iterator old_it = this->objects.begin() + idx; // before deleting object, invalidate all of its steps in order to // invalidate all of the dependent ones in Print (*old_it)->invalidate_all_steps(); delete *old_it; this->objects[idx] = o = new PrintObject(this, model_object, bb); } else { o = new PrintObject(this, model_object, bb); objects.push_back(o); // invalidate steps this->invalidate_step(psSkirt); this->invalidate_step(psBrim); } } for (ModelVolumePtrs::const_iterator v_i = model_object->volumes.begin(); v_i != model_object->volumes.end(); ++v_i) { size_t volume_id = v_i - model_object->volumes.begin(); ModelVolume* volume = *v_i; // get the config applied to this volume PrintRegionConfig config = this->_region_config_from_model_volume(*volume); // find an existing print region with the same config int region_id = -1; for (PrintRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region) { if (config.equals((*region)->config)) { region_id = region - this->regions.begin(); break; } } // if no region exists with the same config, create a new one if (region_id == -1) { PrintRegion* r = this->add_region(); r->config.apply(config); region_id = this->regions.size() - 1; } // assign volume to region o->add_region_volume(region_id, volume_id); } // apply config to print object o->config.apply(this->default_object_config); o->config.apply(object_config, true); } bool Print::apply_config(DynamicPrintConfig config) { // we get a copy of the config object so we can modify it safely config.normalize(); // apply variables to placeholder parser this->placeholder_parser.apply_config(config); bool invalidated = false; // handle changes to print config t_config_option_keys print_diff = this->config.diff(config); if (!print_diff.empty()) { this->config.apply(config, true); if (this->invalidate_state_by_config_options(print_diff)) invalidated = true; } // handle changes to object config defaults this->default_object_config.apply(config, true); FOREACH_OBJECT(this, obj_ptr) { // we don't assume that config contains a full ObjectConfig, // so we base it on the current print-wise default PrintObjectConfig new_config = this->default_object_config; new_config.apply(config, true); // we override the new config with object-specific options { DynamicPrintConfig model_object_config = (*obj_ptr)->model_object()->config; model_object_config.normalize(); new_config.apply(model_object_config, true); } // check whether the new config is different from the current one t_config_option_keys diff = (*obj_ptr)->config.diff(new_config); if (!diff.empty()) { (*obj_ptr)->config.apply(new_config, true); if ((*obj_ptr)->invalidate_state_by_config_options(diff)) invalidated = true; } } // handle changes to regions config defaults this->default_region_config.apply(config, true); // All regions now have distinct settings. // Check whether applying the new region config defaults we'd get different regions. bool rearrange_regions = false; std::vector other_region_configs; FOREACH_REGION(this, it_r) { size_t region_id = it_r - this->regions.begin(); PrintRegion* region = *it_r; std::vector this_region_configs; FOREACH_OBJECT(this, it_o) { PrintObject* object = *it_o; std::vector ®ion_volumes = object->region_volumes[region_id]; for (std::vector::const_iterator volume_id = region_volumes.begin(); volume_id != region_volumes.end(); ++volume_id) { ModelVolume* volume = object->model_object()->volumes.at(*volume_id); PrintRegionConfig new_config = this->_region_config_from_model_volume(*volume); for (std::vector::iterator it = this_region_configs.begin(); it != this_region_configs.end(); ++it) { // 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 if (!it->equals(new_config)) { rearrange_regions = true; goto NEXT_REGION; } } this_region_configs.push_back(new_config); for (std::vector::iterator it = other_region_configs.begin(); it != other_region_configs.end(); ++it) { // 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 if (it->equals(new_config)) { rearrange_regions = true; goto NEXT_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) t_config_option_keys region_config_diff = region->config.diff(new_config); if (!region_config_diff.empty()) { region->config.apply(new_config); FOREACH_OBJECT(this, o) { if ((*o)->invalidate_state_by_config_options(region_config_diff)) invalidated = true; } } } } other_region_configs.insert(other_region_configs.end(), this_region_configs.begin(), this_region_configs.end()); NEXT_REGION: continue; } if (rearrange_regions) { // the current subdivision of regions does not make sense anymore. // we need to remove all objects and re-add them ModelObjectPtrs model_objects; FOREACH_OBJECT(this, o) { model_objects.push_back((*o)->model_object()); } this->clear_objects(); for (ModelObjectPtrs::iterator it = model_objects.begin(); it != model_objects.end(); ++it) { this->add_model_object(*it); } invalidated = true; } return invalidated; } bool Print::has_infinite_skirt() const { return (this->config.skirt_height == -1 && this->config.skirts > 0) || (this->config.ooze_prevention && this->extruders().size() > 1); } bool Print::has_skirt() const { return (this->config.skirt_height > 0 && this->config.skirts > 0) || this->has_infinite_skirt(); } void Print::validate() const { if (this->config.complete_objects) { // check horizontal clearance { Polygons a; FOREACH_OBJECT(this, i_object) { PrintObject* object = *i_object; /* get convex hull of all meshes assigned to this print object (this is the same as model_object()->raw_mesh.convex_hull() but probably more efficient */ Polygon convex_hull; { Polygons mesh_convex_hulls; for (size_t i = 0; i < this->regions.size(); ++i) { for (std::vector::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) { Polygon hull = object->model_object()->volumes[*it]->mesh.convex_hull(); mesh_convex_hulls.push_back(hull); } } // make a single convex hull for all of them convex_hull = Slic3r::Geometry::convex_hull(mesh_convex_hulls); } // apply the same transformations we apply to the actual meshes when slicing them object->model_object()->instances.front()->transform_polygon(&convex_hull); // grow convex hull with the clearance margin { Polygons grown_hull; offset(convex_hull, &grown_hull, scale_(this->config.extruder_clearance_radius.value)/2, 1, jtRound, scale_(0.1)); convex_hull = grown_hull.front(); } // now we check that no instance of convex_hull intersects any of the previously checked object instances for (Points::const_iterator copy = object->_shifted_copies.begin(); copy != object->_shifted_copies.end(); ++copy) { Polygon p = convex_hull; p.translate(*copy); if (intersects(a, p)) throw PrintValidationException("Some objects are too close; your extruder will collide with them."); union_(a, p, &a); } } } // check vertical clearance { std::vector object_height; FOREACH_OBJECT(this, i_object) { PrintObject* object = *i_object; object_height.insert(object_height.end(), object->copies().size(), object->size.z); } std::sort(object_height.begin(), object_height.end()); // ignore the tallest *copy* (this is why we repeat height for all of them): // it will be printed as last one so its height doesn't matter object_height.pop_back(); if (!object_height.empty() && object_height.back() > scale_(this->config.extruder_clearance_height.value)) throw PrintValidationException("Some objects are too tall and cannot be printed without extruder collisions."); } } if (this->config.spiral_vase) { size_t total_copies_count = 0; FOREACH_OBJECT(this, i_object) total_copies_count += (*i_object)->copies().size(); if (total_copies_count > 1) throw PrintValidationException("The Spiral Vase option can only be used when printing a single object."); if (this->regions.size() > 1) throw PrintValidationException("The Spiral Vase option can only be used when printing single material objects."); } { // find the smallest nozzle diameter std::set extruders = this->extruders(); if (extruders.empty()) throw PrintValidationException("The supplied settings will cause an empty print."); std::set nozzle_diameters; for (std::set::iterator it = extruders.begin(); it != extruders.end(); ++it) nozzle_diameters.insert(this->config.nozzle_diameter.get_at(*it)); double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end()); FOREACH_OBJECT(this, i_object) { PrintObject* object = *i_object; // validate first_layer_height double first_layer_height = object->config.get_abs_value("first_layer_height"); double first_layer_min_nozzle_diameter; if (object->config.raft_layers > 0) { // if we have raft layers, only support material extruder is used on first layer size_t first_layer_extruder = object->config.raft_layers == 1 ? object->config.support_material_interface_extruder-1 : object->config.support_material_extruder-1; first_layer_min_nozzle_diameter = this->config.nozzle_diameter.get_at(first_layer_extruder); } else { // if we don't have raft layers, any nozzle diameter is potentially used in first layer first_layer_min_nozzle_diameter = min_nozzle_diameter; } if (first_layer_height > first_layer_min_nozzle_diameter) throw PrintValidationException("First layer height can't be greater than nozzle diameter"); // validate layer_height if (object->config.layer_height.value > min_nozzle_diameter) throw PrintValidationException("Layer height can't be greater than nozzle diameter"); } } } // the bounding box of objects placed in copies position // (without taking skirt/brim/support material into account) BoundingBox Print::bounding_box() const { BoundingBox bb; FOREACH_OBJECT(this, object) { for (Points::const_iterator copy = (*object)->_shifted_copies.begin(); copy != (*object)->_shifted_copies.end(); ++copy) { bb.merge(*copy); Point p = *copy; p.translate((*object)->size); bb.merge(p); } } return bb; } // the total bounding box of extrusions, including skirt/brim/support material // this methods needs to be called even when no steps were processed, so it should // only use configuration values BoundingBox Print::total_bounding_box() const { // get objects bounding box BoundingBox bb = this->bounding_box(); // we need to offset the objects bounding box by at least half the perimeters extrusion width Flow perimeter_flow = this->objects.front()->get_layer(0)->get_region(0)->flow(frPerimeter); double extra = perimeter_flow.width/2; // consider support material if (this->has_support_material()) { extra = std::max(extra, SUPPORT_MATERIAL_MARGIN); } // consider brim and skirt if (this->config.brim_width.value > 0) { Flow brim_flow = this->brim_flow(); extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2); } if (this->has_skirt()) { int skirts = this->config.skirts.value; if (skirts == 0 && this->has_infinite_skirt()) skirts = 1; Flow skirt_flow = this->skirt_flow(); extra = std::max( extra, this->config.brim_width.value + this->config.skirt_distance.value + skirts * skirt_flow.spacing() + skirt_flow.width/2 ); } if (extra > 0) bb.offset(scale_(extra)); return bb; } double Print::skirt_first_layer_height() const { if (this->objects.empty()) CONFESS("skirt_first_layer_height() can't be called without PrintObjects"); return this->objects.front()->config.get_abs_value("first_layer_height"); } Flow Print::brim_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; /* We currently use a random region's perimeter extruder. While this works for most cases, we should probably consider all of the perimeter extruders and take the one with, say, the smallest index. The same logic should be applied to the code that selects the extruder during G-code generation as well. */ return Flow::new_from_config_width( frPerimeter, width, this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1), this->skirt_first_layer_height(), 0 ); } Flow Print::skirt_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; /* We currently use a random object's support material extruder. While this works for most cases, we should probably consider all of the support material extruders and take the one with, say, the smallest index; The same logic should be applied to the code that selects the extruder during G-code generation as well. */ return Flow::new_from_config_width( frPerimeter, width, this->config.nozzle_diameter.get_at(this->objects.front()->config.support_material_extruder-1), this->skirt_first_layer_height(), 0 ); } PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume) { PrintRegionConfig config = this->default_region_config; { DynamicPrintConfig other_config = volume.get_object()->config; other_config.normalize(); config.apply(other_config, true); } { DynamicPrintConfig other_config = volume.config; other_config.normalize(); config.apply(other_config, true); } if (!volume.material_id().empty()) { DynamicPrintConfig material_config = volume.material()->config; material_config.normalize(); config.apply(material_config, true); } return config; } bool Print::has_support_material() const { FOREACH_OBJECT(this, object) { if ((*object)->has_support_material()) return true; } return false; } #ifdef SLIC3RXS REGISTER_CLASS(Print, "Print"); #endif } Slic3r-1.2.9/xs/src/libslic3r/Print.hpp000066400000000000000000000152771254023100400176010ustar00rootroot00000000000000#ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ #include #include #include #include #include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" #include "Point.hpp" #include "Layer.hpp" #include "Model.hpp" #include "PlaceholderParser.hpp" namespace Slic3r { class Print; class PrintObject; class ModelObject; enum PrintStep { psSkirt, psBrim, }; enum PrintObjectStep { posSlice, posPerimeters, posPrepareInfill, posInfill, posSupportMaterial, }; class PrintValidationException : public std::runtime_error { public: PrintValidationException(const std::string &error) : std::runtime_error(error) {}; }; template class PrintState { public: std::set started, done; bool is_started(StepType step) const; bool is_done(StepType step) const; void set_started(StepType step); void set_done(StepType step); bool invalidate(StepType step); }; // A PrintRegion object represents a group of volumes to print // sharing the same config (including the same assigned extruder(s)) class PrintRegion { friend class Print; public: PrintRegionConfig config; Print* print(); Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; private: Print* _print; PrintRegion(Print* print); ~PrintRegion(); }; typedef std::vector LayerPtrs; typedef std::vector SupportLayerPtrs; class BoundingBoxf3; // TODO: for temporary constructor parameter class PrintObject { friend class Print; public: // map of (vectors of volume ids), indexed by region_id /* (we use map instead of vector so that we don't have to worry about resizing it and the [] operator adds new items automagically) */ std::map< size_t,std::vector > region_volumes; PrintObjectConfig config; t_layer_height_ranges layer_height_ranges; // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool typed_slices; Point3 size; // XYZ in scaled coordinates // 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) Point _copies_shift; // Slic3r::Point objects in scaled G-code coordinates in our coordinates Points _shifted_copies; LayerPtrs layers; SupportLayerPtrs support_layers; // TODO: Fill* fill_maker => (is => 'lazy'); PrintState state; Print* print(); ModelObject* model_object(); Points copies() const; bool add_copy(const Pointf &point); bool delete_last_copy(); bool delete_all_copies(); bool set_copies(const Points &points); bool reload_model_instances(); BoundingBox bounding_box() const; // adds region_id, too, if necessary void add_region_volume(int region_id, int volume_id); size_t total_layer_count() const; size_t layer_count() const; void clear_layers(); Layer* get_layer(int idx); Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_layer(int idx); size_t support_layer_count() const; void clear_support_layers(); SupportLayer* get_support_layer(int idx); SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); void delete_support_layer(int idx); // methods for handling state bool invalidate_state_by_config_options(const std::vector &opt_keys); bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); bool has_support_material() const; void bridge_over_infill(); private: Print* _print; ModelObject* _model_object; Points _copies; // Slic3r::Point objects in scaled G-code coordinates // TODO: call model_object->get_bounding_box() instead of accepting // parameter PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox); ~PrintObject(); }; typedef std::vector PrintObjectPtrs; typedef std::vector PrintRegionPtrs; class Print { public: PrintConfig config; PrintObjectConfig default_object_config; PrintRegionConfig default_region_config; PrintObjectPtrs objects; PrintRegionPtrs regions; PlaceholderParser placeholder_parser; // TODO: status_cb double total_used_filament, total_extruded_volume; PrintState state; // ordered collections of extrusion paths to build skirt loops and brim ExtrusionEntityCollection skirt, brim; Print(); ~Print(); // methods for handling objects void clear_objects(); PrintObject* get_object(size_t idx); void delete_object(size_t idx); void reload_object(size_t idx); bool reload_model_instances(); // methods for handling regions PrintRegion* get_region(size_t idx); PrintRegion* add_region(); // methods for handling state bool invalidate_state_by_config_options(const std::vector &opt_keys); bool invalidate_step(PrintStep step); bool invalidate_all_steps(); bool step_done(PrintObjectStep step) const; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); bool has_infinite_skirt() const; bool has_skirt() const; void validate() const; BoundingBox bounding_box() const; BoundingBox total_bounding_box() const; double skirt_first_layer_height() const; Flow brim_flow() const; Flow skirt_flow() const; std::set object_extruders() const; std::set support_material_extruders() const; std::set extruders() const; void _simplify_slices(double distance); double max_allowed_layer_height() const; bool has_support_material() const; private: void clear_regions(); void delete_region(size_t idx); PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); }; #define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator) #define FOREACH_REGION(print, region) FOREACH_BASE(PrintRegionPtrs, (print)->regions, region) #define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->objects, object) #define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->layers, layer) #define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->regions, layerm) } #endif Slic3r-1.2.9/xs/src/libslic3r/PrintConfig.cpp000066400000000000000000002177361254023100400207260ustar00rootroot00000000000000#include "PrintConfig.hpp" namespace Slic3r { t_optiondef_map PrintConfigDef::build_def() { t_optiondef_map Options; Options["avoid_crossing_perimeters"].type = coBool; Options["avoid_crossing_perimeters"].label = "Avoid crossing perimeters"; Options["avoid_crossing_perimeters"].tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation."; Options["avoid_crossing_perimeters"].cli = "avoid-crossing-perimeters!"; Options["bed_shape"].type = coPoints; Options["bed_shape"].label = "Bed shape"; Options["bed_temperature"].type = coInt; Options["bed_temperature"].label = "Other layers"; Options["bed_temperature"].tooltip = "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output."; Options["bed_temperature"].cli = "bed-temperature=i"; Options["bed_temperature"].full_label = "Bed temperature"; Options["bed_temperature"].min = 0; Options["bed_temperature"].max = 300; Options["before_layer_gcode"].type = coString; Options["before_layer_gcode"].label = "Before layer change G-code"; Options["before_layer_gcode"].tooltip = "This custom code is inserted at every layer change, right before the Z move. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; Options["before_layer_gcode"].cli = "before-layer-gcode=s"; Options["before_layer_gcode"].multiline = true; Options["before_layer_gcode"].full_width = true; Options["before_layer_gcode"].height = 50; Options["bottom_solid_layers"].type = coInt; Options["bottom_solid_layers"].label = "Bottom"; Options["bottom_solid_layers"].category = "Layers and Perimeters"; Options["bottom_solid_layers"].tooltip = "Number of solid layers to generate on bottom surfaces."; Options["bottom_solid_layers"].cli = "bottom-solid-layers=i"; Options["bottom_solid_layers"].full_label = "Bottom solid layers"; Options["bottom_solid_layers"].min = 0; Options["bridge_acceleration"].type = coFloat; Options["bridge_acceleration"].label = "Bridge"; Options["bridge_acceleration"].tooltip = "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges."; Options["bridge_acceleration"].sidetext = "mm/s²"; Options["bridge_acceleration"].cli = "bridge-acceleration=f"; Options["bridge_acceleration"].min = 0; Options["bridge_fan_speed"].type = coInt; Options["bridge_fan_speed"].label = "Bridges fan speed"; Options["bridge_fan_speed"].tooltip = "This fan speed is enforced during all bridges and overhangs."; Options["bridge_fan_speed"].sidetext = "%"; Options["bridge_fan_speed"].cli = "bridge-fan-speed=i"; Options["bridge_fan_speed"].min = 0; Options["bridge_fan_speed"].max = 100; Options["bridge_flow_ratio"].type = coFloat; Options["bridge_flow_ratio"].label = "Bridge flow ratio"; Options["bridge_flow_ratio"].category = "Advanced"; Options["bridge_flow_ratio"].tooltip = "This factor affects the amount of plastic for bridging. You can decrease it slightly to pull the extrudates and prevent sagging, although default settings are usually good and you should experiment with cooling (use a fan) before tweaking this."; Options["bridge_flow_ratio"].cli = "bridge-flow-ratio=f"; Options["bridge_flow_ratio"].min = 0; Options["bridge_speed"].type = coFloat; Options["bridge_speed"].label = "Bridges"; Options["bridge_speed"].category = "Speed"; Options["bridge_speed"].tooltip = "Speed for printing bridges."; Options["bridge_speed"].sidetext = "mm/s"; Options["bridge_speed"].cli = "bridge-speed=f"; Options["bridge_speed"].aliases.push_back("bridge_feed_rate"); Options["bridge_speed"].min = 0; Options["brim_width"].type = coFloat; Options["brim_width"].label = "Brim width"; Options["brim_width"].tooltip = "Horizontal width of the brim that will be printed around each object on the first layer."; Options["brim_width"].sidetext = "mm"; Options["brim_width"].cli = "brim-width=f"; Options["brim_width"].min = 0; Options["complete_objects"].type = coBool; Options["complete_objects"].label = "Complete individual objects"; Options["complete_objects"].tooltip = "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware."; Options["complete_objects"].cli = "complete-objects!"; Options["cooling"].type = coBool; Options["cooling"].label = "Enable auto cooling"; Options["cooling"].tooltip = "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time."; Options["cooling"].cli = "cooling!"; Options["default_acceleration"].type = coFloat; Options["default_acceleration"].label = "Default"; Options["default_acceleration"].tooltip = "This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all."; Options["default_acceleration"].sidetext = "mm/s²"; Options["default_acceleration"].cli = "default-acceleration=f"; Options["default_acceleration"].min = 0; Options["disable_fan_first_layers"].type = coInt; Options["disable_fan_first_layers"].label = "Disable fan for the first"; Options["disable_fan_first_layers"].tooltip = "You can set this to a positive value to disable fan at all during the first layers, so that it does not make adhesion worse."; Options["disable_fan_first_layers"].sidetext = "layers"; Options["disable_fan_first_layers"].cli = "disable-fan-first-layers=i"; Options["disable_fan_first_layers"].min = 0; Options["disable_fan_first_layers"].max = 1000; Options["dont_support_bridges"].type = coBool; Options["dont_support_bridges"].label = "Don't support bridges"; Options["dont_support_bridges"].category = "Support material"; Options["dont_support_bridges"].tooltip = "Experimental option for preventing support material from being generated under bridged areas."; Options["dont_support_bridges"].cli = "dont-support-bridges!"; Options["duplicate_distance"].type = coFloat; Options["duplicate_distance"].label = "Distance between copies"; Options["duplicate_distance"].tooltip = "Distance used for the auto-arrange feature of the plater."; Options["duplicate_distance"].sidetext = "mm"; Options["duplicate_distance"].cli = "duplicate-distance=f"; Options["duplicate_distance"].aliases.push_back("multiply_distance"); Options["duplicate_distance"].min = 0; Options["end_gcode"].type = coString; Options["end_gcode"].label = "End G-code"; Options["end_gcode"].tooltip = "This end procedure is inserted at the end of the output file. Note that you can use placeholder variables for all Slic3r settings."; Options["end_gcode"].cli = "end-gcode=s"; Options["end_gcode"].multiline = true; Options["end_gcode"].full_width = true; Options["end_gcode"].height = 120; Options["external_fill_pattern"].type = coEnum; Options["external_fill_pattern"].label = "Top/bottom fill pattern"; Options["external_fill_pattern"].category = "Infill"; Options["external_fill_pattern"].tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells."; Options["external_fill_pattern"].cli = "external-fill-pattern|solid-fill-pattern=s"; Options["external_fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["external_fill_pattern"].enum_values.push_back("rectilinear"); Options["external_fill_pattern"].enum_values.push_back("concentric"); Options["external_fill_pattern"].enum_values.push_back("hilbertcurve"); Options["external_fill_pattern"].enum_values.push_back("archimedeanchords"); Options["external_fill_pattern"].enum_values.push_back("octagramspiral"); Options["external_fill_pattern"].enum_labels.push_back("Rectilinear"); Options["external_fill_pattern"].enum_labels.push_back("Concentric"); Options["external_fill_pattern"].enum_labels.push_back("Hilbert Curve"); Options["external_fill_pattern"].enum_labels.push_back("Archimedean Chords"); Options["external_fill_pattern"].enum_labels.push_back("Octagram Spiral"); Options["external_fill_pattern"].aliases.push_back("solid_fill_pattern"); Options["external_perimeter_extrusion_width"].type = coFloatOrPercent; Options["external_perimeter_extrusion_width"].label = "External perimeters"; Options["external_perimeter_extrusion_width"].category = "Extrusion Width"; Options["external_perimeter_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for external perimeters. If left zero, an automatic value will be used that maximizes accuracy of the external visible surfaces. If expressed as percentage (for example 200%) it will be computed over layer height."; Options["external_perimeter_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["external_perimeter_extrusion_width"].cli = "external-perimeter-extrusion-width=s"; Options["external_perimeter_speed"].type = coFloatOrPercent; Options["external_perimeter_speed"].label = "External perimeters"; Options["external_perimeter_speed"].category = "Speed"; Options["external_perimeter_speed"].tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; Options["external_perimeter_speed"].sidetext = "mm/s or %"; Options["external_perimeter_speed"].cli = "external-perimeter-speed=s"; Options["external_perimeter_speed"].ratio_over = "perimeter_speed"; Options["external_perimeter_speed"].min = 0; Options["external_perimeters_first"].type = coBool; Options["external_perimeters_first"].label = "External perimeters first"; Options["external_perimeters_first"].category = "Layers and Perimeters"; Options["external_perimeters_first"].tooltip = "Print contour perimeters from the outermost one to the innermost one instead of the default inverse order."; Options["external_perimeters_first"].cli = "external-perimeters-first!"; Options["extra_perimeters"].type = coBool; Options["extra_perimeters"].label = "Extra perimeters if needed"; Options["extra_perimeters"].category = "Layers and Perimeters"; Options["extra_perimeters"].tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls."; Options["extra_perimeters"].cli = "extra-perimeters!"; Options["extruder"].type = coInt; Options["extruder"].gui_type = "i_enum_open"; Options["extruder"].label = "Extruder"; Options["extruder"].category = "Extruders"; Options["extruder"].tooltip = "The extruder to use (unless more specific extruder settings are specified)."; Options["extruder"].cli = "extruder=i"; Options["extruder"].min = 0; // 0 = inherit defaults Options["extruder"].enum_labels.push_back("default"); // override label for item 0 Options["extruder"].enum_labels.push_back("1"); Options["extruder"].enum_labels.push_back("2"); Options["extruder"].enum_labels.push_back("3"); Options["extruder"].enum_labels.push_back("4"); Options["extruder_clearance_height"].type = coFloat; Options["extruder_clearance_height"].label = "Height"; Options["extruder_clearance_height"].tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. In other words, this is the height of the clearance cylinder around your extruder, and it represents the maximum depth the extruder can peek before colliding with other printed objects."; Options["extruder_clearance_height"].sidetext = "mm"; Options["extruder_clearance_height"].cli = "extruder-clearance-height=f"; Options["extruder_clearance_height"].min = 0; Options["extruder_clearance_radius"].type = coFloat; Options["extruder_clearance_radius"].label = "Radius"; Options["extruder_clearance_radius"].tooltip = "Set this to the clearance radius around your extruder. If the extruder is not centered, choose the largest value for safety. This setting is used to check for collisions and to display the graphical preview in the plater."; Options["extruder_clearance_radius"].sidetext = "mm"; Options["extruder_clearance_radius"].cli = "extruder-clearance-radius=f"; Options["extruder_clearance_radius"].min = 0; Options["extruder_offset"].type = coPoints; Options["extruder_offset"].label = "Extruder offset"; Options["extruder_offset"].tooltip = "If your firmware doesn't handle the extruder displacement you need the G-code to take it into account. This option lets you specify the displacement of each extruder with respect to the first one. It expects positive coordinates (they will be subtracted from the XY coordinate)."; Options["extruder_offset"].sidetext = "mm"; Options["extruder_offset"].cli = "extruder-offset=s@"; Options["extrusion_axis"].type = coString; Options["extrusion_axis"].label = "Extrusion axis"; Options["extrusion_axis"].tooltip = "Use this option to set the axis letter associated to your printer's extruder (usually E but some printers use A)."; Options["extrusion_axis"].cli = "extrusion-axis=s"; Options["extrusion_multiplier"].type = coFloats; Options["extrusion_multiplier"].label = "Extrusion multiplier"; Options["extrusion_multiplier"].tooltip = "This factor changes the amount of flow proportionally. You may need to tweak this setting to get nice surface finish and correct single wall widths. Usual values are between 0.9 and 1.1. If you think you need to change this more, check filament diameter and your firmware E steps."; Options["extrusion_multiplier"].cli = "extrusion-multiplier=f@"; Options["extrusion_width"].type = coFloatOrPercent; Options["extrusion_width"].label = "Default extrusion width"; Options["extrusion_width"].category = "Extrusion Width"; Options["extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width. If left to zero, Slic3r calculates a width automatically. If expressed as percentage (for example: 230%) it will be computed over layer height."; Options["extrusion_width"].sidetext = "mm or % (leave 0 for auto)"; Options["extrusion_width"].cli = "extrusion-width=s"; Options["fan_always_on"].type = coBool; Options["fan_always_on"].label = "Keep fan always on"; Options["fan_always_on"].tooltip = "If this is enabled, fan will never be disabled and will be kept running at least at its minimum speed. Useful for PLA, harmful for ABS."; Options["fan_always_on"].cli = "fan-always-on!"; Options["fan_below_layer_time"].type = coInt; Options["fan_below_layer_time"].label = "Enable fan if layer print time is below"; Options["fan_below_layer_time"].tooltip = "If layer print time is estimated below this number of seconds, fan will be enabled and its speed will be calculated by interpolating the minimum and maximum speeds."; Options["fan_below_layer_time"].sidetext = "approximate seconds"; Options["fan_below_layer_time"].cli = "fan-below-layer-time=i"; Options["fan_below_layer_time"].width = 60; Options["fan_below_layer_time"].min = 0; Options["fan_below_layer_time"].max = 1000; Options["filament_colour"].type = coStrings; Options["filament_colour"].label = "Color"; Options["filament_colour"].tooltip = "This is only used in the Slic3r interface as a visual help."; Options["filament_colour"].cli = "filament-color=s@"; Options["filament_colour"].gui_type = "color"; Options["filament_diameter"].type = coFloats; Options["filament_diameter"].label = "Diameter"; Options["filament_diameter"].tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."; Options["filament_diameter"].sidetext = "mm"; Options["filament_diameter"].cli = "filament-diameter=f@"; Options["filament_diameter"].min = 0; Options["fill_angle"].type = coInt; Options["fill_angle"].label = "Fill angle"; Options["fill_angle"].category = "Infill"; Options["fill_angle"].tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them."; Options["fill_angle"].sidetext = "°"; Options["fill_angle"].cli = "fill-angle=i"; Options["fill_angle"].min = 0; Options["fill_angle"].max = 359; Options["fill_density"].type = coPercent; Options["fill_density"].gui_type = "f_enum_open"; Options["fill_density"].gui_flags = "show_value"; Options["fill_density"].label = "Fill density"; Options["fill_density"].category = "Infill"; Options["fill_density"].tooltip = "Density of internal infill, expressed in the range 0% - 100%."; Options["fill_density"].sidetext = "%"; Options["fill_density"].cli = "fill-density=s"; Options["fill_density"].min = 0; Options["fill_density"].max = 100; Options["fill_density"].enum_values.push_back("0"); Options["fill_density"].enum_values.push_back("5"); Options["fill_density"].enum_values.push_back("10"); Options["fill_density"].enum_values.push_back("15"); Options["fill_density"].enum_values.push_back("20"); Options["fill_density"].enum_values.push_back("25"); Options["fill_density"].enum_values.push_back("30"); Options["fill_density"].enum_values.push_back("40"); Options["fill_density"].enum_values.push_back("50"); Options["fill_density"].enum_values.push_back("60"); Options["fill_density"].enum_values.push_back("70"); Options["fill_density"].enum_values.push_back("80"); Options["fill_density"].enum_values.push_back("90"); Options["fill_density"].enum_values.push_back("100"); Options["fill_density"].enum_labels.push_back("0%"); Options["fill_density"].enum_labels.push_back("5%"); Options["fill_density"].enum_labels.push_back("10%"); Options["fill_density"].enum_labels.push_back("15%"); Options["fill_density"].enum_labels.push_back("20%"); Options["fill_density"].enum_labels.push_back("25%"); Options["fill_density"].enum_labels.push_back("30%"); Options["fill_density"].enum_labels.push_back("40%"); Options["fill_density"].enum_labels.push_back("50%"); Options["fill_density"].enum_labels.push_back("60%"); Options["fill_density"].enum_labels.push_back("70%"); Options["fill_density"].enum_labels.push_back("80%"); Options["fill_density"].enum_labels.push_back("90%"); Options["fill_density"].enum_labels.push_back("100%"); Options["fill_pattern"].type = coEnum; Options["fill_pattern"].label = "Fill pattern"; Options["fill_pattern"].category = "Infill"; Options["fill_pattern"].tooltip = "Fill pattern for general low-density infill."; Options["fill_pattern"].cli = "fill-pattern=s"; Options["fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["fill_pattern"].enum_values.push_back("rectilinear"); Options["fill_pattern"].enum_values.push_back("line"); Options["fill_pattern"].enum_values.push_back("concentric"); Options["fill_pattern"].enum_values.push_back("honeycomb"); Options["fill_pattern"].enum_values.push_back("3dhoneycomb"); Options["fill_pattern"].enum_values.push_back("hilbertcurve"); Options["fill_pattern"].enum_values.push_back("archimedeanchords"); Options["fill_pattern"].enum_values.push_back("octagramspiral"); Options["fill_pattern"].enum_labels.push_back("Rectilinear"); Options["fill_pattern"].enum_labels.push_back("Line"); Options["fill_pattern"].enum_labels.push_back("Concentric"); Options["fill_pattern"].enum_labels.push_back("Honeycomb"); Options["fill_pattern"].enum_labels.push_back("3D Honeycomb"); Options["fill_pattern"].enum_labels.push_back("Hilbert Curve"); Options["fill_pattern"].enum_labels.push_back("Archimedean Chords"); Options["fill_pattern"].enum_labels.push_back("Octagram Spiral"); Options["first_layer_acceleration"].type = coFloat; Options["first_layer_acceleration"].label = "First layer"; Options["first_layer_acceleration"].tooltip = "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer."; Options["first_layer_acceleration"].sidetext = "mm/s²"; Options["first_layer_acceleration"].cli = "first-layer-acceleration=f"; Options["first_layer_acceleration"].min = 0; Options["first_layer_bed_temperature"].type = coInt; Options["first_layer_bed_temperature"].label = "First layer"; Options["first_layer_bed_temperature"].tooltip = "Heated build plate temperature for the first layer. Set this to zero to disable bed temperature control commands in the output."; Options["first_layer_bed_temperature"].cli = "first-layer-bed-temperature=i"; Options["first_layer_bed_temperature"].max = 0; Options["first_layer_bed_temperature"].max = 300; Options["first_layer_extrusion_width"].type = coFloatOrPercent; Options["first_layer_extrusion_width"].label = "First layer"; Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) it will be computed over first layer height. If set to zero, it will use the Default Extrusion Width."; Options["first_layer_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["first_layer_extrusion_width"].cli = "first-layer-extrusion-width=s"; Options["first_layer_extrusion_width"].ratio_over = "first_layer_height"; Options["first_layer_height"].type = coFloatOrPercent; Options["first_layer_height"].label = "First layer height"; Options["first_layer_height"].category = "Layers and Perimeters"; Options["first_layer_height"].tooltip = "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height."; Options["first_layer_height"].sidetext = "mm or %"; Options["first_layer_height"].cli = "first-layer-height=s"; Options["first_layer_height"].ratio_over = "layer_height"; Options["first_layer_speed"].type = coFloatOrPercent; Options["first_layer_speed"].label = "First layer speed"; Options["first_layer_speed"].tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds."; Options["first_layer_speed"].sidetext = "mm/s or %"; Options["first_layer_speed"].cli = "first-layer-speed=s"; Options["first_layer_speed"].min = 0; Options["first_layer_temperature"].type = coInts; Options["first_layer_temperature"].label = "First layer"; Options["first_layer_temperature"].tooltip = "Extruder temperature for first layer. If you want to control temperature manually during print, set this to zero to disable temperature control commands in the output file."; Options["first_layer_temperature"].cli = "first-layer-temperature=i@"; Options["first_layer_temperature"].min = 0; Options["first_layer_temperature"].max = 400; Options["gap_fill_speed"].type = coFloat; Options["gap_fill_speed"].label = "Gap fill"; Options["gap_fill_speed"].category = "Speed"; Options["gap_fill_speed"].tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; Options["gap_fill_speed"].sidetext = "mm/s"; Options["gap_fill_speed"].cli = "gap-fill-speed=f"; Options["gap_fill_speed"].min = 0; Options["gcode_arcs"].type = coBool; Options["gcode_arcs"].label = "Use native G-code arcs"; Options["gcode_arcs"].tooltip = "This experimental feature tries to detect arcs from segments and generates G2/G3 arc commands instead of multiple straight G1 commands."; Options["gcode_arcs"].cli = "gcode-arcs!"; Options["gcode_comments"].type = coBool; Options["gcode_comments"].label = "Verbose G-code"; Options["gcode_comments"].tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down."; Options["gcode_comments"].cli = "gcode-comments!"; Options["gcode_flavor"].type = coEnum; Options["gcode_flavor"].label = "G-code flavor"; Options["gcode_flavor"].tooltip = "Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer's firmware to get a compatible output. The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all."; Options["gcode_flavor"].cli = "gcode-flavor=s"; Options["gcode_flavor"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["gcode_flavor"].enum_values.push_back("reprap"); Options["gcode_flavor"].enum_values.push_back("teacup"); Options["gcode_flavor"].enum_values.push_back("makerware"); Options["gcode_flavor"].enum_values.push_back("sailfish"); Options["gcode_flavor"].enum_values.push_back("mach3"); Options["gcode_flavor"].enum_values.push_back("machinekit"); Options["gcode_flavor"].enum_values.push_back("no-extrusion"); Options["gcode_flavor"].enum_labels.push_back("RepRap (Marlin/Sprinter/Repetier)"); Options["gcode_flavor"].enum_labels.push_back("Teacup"); Options["gcode_flavor"].enum_labels.push_back("MakerWare (MakerBot)"); Options["gcode_flavor"].enum_labels.push_back("Sailfish (MakerBot)"); Options["gcode_flavor"].enum_labels.push_back("Mach3/LinuxCNC"); Options["gcode_flavor"].enum_labels.push_back("Machinekit"); Options["gcode_flavor"].enum_labels.push_back("No extrusion"); Options["infill_acceleration"].type = coFloat; Options["infill_acceleration"].label = "Infill"; Options["infill_acceleration"].tooltip = "This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill."; Options["infill_acceleration"].sidetext = "mm/s²"; Options["infill_acceleration"].cli = "infill-acceleration=f"; Options["infill_acceleration"].min = 0; Options["infill_every_layers"].type = coInt; Options["infill_every_layers"].label = "Combine infill every"; Options["infill_every_layers"].category = "Infill"; Options["infill_every_layers"].tooltip = "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy."; Options["infill_every_layers"].sidetext = "layers"; Options["infill_every_layers"].cli = "infill-every-layers=i"; Options["infill_every_layers"].full_label = "Combine infill every n layers"; Options["infill_every_layers"].min = 1; Options["infill_extruder"].type = coInt; Options["infill_extruder"].label = "Infill extruder"; Options["infill_extruder"].category = "Extruders"; Options["infill_extruder"].tooltip = "The extruder to use when printing infill."; Options["infill_extruder"].cli = "infill-extruder=i"; Options["infill_extruder"].min = 1; Options["infill_extrusion_width"].type = coFloatOrPercent; Options["infill_extrusion_width"].label = "Infill"; Options["infill_extrusion_width"].category = "Extrusion Width"; Options["infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) it will be computed over layer height."; Options["infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["infill_extrusion_width"].cli = "infill-extrusion-width=s"; Options["infill_first"].type = coBool; Options["infill_first"].label = "Infill before perimeters"; Options["infill_first"].tooltip = "This option will switch the print order of perimeters and infill, making the latter first."; Options["infill_first"].cli = "infill-first!"; Options["infill_only_where_needed"].type = coBool; Options["infill_only_where_needed"].label = "Only infill where needed"; Options["infill_only_where_needed"].category = "Infill"; Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved."; Options["infill_only_where_needed"].cli = "infill-only-where-needed!"; Options["infill_overlap"].type = coFloatOrPercent; Options["infill_overlap"].label = "Infill/perimeters overlap"; Options["infill_overlap"].category = "Advanced"; Options["infill_overlap"].tooltip = "This setting applies an additional overlap between infill and perimeters for better bonding. Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed as percentage (example: 15%) it is calculated over perimeter extrusion width."; Options["infill_overlap"].sidetext = "mm or %"; Options["infill_overlap"].cli = "infill-overlap=s"; Options["infill_overlap"].ratio_over = "perimeter_extrusion_width"; Options["infill_speed"].type = coFloat; Options["infill_speed"].label = "Infill"; Options["infill_speed"].category = "Speed"; Options["infill_speed"].tooltip = "Speed for printing the internal fill. Set to zero for auto."; Options["infill_speed"].sidetext = "mm/s"; Options["infill_speed"].cli = "infill-speed=f"; Options["infill_speed"].aliases.push_back("print_feed_rate"); Options["infill_speed"].aliases.push_back("infill_feed_rate"); Options["infill_speed"].min = 0; Options["interface_shells"].type = coBool; Options["interface_shells"].label = "Interface shells"; Options["interface_shells"].tooltip = "Force the generation of solid shells between adjacent materials/volumes. Useful for multi-extruder prints with translucent materials or manual soluble support material."; Options["interface_shells"].cli = "interface-shells!"; Options["interface_shells"].category = "Layers and Perimeters"; Options["layer_gcode"].type = coString; Options["layer_gcode"].label = "After layer change G-code"; Options["layer_gcode"].tooltip = "This custom code is inserted at every layer change, right after the Z move and before the extruder moves to the first layer point. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; Options["layer_gcode"].cli = "after-layer-gcode|layer-gcode=s"; Options["layer_gcode"].multiline = true; Options["layer_gcode"].full_width = true; Options["layer_gcode"].height = 50; Options["layer_height"].type = coFloat; Options["layer_height"].label = "Layer height"; Options["layer_height"].category = "Layers and Perimeters"; Options["layer_height"].tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print."; Options["layer_height"].sidetext = "mm"; Options["layer_height"].cli = "layer-height=f"; Options["layer_height"].min = 0; Options["max_fan_speed"].type = coInt; Options["max_fan_speed"].label = "Max"; Options["max_fan_speed"].tooltip = "This setting represents the maximum speed of your fan."; Options["max_fan_speed"].sidetext = "%"; Options["max_fan_speed"].cli = "max-fan-speed=i"; Options["max_fan_speed"].min = 0; Options["max_fan_speed"].max = 100; Options["min_fan_speed"].type = coInt; Options["min_fan_speed"].label = "Min"; Options["min_fan_speed"].tooltip = "This setting represents the minimum PWM your fan needs to work."; Options["min_fan_speed"].sidetext = "%"; Options["min_fan_speed"].cli = "min-fan-speed=i"; Options["min_fan_speed"].min = 0; Options["min_fan_speed"].max = 100; Options["min_print_speed"].type = coInt; Options["min_print_speed"].label = "Min print speed"; Options["min_print_speed"].tooltip = "Slic3r will not scale speed down below this speed."; Options["min_print_speed"].sidetext = "mm/s"; Options["min_print_speed"].cli = "min-print-speed=f"; Options["min_print_speed"].min = 0; Options["max_print_speed"].type = coFloat; Options["max_print_speed"].label = "Max print speed"; Options["max_print_speed"].tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow."; Options["max_print_speed"].sidetext = "mm/s"; Options["max_print_speed"].cli = "max-print-speed=f"; Options["max_print_speed"].min = 1; Options["max_volumetric_speed"].type = coFloat; Options["max_volumetric_speed"].label = "Max volumetric speed"; Options["max_volumetric_speed"].tooltip = "This experimental setting is used to set the maximum volumetric speed your extruder supports."; Options["max_volumetric_speed"].sidetext = "mm³/s"; Options["max_volumetric_speed"].cli = "max-volumetric-speed=f"; Options["max_volumetric_speed"].min = 0; Options["min_skirt_length"].type = coFloat; Options["min_skirt_length"].label = "Minimum extrusion length"; Options["min_skirt_length"].tooltip = "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder."; Options["min_skirt_length"].sidetext = "mm"; Options["min_skirt_length"].cli = "min-skirt-length=f"; Options["min_skirt_length"].min = 0; Options["notes"].type = coString; Options["notes"].label = "Configuration notes"; Options["notes"].tooltip = "You can put here your personal notes. This text will be added to the G-code header comments."; Options["notes"].cli = "notes=s"; Options["notes"].multiline = true; Options["notes"].full_width = true; Options["notes"].height = 130; Options["nozzle_diameter"].type = coFloats; Options["nozzle_diameter"].label = "Nozzle diameter"; Options["nozzle_diameter"].tooltip = "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)"; Options["nozzle_diameter"].sidetext = "mm"; Options["nozzle_diameter"].cli = "nozzle-diameter=f@"; Options["octoprint_apikey"].type = coString; Options["octoprint_apikey"].label = "API Key"; Options["octoprint_apikey"].tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication."; Options["octoprint_apikey"].cli = "octoprint-apikey=s"; Options["octoprint_host"].type = coString; Options["octoprint_host"].label = "Host or IP"; Options["octoprint_host"].tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance."; Options["octoprint_host"].cli = "octoprint-host=s"; Options["only_retract_when_crossing_perimeters"].type = coBool; Options["only_retract_when_crossing_perimeters"].label = "Only retract when crossing perimeters"; Options["only_retract_when_crossing_perimeters"].tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible)."; Options["only_retract_when_crossing_perimeters"].cli = "only-retract-when-crossing-perimeters!"; Options["ooze_prevention"].type = coBool; Options["ooze_prevention"].label = "Enable"; Options["ooze_prevention"].tooltip = "This option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures."; Options["ooze_prevention"].cli = "ooze-prevention!"; Options["output_filename_format"].type = coString; Options["output_filename_format"].label = "Output filename format"; Options["output_filename_format"].tooltip = "You can use all configuration options as variables inside this template. For example: [layer_height], [fill_density] etc. You can also use [timestamp], [year], [month], [day], [hour], [minute], [second], [version], [input_filename], [input_filename_base]."; Options["output_filename_format"].cli = "output-filename-format=s"; Options["output_filename_format"].full_width = true; Options["overhangs"].type = coBool; Options["overhangs"].label = "Detect bridging perimeters"; Options["overhangs"].category = "Layers and Perimeters"; Options["overhangs"].tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan."; Options["overhangs"].cli = "overhangs!"; Options["perimeter_acceleration"].type = coFloat; Options["perimeter_acceleration"].label = "Perimeters"; Options["perimeter_acceleration"].tooltip = "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters."; Options["perimeter_acceleration"].sidetext = "mm/s²"; Options["perimeter_acceleration"].cli = "perimeter-acceleration=f"; Options["perimeter_extruder"].type = coInt; Options["perimeter_extruder"].label = "Perimeter extruder"; Options["perimeter_extruder"].category = "Extruders"; Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters and brim. First extruder is 1."; Options["perimeter_extruder"].cli = "perimeter-extruder=i"; Options["perimeter_extruder"].aliases.push_back("perimeters_extruder"); Options["perimeter_extruder"].min = 1; Options["perimeter_extrusion_width"].type = coFloatOrPercent; Options["perimeter_extrusion_width"].label = "Perimeters"; Options["perimeter_extrusion_width"].category = "Extrusion Width"; Options["perimeter_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If expressed as percentage (for example 200%) it will be computed over layer height."; Options["perimeter_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["perimeter_extrusion_width"].cli = "perimeter-extrusion-width=s"; Options["perimeter_extrusion_width"].aliases.push_back("perimeters_extrusion_width"); Options["perimeter_speed"].type = coFloat; Options["perimeter_speed"].label = "Perimeters"; Options["perimeter_speed"].category = "Speed"; Options["perimeter_speed"].tooltip = "Speed for perimeters (contours, aka vertical shells). Set to zero for auto."; Options["perimeter_speed"].sidetext = "mm/s"; Options["perimeter_speed"].cli = "perimeter-speed=f"; Options["perimeter_speed"].aliases.push_back("perimeter_feed_rate"); Options["perimeter_speed"].min = 0; Options["perimeters"].type = coInt; Options["perimeters"].label = "Perimeters"; Options["perimeters"].category = "Layers and Perimeters"; Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled."; Options["perimeters"].sidetext = "(minimum)"; Options["perimeters"].cli = "perimeters=i"; Options["perimeters"].aliases.push_back("perimeter_offsets"); Options["perimeters"].min = 0; Options["post_process"].type = coStrings; Options["post_process"].label = "Post-processing scripts"; Options["post_process"].tooltip = "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables."; Options["post_process"].cli = "post-process=s@"; Options["post_process"].gui_flags = "serialized"; Options["post_process"].multiline = true; Options["post_process"].full_width = true; Options["post_process"].height = 60; Options["pressure_advance"].type = coFloat; Options["pressure_advance"].label = "Pressure advance"; Options["pressure_advance"].tooltip = "When set to a non-zero value, this experimental option enables pressure regulation. It's the K constant for the advance algorithm that pushes more or less filament upon speed changes. It's useful for Bowden-tube extruders. Reasonable values are in range 0-10."; Options["pressure_advance"].cli = "pressure-advance=f"; Options["pressure_advance"].min = 0; Options["raft_layers"].type = coInt; Options["raft_layers"].label = "Raft layers"; Options["raft_layers"].category = "Support material"; Options["raft_layers"].tooltip = "The object will be raised by this number of layers, and support material will be generated under it."; Options["raft_layers"].sidetext = "layers"; Options["raft_layers"].cli = "raft-layers=i"; Options["raft_layers"].min = 0; Options["resolution"].type = coFloat; Options["resolution"].label = "Resolution"; Options["resolution"].tooltip = "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input."; Options["resolution"].sidetext = "mm"; Options["resolution"].cli = "resolution=f"; Options["resolution"].min = 0; Options["retract_before_travel"].type = coFloats; Options["retract_before_travel"].label = "Minimum travel after retraction"; Options["retract_before_travel"].tooltip = "Retraction is not triggered when travel moves are shorter than this length."; Options["retract_before_travel"].sidetext = "mm"; Options["retract_before_travel"].cli = "retract-before-travel=f@"; Options["retract_layer_change"].type = coBools; Options["retract_layer_change"].label = "Retract on layer change"; Options["retract_layer_change"].tooltip = "This flag enforces a retraction whenever a Z move is done."; Options["retract_layer_change"].cli = "retract-layer-change!"; Options["retract_length"].type = coFloats; Options["retract_length"].label = "Length"; Options["retract_length"].full_label = "Retraction Length"; Options["retract_length"].tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; Options["retract_length"].sidetext = "mm (zero to disable)"; Options["retract_length"].cli = "retract-length=f@"; Options["retract_length_toolchange"].type = coFloats; Options["retract_length_toolchange"].label = "Length"; Options["retract_length_toolchange"].full_label = "Retraction Length (Toolchange)"; Options["retract_length_toolchange"].tooltip = "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; Options["retract_length_toolchange"].sidetext = "mm (zero to disable)"; Options["retract_length_toolchange"].cli = "retract-length-toolchange=f@"; Options["retract_lift"].type = coFloats; Options["retract_lift"].label = "Lift Z"; Options["retract_lift"].tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered."; Options["retract_lift"].sidetext = "mm"; Options["retract_lift"].cli = "retract-lift=f@"; Options["retract_restart_extra"].type = coFloats; Options["retract_restart_extra"].label = "Extra length on restart"; Options["retract_restart_extra"].tooltip = "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed."; Options["retract_restart_extra"].sidetext = "mm"; Options["retract_restart_extra"].cli = "retract-restart-extra=f@"; Options["retract_restart_extra_toolchange"].type = coFloats; Options["retract_restart_extra_toolchange"].label = "Extra length on restart"; Options["retract_restart_extra_toolchange"].tooltip = "When the retraction is compensated after changing tool, the extruder will push this additional amount of filament."; Options["retract_restart_extra_toolchange"].sidetext = "mm"; Options["retract_restart_extra_toolchange"].cli = "retract-restart-extra-toolchange=f@"; Options["retract_speed"].type = coInts; Options["retract_speed"].label = "Speed"; Options["retract_speed"].full_label = "Retraction Speed"; Options["retract_speed"].tooltip = "The speed for retractions (it only applies to the extruder motor)."; Options["retract_speed"].sidetext = "mm/s"; Options["retract_speed"].cli = "retract-speed=f@"; Options["seam_position"].type = coEnum; Options["seam_position"].label = "Seam position"; Options["seam_position"].category = "Layers and perimeters"; Options["seam_position"].tooltip = "Position of perimeters starting points."; Options["seam_position"].cli = "seam-position=s"; Options["seam_position"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["seam_position"].enum_values.push_back("random"); Options["seam_position"].enum_values.push_back("nearest"); Options["seam_position"].enum_values.push_back("aligned"); Options["seam_position"].enum_labels.push_back("Random"); Options["seam_position"].enum_labels.push_back("Nearest"); Options["seam_position"].enum_labels.push_back("Aligned"); Options["skirt_distance"].type = coFloat; Options["skirt_distance"].label = "Distance from object"; Options["skirt_distance"].tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion."; Options["skirt_distance"].sidetext = "mm"; Options["skirt_distance"].cli = "skirt-distance=f"; Options["skirt_distance"].min = 0; Options["skirt_height"].type = coInt; Options["skirt_height"].label = "Skirt height"; Options["skirt_height"].tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt as a shield against drafts."; Options["skirt_height"].sidetext = "layers"; Options["skirt_height"].cli = "skirt-height=i"; Options["skirts"].type = coInt; Options["skirts"].label = "Loops (minimum)"; Options["skirts"].full_label = "Skirt Loops"; Options["skirts"].tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely."; Options["skirts"].cli = "skirts=i"; Options["skirts"].min = 0; Options["slowdown_below_layer_time"].type = coInt; Options["slowdown_below_layer_time"].label = "Slow down if layer print time is below"; Options["slowdown_below_layer_time"].tooltip = "If layer print time is estimated below this number of seconds, print moves speed will be scaled down to extend duration to this value."; Options["slowdown_below_layer_time"].sidetext = "approximate seconds"; Options["slowdown_below_layer_time"].cli = "slowdown-below-layer-time=i"; Options["slowdown_below_layer_time"].width = 60; Options["slowdown_below_layer_time"].min = 0; Options["slowdown_below_layer_time"].max = 1000; Options["small_perimeter_speed"].type = coFloatOrPercent; Options["small_perimeter_speed"].label = "Small perimeters"; Options["small_perimeter_speed"].category = "Speed"; Options["small_perimeter_speed"].tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; Options["small_perimeter_speed"].sidetext = "mm/s or %"; Options["small_perimeter_speed"].cli = "small-perimeter-speed=s"; Options["small_perimeter_speed"].ratio_over = "perimeter_speed"; Options["small_perimeter_speed"].min = 0; Options["solid_infill_below_area"].type = coFloat; Options["solid_infill_below_area"].label = "Solid infill threshold area"; Options["solid_infill_below_area"].category = "Infill"; Options["solid_infill_below_area"].tooltip = "Force solid infill for regions having a smaller area than the specified threshold."; Options["solid_infill_below_area"].sidetext = "mm²"; Options["solid_infill_below_area"].cli = "solid-infill-below-area=f"; Options["solid_infill_below_area"].min = 0; Options["solid_infill_extruder"].type = coInt; Options["solid_infill_extruder"].label = "Solid infill extruder"; Options["solid_infill_extruder"].category = "Extruders"; Options["solid_infill_extruder"].tooltip = "The extruder to use when printing solid infill."; Options["solid_infill_extruder"].cli = "solid-infill-extruder=i"; Options["solid_infill_extruder"].min = 1; Options["solid_infill_every_layers"].type = coInt; Options["solid_infill_every_layers"].label = "Solid infill every"; Options["solid_infill_every_layers"].category = "Infill"; Options["solid_infill_every_layers"].tooltip = "This feature allows to force a solid layer every given number of layers. Zero to disable. You can set this to any value (for example 9999); Slic3r will automatically choose the maximum possible number of layers to combine according to nozzle diameter and layer height."; Options["solid_infill_every_layers"].sidetext = "layers"; Options["solid_infill_every_layers"].cli = "solid-infill-every-layers=i"; Options["solid_infill_every_layers"].min = 0; Options["solid_infill_extrusion_width"].type = coFloatOrPercent; Options["solid_infill_extrusion_width"].label = "Solid infill"; Options["solid_infill_extrusion_width"].category = "Extrusion Width"; Options["solid_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If expressed as percentage (for example 90%) it will be computed over layer height."; Options["solid_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["solid_infill_extrusion_width"].cli = "solid-infill-extrusion-width=s"; Options["solid_infill_speed"].type = coFloatOrPercent; Options["solid_infill_speed"].label = "Solid infill"; Options["solid_infill_speed"].category = "Speed"; Options["solid_infill_speed"].tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto."; Options["solid_infill_speed"].sidetext = "mm/s or %"; Options["solid_infill_speed"].cli = "solid-infill-speed=s"; Options["solid_infill_speed"].ratio_over = "infill_speed"; Options["solid_infill_speed"].aliases.push_back("solid_infill_feed_rate"); Options["solid_infill_speed"].min = 0; Options["solid_layers"].type = coInt; Options["solid_layers"].label = "Solid layers"; Options["solid_layers"].tooltip = "Number of solid layers to generate on top and bottom surfaces."; Options["solid_layers"].cli = "solid-layers=i"; Options["solid_layers"].shortcut.push_back("top_solid_layers"); Options["solid_layers"].shortcut.push_back("bottom_solid_layers"); Options["solid_layers"].min = 0; Options["spiral_vase"].type = coBool; Options["spiral_vase"].label = "Spiral vase"; Options["spiral_vase"].tooltip = "This feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. This option requires a single perimeter, no infill, no top solid layers and no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won't work when printing more than an object."; Options["spiral_vase"].cli = "spiral-vase!"; Options["standby_temperature_delta"].type = coInt; Options["standby_temperature_delta"].label = "Temperature variation"; Options["standby_temperature_delta"].tooltip = "Temperature difference to be applied when an extruder is not active."; Options["standby_temperature_delta"].sidetext = "∆°C"; Options["standby_temperature_delta"].cli = "standby-temperature-delta=i"; Options["standby_temperature_delta"].min = -400; Options["standby_temperature_delta"].max = 400; Options["start_gcode"].type = coString; Options["start_gcode"].label = "Start G-code"; Options["start_gcode"].tooltip = "This start procedure is inserted at the beginning, after bed has reached the target temperature and extruder just started heating, and before extruder has finished heating. If Slic3r detects M104 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want."; Options["start_gcode"].cli = "start-gcode=s"; Options["start_gcode"].multiline = true; Options["start_gcode"].full_width = true; Options["start_gcode"].height = 120; Options["support_material"].type = coBool; Options["support_material"].label = "Generate support material"; Options["support_material"].category = "Support material"; Options["support_material"].tooltip = "Enable support material generation."; Options["support_material"].cli = "support-material!"; Options["support_material_angle"].type = coInt; Options["support_material_angle"].label = "Pattern angle"; Options["support_material_angle"].category = "Support material"; Options["support_material_angle"].tooltip = "Use this setting to rotate the support material pattern on the horizontal plane."; Options["support_material_angle"].sidetext = "°"; Options["support_material_angle"].cli = "support-material-angle=i"; Options["support_material_angle"].min = 0; Options["support_material_angle"].max = 359; Options["support_material_contact_distance"].type = coFloat; Options["support_material_contact_distance"].gui_type = "f_enum_open"; Options["support_material_contact_distance"].label = "Contact Z distance"; Options["support_material_contact_distance"].category = "Support material"; Options["support_material_contact_distance"].tooltip = "The vertical distance between object and support material interface. Setting this to 0 will also prevent Slic3r from using bridge flow and speed for the first object layer."; Options["support_material_contact_distance"].sidetext = "mm"; Options["support_material_contact_distance"].cli = "support-material-contact-distance=f"; Options["support_material_contact_distance"].min = 0; Options["support_material_contact_distance"].enum_values.push_back("0"); Options["support_material_contact_distance"].enum_values.push_back("0.2"); Options["support_material_contact_distance"].enum_labels.push_back("0 (soluble)"); Options["support_material_contact_distance"].enum_labels.push_back("0.2 (detachable)"); Options["support_material_enforce_layers"].type = coInt; Options["support_material_enforce_layers"].label = "Enforce support for the first"; Options["support_material_enforce_layers"].category = "Support material"; Options["support_material_enforce_layers"].tooltip = "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate."; Options["support_material_enforce_layers"].sidetext = "layers"; Options["support_material_enforce_layers"].cli = "support-material-enforce-layers=f"; Options["support_material_enforce_layers"].full_label = "Enforce support for the first n layers"; Options["support_material_enforce_layers"].min = 0; Options["support_material_extruder"].type = coInt; Options["support_material_extruder"].label = "Support material/raft/skirt extruder"; Options["support_material_extruder"].category = "Extruders"; Options["support_material_extruder"].tooltip = "The extruder to use when printing support material, raft and skirt."; Options["support_material_extruder"].cli = "support-material-extruder=i"; Options["support_material_extruder"].min = 1; Options["support_material_extrusion_width"].type = coFloatOrPercent; Options["support_material_extrusion_width"].label = "Support material"; Options["support_material_extrusion_width"].category = "Extrusion Width"; Options["support_material_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) it will be computed over layer height."; Options["support_material_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["support_material_extrusion_width"].cli = "support-material-extrusion-width=s"; Options["support_material_interface_extruder"].type = coInt; Options["support_material_interface_extruder"].label = "Support material/raft interface extruder"; Options["support_material_interface_extruder"].category = "Extruders"; Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too."; Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i"; Options["support_material_interface_extruder"].min = 1; Options["support_material_interface_layers"].type = coInt; Options["support_material_interface_layers"].label = "Interface layers"; Options["support_material_interface_layers"].category = "Support material"; Options["support_material_interface_layers"].tooltip = "Number of interface layers to insert between the object(s) and support material."; Options["support_material_interface_layers"].sidetext = "layers"; Options["support_material_interface_layers"].cli = "support-material-interface-layers=i"; Options["support_material_interface_layers"].min = 0; Options["support_material_interface_spacing"].type = coFloat; Options["support_material_interface_spacing"].label = "Interface pattern spacing"; Options["support_material_interface_spacing"].category = "Support material"; Options["support_material_interface_spacing"].tooltip = "Spacing between interface lines. Set zero to get a solid interface."; Options["support_material_interface_spacing"].sidetext = "mm"; Options["support_material_interface_spacing"].cli = "support-material-interface-spacing=f"; Options["support_material_interface_spacing"].min = 0; Options["support_material_interface_speed"].type = coFloatOrPercent; Options["support_material_interface_speed"].label = "Support material interface"; Options["support_material_interface_speed"].category = "Support material"; Options["support_material_interface_speed"].tooltip = "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed."; Options["support_material_interface_speed"].sidetext = "mm/s or %"; Options["support_material_interface_speed"].cli = "support-material-interface-speed=s"; Options["support_material_interface_speed"].ratio_over = "support_material_speed"; Options["support_material_interface_speed"].min = 0; Options["support_material_pattern"].type = coEnum; Options["support_material_pattern"].label = "Pattern"; Options["support_material_pattern"].category = "Support material"; Options["support_material_pattern"].tooltip = "Pattern used to generate support material."; Options["support_material_pattern"].cli = "support-material-pattern=s"; Options["support_material_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["support_material_pattern"].enum_values.push_back("rectilinear"); Options["support_material_pattern"].enum_values.push_back("rectilinear-grid"); Options["support_material_pattern"].enum_values.push_back("honeycomb"); Options["support_material_pattern"].enum_values.push_back("pillars"); Options["support_material_pattern"].enum_labels.push_back("rectilinear"); Options["support_material_pattern"].enum_labels.push_back("rectilinear grid"); Options["support_material_pattern"].enum_labels.push_back("honeycomb"); Options["support_material_pattern"].enum_labels.push_back("pillars"); Options["support_material_spacing"].type = coFloat; Options["support_material_spacing"].label = "Pattern spacing"; Options["support_material_spacing"].category = "Support material"; Options["support_material_spacing"].tooltip = "Spacing between support material lines."; Options["support_material_spacing"].sidetext = "mm"; Options["support_material_spacing"].cli = "support-material-spacing=f"; Options["support_material_spacing"].min = 0; Options["support_material_speed"].type = coFloat; Options["support_material_speed"].label = "Support material"; Options["support_material_speed"].category = "Support material"; Options["support_material_speed"].tooltip = "Speed for printing support material."; Options["support_material_speed"].sidetext = "mm/s"; Options["support_material_speed"].cli = "support-material-speed=f"; Options["support_material_speed"].min = 0; Options["support_material_threshold"].type = coInt; Options["support_material_threshold"].label = "Overhang threshold"; Options["support_material_threshold"].category = "Support material"; Options["support_material_threshold"].tooltip = "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)."; Options["support_material_threshold"].sidetext = "°"; Options["support_material_threshold"].cli = "support-material-threshold=i"; Options["support_material_threshold"].min = 0; Options["support_material_threshold"].max = 90; Options["temperature"].type = coInts; Options["temperature"].label = "Other layers"; Options["temperature"].tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable temperature control commands in the output."; Options["temperature"].cli = "temperature=i@"; Options["temperature"].full_label = "Temperature"; Options["temperature"].max = 0; Options["temperature"].max = 400; Options["thin_walls"].type = coBool; Options["thin_walls"].label = "Detect thin walls"; Options["thin_walls"].category = "Layers and Perimeters"; Options["thin_walls"].tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)."; Options["thin_walls"].cli = "thin-walls!"; Options["threads"].type = coInt; Options["threads"].label = "Threads"; Options["threads"].tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors."; Options["threads"].cli = "threads|j=i"; Options["threads"].readonly = true; Options["threads"].min = 1; Options["threads"].max = 16; Options["toolchange_gcode"].type = coString; Options["toolchange_gcode"].label = "Tool change G-code"; Options["toolchange_gcode"].tooltip = "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]."; Options["toolchange_gcode"].cli = "toolchange-gcode=s"; Options["toolchange_gcode"].multiline = true; Options["toolchange_gcode"].full_width = true; Options["toolchange_gcode"].height = 50; Options["top_infill_extrusion_width"].type = coFloatOrPercent; Options["top_infill_extrusion_width"].label = "Top solid infill"; Options["top_infill_extrusion_width"].category = "Extrusion Width"; Options["top_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) it will be computed over layer height."; Options["top_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["top_infill_extrusion_width"].cli = "top-infill-extrusion-width=s"; Options["top_solid_infill_speed"].type = coFloatOrPercent; Options["top_solid_infill_speed"].label = "Top solid infill"; Options["top_solid_infill_speed"].category = "Speed"; Options["top_solid_infill_speed"].tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto."; Options["top_solid_infill_speed"].sidetext = "mm/s or %"; Options["top_solid_infill_speed"].cli = "top-solid-infill-speed=s"; Options["top_solid_infill_speed"].ratio_over = "solid_infill_speed"; Options["top_solid_infill_speed"].min = 0; Options["top_solid_layers"].type = coInt; Options["top_solid_layers"].label = "Top"; Options["top_solid_layers"].category = "Layers and Perimeters"; Options["top_solid_layers"].tooltip = "Number of solid layers to generate on top surfaces."; Options["top_solid_layers"].cli = "top-solid-layers=i"; Options["top_solid_layers"].full_label = "Top solid layers"; Options["top_solid_layers"].min = 0; Options["travel_speed"].type = coFloat; Options["travel_speed"].label = "Travel"; Options["travel_speed"].tooltip = "Speed for travel moves (jumps between distant extrusion points)."; Options["travel_speed"].sidetext = "mm/s"; Options["travel_speed"].cli = "travel-speed=f"; Options["travel_speed"].aliases.push_back("travel_feed_rate"); Options["travel_speed"].min = 1; Options["use_firmware_retraction"].type = coBool; Options["use_firmware_retraction"].label = "Use firmware retraction"; Options["use_firmware_retraction"].tooltip = "This experimental setting uses G10 and G11 commands to have the firmware handle the retraction. This is only supported in recent Marlin."; Options["use_firmware_retraction"].cli = "use-firmware-retraction!"; Options["use_relative_e_distances"].type = coBool; Options["use_relative_e_distances"].label = "Use relative E distances"; Options["use_relative_e_distances"].tooltip = "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values."; Options["use_relative_e_distances"].cli = "use-relative-e-distances!"; Options["use_volumetric_e"].type = coBool; Options["use_volumetric_e"].label = "Use volumetric E"; Options["use_volumetric_e"].tooltip = "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. If your firmware doesn't already know filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' in your start G-code in order to turn volumetric mode on and use the filament diameter associated to the filament selected in Slic3r. This is only supported in recent Marlin."; Options["use_volumetric_e"].cli = "use-volumetric-e!"; Options["vibration_limit"].type = coFloat; Options["vibration_limit"].label = "Vibration limit (deprecated)"; Options["vibration_limit"].tooltip = "This experimental option will slow down those moves hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance. Set zero to disable."; Options["vibration_limit"].sidetext = "Hz"; Options["vibration_limit"].cli = "vibration-limit=f"; Options["vibration_limit"].min = 0; Options["wipe"].type = coBools; Options["wipe"].label = "Wipe while retracting"; Options["wipe"].tooltip = "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders."; Options["wipe"].cli = "wipe!"; Options["xy_size_compensation"].type = coFloat; Options["xy_size_compensation"].label = "XY Size Compensation"; Options["xy_size_compensation"].category = "Advanced"; Options["xy_size_compensation"].tooltip = "The object will be grown/shrunk in the XY plane by the configured value (negative = inwards, positive = outwards). This might be useful for fine-tuning hole sizes."; Options["xy_size_compensation"].sidetext = "mm"; Options["xy_size_compensation"].cli = "xy-size-compensation=f"; Options["z_offset"].type = coFloat; Options["z_offset"].label = "Z offset"; Options["z_offset"].tooltip = "This value will be added (or subtracted) from all the Z coordinates in the output G-code. It is used to compensate for bad Z endstop position: for example, if your endstop zero actually leaves the nozzle 0.3mm far from the print bed, set this to -0.3 (or fix your endstop)."; Options["z_offset"].sidetext = "mm"; Options["z_offset"].cli = "z-offset=f"; return Options; }; t_optiondef_map PrintConfigDef::def = PrintConfigDef::build_def(); void DynamicPrintConfig::normalize() { if (this->has("extruder")) { int extruder = this->option("extruder")->getInt(); this->erase("extruder"); if (extruder != 0) { if (!this->has("infill_extruder")) this->option("infill_extruder", true)->setInt(extruder); if (!this->has("perimeter_extruder")) this->option("perimeter_extruder", true)->setInt(extruder); if (!this->has("support_material_extruder")) this->option("support_material_extruder", true)->setInt(extruder); if (!this->has("support_material_interface_extruder")) this->option("support_material_interface_extruder", true)->setInt(extruder); } } if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all ConfigOptionBools* opt = this->opt("retract_layer_change", true); opt->values.assign(opt->values.size(), false); // set all values to false } { this->opt("perimeters", true)->value = 1; this->opt("top_solid_layers", true)->value = 0; this->opt("fill_density", true)->value = 0; } } } #ifdef SLIC3RXS REGISTER_CLASS(DynamicPrintConfig, "Config"); REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject"); REGISTER_CLASS(PrintRegionConfig, "Config::PrintRegion"); REGISTER_CLASS(GCodeConfig, "Config::GCode"); REGISTER_CLASS(PrintConfig, "Config::Print"); REGISTER_CLASS(FullPrintConfig, "Config::Full"); #endif } Slic3r-1.2.9/xs/src/libslic3r/PrintConfig.hpp000066400000000000000000001106621254023100400207210ustar00rootroot00000000000000#ifndef slic3r_PrintConfig_hpp_ #define slic3r_PrintConfig_hpp_ #include "Config.hpp" namespace Slic3r { enum GCodeFlavor { gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfMachinekit, gcfNoExtrusion, }; enum InfillPattern { ipRectilinear, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, }; enum SupportMaterialPattern { smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpPillars, }; enum SeamPosition { spRandom, spNearest, spAligned }; template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["reprap"] = gcfRepRap; keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; keys_map["sailfish"] = gcfSailfish; keys_map["mach3"] = gcfMach3; keys_map["machinekit"] = gcfMachinekit; keys_map["no-extrusion"] = gcfNoExtrusion; return keys_map; } template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["rectilinear"] = ipRectilinear; keys_map["line"] = ipLine; keys_map["concentric"] = ipConcentric; keys_map["honeycomb"] = ipHoneycomb; keys_map["3dhoneycomb"] = ip3DHoneycomb; keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; return keys_map; } template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["rectilinear"] = smpRectilinear; keys_map["rectilinear-grid"] = smpRectilinearGrid; keys_map["honeycomb"] = smpHoneycomb; keys_map["pillars"] = smpPillars; return keys_map; } template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["random"] = spRandom; keys_map["nearest"] = spNearest; keys_map["aligned"] = spAligned; return keys_map; } class PrintConfigDef { public: static t_optiondef_map def; static t_optiondef_map build_def(); }; class DynamicPrintConfig : public DynamicConfig { public: DynamicPrintConfig() { this->def = &PrintConfigDef::def; }; void normalize(); }; class StaticPrintConfig : public virtual StaticConfig { public: StaticPrintConfig() { this->def = &PrintConfigDef::def; }; }; class PrintObjectConfig : public virtual StaticPrintConfig { public: ConfigOptionBool dont_support_bridges; ConfigOptionFloatOrPercent extrusion_width; ConfigOptionFloatOrPercent first_layer_height; ConfigOptionBool infill_only_where_needed; ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; ConfigOptionEnum seam_position; ConfigOptionBool support_material; ConfigOptionInt support_material_angle; ConfigOptionFloat support_material_contact_distance; ConfigOptionInt support_material_enforce_layers; ConfigOptionInt support_material_extruder; ConfigOptionFloatOrPercent support_material_extrusion_width; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; ConfigOptionInt support_material_threshold; ConfigOptionFloat xy_size_compensation; PrintObjectConfig() : StaticPrintConfig() { this->dont_support_bridges.value = true; this->extrusion_width.value = 0; this->extrusion_width.percent = false; this->first_layer_height.value = 0.35; this->first_layer_height.percent = false; this->infill_only_where_needed.value = false; this->interface_shells.value = false; this->layer_height.value = 0.3; this->raft_layers.value = 0; this->seam_position.value = spAligned; this->support_material.value = false; this->support_material_angle.value = 0; this->support_material_contact_distance.value = 0.2; this->support_material_enforce_layers.value = 0; this->support_material_extruder.value = 1; this->support_material_extrusion_width.value = 0; this->support_material_extrusion_width.percent = false; this->support_material_interface_extruder.value = 1; this->support_material_interface_layers.value = 3; this->support_material_interface_spacing.value = 0; this->support_material_interface_speed.value = 100; this->support_material_interface_speed.percent = true; this->support_material_pattern.value = smpPillars; this->support_material_spacing.value = 2.5; this->support_material_speed.value = 60; this->support_material_threshold.value = 0; this->xy_size_compensation.value = 0; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "dont_support_bridges") return &this->dont_support_bridges; if (opt_key == "extrusion_width") return &this->extrusion_width; if (opt_key == "first_layer_height") return &this->first_layer_height; if (opt_key == "infill_only_where_needed") return &this->infill_only_where_needed; if (opt_key == "interface_shells") return &this->interface_shells; if (opt_key == "layer_height") return &this->layer_height; if (opt_key == "raft_layers") return &this->raft_layers; if (opt_key == "seam_position") return &this->seam_position; if (opt_key == "support_material") return &this->support_material; if (opt_key == "support_material_angle") return &this->support_material_angle; if (opt_key == "support_material_contact_distance") return &this->support_material_contact_distance; if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers; if (opt_key == "support_material_extruder") return &this->support_material_extruder; if (opt_key == "support_material_extrusion_width") return &this->support_material_extrusion_width; if (opt_key == "support_material_interface_extruder") return &this->support_material_interface_extruder; if (opt_key == "support_material_interface_layers") return &this->support_material_interface_layers; if (opt_key == "support_material_interface_spacing") return &this->support_material_interface_spacing; if (opt_key == "support_material_interface_speed") return &this->support_material_interface_speed; if (opt_key == "support_material_pattern") return &this->support_material_pattern; if (opt_key == "support_material_spacing") return &this->support_material_spacing; if (opt_key == "support_material_speed") return &this->support_material_speed; if (opt_key == "support_material_threshold") return &this->support_material_threshold; if (opt_key == "xy_size_compensation") return &this->xy_size_compensation; return NULL; }; }; class PrintRegionConfig : public virtual StaticPrintConfig { public: ConfigOptionInt bottom_solid_layers; ConfigOptionFloat bridge_flow_ratio; ConfigOptionFloat bridge_speed; ConfigOptionEnum external_fill_pattern; ConfigOptionFloatOrPercent external_perimeter_extrusion_width; ConfigOptionFloatOrPercent external_perimeter_speed; ConfigOptionBool external_perimeters_first; ConfigOptionBool extra_perimeters; ConfigOptionInt fill_angle; ConfigOptionPercent fill_density; ConfigOptionEnum fill_pattern; ConfigOptionFloat gap_fill_speed; ConfigOptionInt infill_extruder; ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionInt infill_every_layers; ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloat infill_speed; ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionFloat perimeter_speed; ConfigOptionInt perimeters; ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloat solid_infill_below_area; ConfigOptionInt solid_infill_extruder; ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; ConfigOptionFloatOrPercent top_solid_infill_speed; PrintRegionConfig() : StaticPrintConfig() { this->bottom_solid_layers.value = 3; this->bridge_flow_ratio.value = 1; this->bridge_speed.value = 60; this->external_fill_pattern.value = ipRectilinear; this->external_perimeter_extrusion_width.value = 0; this->external_perimeter_extrusion_width.percent = false; this->external_perimeter_speed.value = 50; this->external_perimeter_speed.percent = true; this->external_perimeters_first.value = false; this->extra_perimeters.value = true; this->fill_angle.value = 45; this->fill_density.value = 20; this->fill_pattern.value = ipHoneycomb; this->gap_fill_speed.value = 20; this->infill_extruder.value = 1; this->infill_extrusion_width.value = 0; this->infill_extrusion_width.percent = false; this->infill_every_layers.value = 1; this->infill_overlap.value = 15; this->infill_overlap.percent = true; this->infill_speed.value = 80; this->overhangs.value = true; this->perimeter_extruder.value = 1; this->perimeter_extrusion_width.value = 0; this->perimeter_extrusion_width.percent = false; this->perimeter_speed.value = 60; this->perimeters.value = 3; this->solid_infill_extruder.value = 1; this->small_perimeter_speed.value = 15; this->small_perimeter_speed.percent = false; this->solid_infill_below_area.value = 70; this->solid_infill_extrusion_width.value = 0; this->solid_infill_extrusion_width.percent = false; this->solid_infill_every_layers.value = 0; this->solid_infill_speed.value = 20; this->solid_infill_speed.percent = false; this->thin_walls.value = true; this->top_infill_extrusion_width.value = 0; this->top_infill_extrusion_width.percent = false; this->top_solid_infill_speed.value = 15; this->top_solid_infill_speed.percent = false; this->top_solid_layers.value = 3; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers; if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio; if (opt_key == "bridge_speed") return &this->bridge_speed; if (opt_key == "external_fill_pattern") return &this->external_fill_pattern; if (opt_key == "external_perimeter_extrusion_width") return &this->external_perimeter_extrusion_width; if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed; if (opt_key == "external_perimeters_first") return &this->external_perimeters_first; if (opt_key == "extra_perimeters") return &this->extra_perimeters; if (opt_key == "fill_angle") return &this->fill_angle; if (opt_key == "fill_density") return &this->fill_density; if (opt_key == "fill_pattern") return &this->fill_pattern; if (opt_key == "gap_fill_speed") return &this->gap_fill_speed; if (opt_key == "infill_extruder") return &this->infill_extruder; if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width; if (opt_key == "infill_every_layers") return &this->infill_every_layers; if (opt_key == "infill_overlap") return &this->infill_overlap; if (opt_key == "infill_speed") return &this->infill_speed; if (opt_key == "overhangs") return &this->overhangs; if (opt_key == "perimeter_extruder") return &this->perimeter_extruder; if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width; if (opt_key == "perimeter_speed") return &this->perimeter_speed; if (opt_key == "perimeters") return &this->perimeters; if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; if (opt_key == "solid_infill_extruder") return &this->solid_infill_extruder; if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; if (opt_key == "thin_walls") return &this->thin_walls; if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width; if (opt_key == "top_solid_infill_speed") return &this->top_solid_infill_speed; if (opt_key == "top_solid_layers") return &this->top_solid_layers; return NULL; }; }; class GCodeConfig : public virtual StaticPrintConfig { public: ConfigOptionString before_layer_gcode; ConfigOptionString end_gcode; ConfigOptionString extrusion_axis; ConfigOptionFloats extrusion_multiplier; ConfigOptionFloats filament_diameter; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; ConfigOptionFloat max_print_speed; ConfigOptionFloat max_volumetric_speed; ConfigOptionFloat pressure_advance; ConfigOptionFloats retract_length; ConfigOptionFloats retract_length_toolchange; ConfigOptionFloats retract_lift; ConfigOptionFloats retract_restart_extra; ConfigOptionFloats retract_restart_extra_toolchange; ConfigOptionInts retract_speed; ConfigOptionString start_gcode; ConfigOptionString toolchange_gcode; ConfigOptionFloat travel_speed; ConfigOptionBool use_firmware_retraction; ConfigOptionBool use_relative_e_distances; ConfigOptionBool use_volumetric_e; GCodeConfig() : StaticPrintConfig() { this->before_layer_gcode.value = ""; this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"; this->extrusion_axis.value = "E"; this->extrusion_multiplier.values.resize(1); this->extrusion_multiplier.values[0] = 1; this->filament_diameter.values.resize(1); this->filament_diameter.values[0] = 3; this->gcode_comments.value = false; this->gcode_flavor.value = gcfRepRap; this->layer_gcode.value = ""; this->max_print_speed.value = 80; this->max_volumetric_speed.value = 0; this->pressure_advance.value = 0; this->retract_length.values.resize(1); this->retract_length.values[0] = 2; this->retract_length_toolchange.values.resize(1); this->retract_length_toolchange.values[0] = 10; this->retract_lift.values.resize(1); this->retract_lift.values[0] = 0; this->retract_restart_extra.values.resize(1); this->retract_restart_extra.values[0] = 0; this->retract_restart_extra_toolchange.values.resize(1); this->retract_restart_extra_toolchange.values[0] = 0; this->retract_speed.values.resize(1); this->retract_speed.values[0] = 40; this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"; this->toolchange_gcode.value = ""; this->travel_speed.value = 130; this->use_firmware_retraction.value = false; this->use_relative_e_distances.value = false; this->use_volumetric_e.value = false; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "before_layer_gcode") return &this->before_layer_gcode; if (opt_key == "end_gcode") return &this->end_gcode; if (opt_key == "extrusion_axis") return &this->extrusion_axis; if (opt_key == "extrusion_multiplier") return &this->extrusion_multiplier; if (opt_key == "filament_diameter") return &this->filament_diameter; if (opt_key == "gcode_comments") return &this->gcode_comments; if (opt_key == "gcode_flavor") return &this->gcode_flavor; if (opt_key == "layer_gcode") return &this->layer_gcode; if (opt_key == "max_print_speed") return &this->max_print_speed; if (opt_key == "max_volumetric_speed") return &this->max_volumetric_speed; if (opt_key == "pressure_advance") return &this->pressure_advance; if (opt_key == "retract_length") return &this->retract_length; if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange; if (opt_key == "retract_lift") return &this->retract_lift; if (opt_key == "retract_restart_extra") return &this->retract_restart_extra; if (opt_key == "retract_restart_extra_toolchange") return &this->retract_restart_extra_toolchange; if (opt_key == "retract_speed") return &this->retract_speed; if (opt_key == "start_gcode") return &this->start_gcode; if (opt_key == "toolchange_gcode") return &this->toolchange_gcode; if (opt_key == "travel_speed") return &this->travel_speed; if (opt_key == "use_firmware_retraction") return &this->use_firmware_retraction; if (opt_key == "use_relative_e_distances") return &this->use_relative_e_distances; if (opt_key == "use_volumetric_e") return &this->use_volumetric_e; return NULL; }; std::string get_extrusion_axis() const { if ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) { return "A"; } else if (this->gcode_flavor.value == gcfNoExtrusion) { return ""; } else { return this->extrusion_axis.value; } }; }; class PrintConfig : public GCodeConfig { public: ConfigOptionBool avoid_crossing_perimeters; ConfigOptionPoints bed_shape; ConfigOptionInt bed_temperature; ConfigOptionFloat bridge_acceleration; ConfigOptionInt bridge_fan_speed; ConfigOptionFloat brim_width; ConfigOptionBool complete_objects; ConfigOptionBool cooling; ConfigOptionFloat default_acceleration; ConfigOptionInt disable_fan_first_layers; ConfigOptionFloat duplicate_distance; ConfigOptionFloat extruder_clearance_height; ConfigOptionFloat extruder_clearance_radius; ConfigOptionPoints extruder_offset; ConfigOptionBool fan_always_on; ConfigOptionInt fan_below_layer_time; ConfigOptionStrings filament_colour; ConfigOptionFloat first_layer_acceleration; ConfigOptionInt first_layer_bed_temperature; ConfigOptionFloatOrPercent first_layer_extrusion_width; ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionInts first_layer_temperature; ConfigOptionBool gcode_arcs; ConfigOptionFloat infill_acceleration; ConfigOptionBool infill_first; ConfigOptionInt max_fan_speed; ConfigOptionInt min_fan_speed; ConfigOptionInt min_print_speed; ConfigOptionFloat min_skirt_length; ConfigOptionString notes; ConfigOptionFloats nozzle_diameter; ConfigOptionBool only_retract_when_crossing_perimeters; ConfigOptionBool ooze_prevention; ConfigOptionString output_filename_format; ConfigOptionFloat perimeter_acceleration; ConfigOptionStrings post_process; ConfigOptionFloat resolution; ConfigOptionFloats retract_before_travel; ConfigOptionBools retract_layer_change; ConfigOptionFloat skirt_distance; ConfigOptionInt skirt_height; ConfigOptionInt skirts; ConfigOptionInt slowdown_below_layer_time; ConfigOptionBool spiral_vase; ConfigOptionInt standby_temperature_delta; ConfigOptionInts temperature; ConfigOptionInt threads; ConfigOptionFloat vibration_limit; ConfigOptionBools wipe; ConfigOptionFloat z_offset; PrintConfig() : GCodeConfig() { this->avoid_crossing_perimeters.value = false; this->bed_shape.values.push_back(Pointf(0,0)); this->bed_shape.values.push_back(Pointf(200,0)); this->bed_shape.values.push_back(Pointf(200,200)); this->bed_shape.values.push_back(Pointf(0,200)); this->bed_temperature.value = 0; this->bridge_acceleration.value = 0; this->bridge_fan_speed.value = 100; this->brim_width.value = 0; this->complete_objects.value = false; this->cooling.value = true; this->default_acceleration.value = 0; this->disable_fan_first_layers.value = 3; this->duplicate_distance.value = 6; this->extruder_clearance_height.value = 20; this->extruder_clearance_radius.value = 20; this->extruder_offset.values.resize(1); this->extruder_offset.values[0] = Pointf(0,0); this->fan_always_on.value = false; this->fan_below_layer_time.value = 60; this->filament_colour.values.resize(1); this->filament_colour.values[0] = "#FFFFFF"; this->first_layer_acceleration.value = 0; this->first_layer_bed_temperature.value = 0; this->first_layer_extrusion_width.value = 200; this->first_layer_extrusion_width.percent = true; this->first_layer_speed.value = 30; this->first_layer_speed.percent = false; this->first_layer_temperature.values.resize(1); this->first_layer_temperature.values[0] = 200; this->gcode_arcs.value = false; this->infill_acceleration.value = 0; this->infill_first.value = false; this->max_fan_speed.value = 100; this->min_fan_speed.value = 35; this->min_print_speed.value = 10; this->min_skirt_length.value = 0; this->notes.value = ""; this->nozzle_diameter.values.resize(1); this->nozzle_diameter.values[0] = 0.5; this->only_retract_when_crossing_perimeters.value = true; this->ooze_prevention.value = false; this->output_filename_format.value = "[input_filename_base].gcode"; this->perimeter_acceleration.value = 0; this->resolution.value = 0; this->retract_before_travel.values.resize(1); this->retract_before_travel.values[0] = 2; this->retract_layer_change.values.resize(1); this->retract_layer_change.values[0] = false; this->skirt_distance.value = 6; this->skirt_height.value = 1; this->skirts.value = 1; this->slowdown_below_layer_time.value = 5; this->spiral_vase.value = false; this->standby_temperature_delta.value = -5; this->temperature.values.resize(1); this->temperature.values[0] = 200; this->threads.value = 2; this->vibration_limit.value = 0; this->wipe.values.resize(1); this->wipe.values[0] = false; this->z_offset.value = 0; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "avoid_crossing_perimeters") return &this->avoid_crossing_perimeters; if (opt_key == "bed_shape") return &this->bed_shape; if (opt_key == "bed_temperature") return &this->bed_temperature; if (opt_key == "bridge_acceleration") return &this->bridge_acceleration; if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed; if (opt_key == "brim_width") return &this->brim_width; if (opt_key == "complete_objects") return &this->complete_objects; if (opt_key == "cooling") return &this->cooling; if (opt_key == "default_acceleration") return &this->default_acceleration; if (opt_key == "disable_fan_first_layers") return &this->disable_fan_first_layers; if (opt_key == "duplicate_distance") return &this->duplicate_distance; if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height; if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius; if (opt_key == "extruder_offset") return &this->extruder_offset; if (opt_key == "fan_always_on") return &this->fan_always_on; if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time; if (opt_key == "filament_colour") return &this->filament_colour; if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration; if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature; if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width; if (opt_key == "first_layer_speed") return &this->first_layer_speed; if (opt_key == "first_layer_temperature") return &this->first_layer_temperature; if (opt_key == "gcode_arcs") return &this->gcode_arcs; if (opt_key == "infill_acceleration") return &this->infill_acceleration; if (opt_key == "infill_first") return &this->infill_first; if (opt_key == "max_fan_speed") return &this->max_fan_speed; if (opt_key == "min_fan_speed") return &this->min_fan_speed; if (opt_key == "min_print_speed") return &this->min_print_speed; if (opt_key == "min_skirt_length") return &this->min_skirt_length; if (opt_key == "notes") return &this->notes; if (opt_key == "nozzle_diameter") return &this->nozzle_diameter; if (opt_key == "only_retract_when_crossing_perimeters") return &this->only_retract_when_crossing_perimeters; if (opt_key == "ooze_prevention") return &this->ooze_prevention; if (opt_key == "output_filename_format") return &this->output_filename_format; if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; if (opt_key == "post_process") return &this->post_process; if (opt_key == "resolution") return &this->resolution; if (opt_key == "retract_before_travel") return &this->retract_before_travel; if (opt_key == "retract_layer_change") return &this->retract_layer_change; if (opt_key == "skirt_distance") return &this->skirt_distance; if (opt_key == "skirt_height") return &this->skirt_height; if (opt_key == "skirts") return &this->skirts; if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time; if (opt_key == "spiral_vase") return &this->spiral_vase; if (opt_key == "standby_temperature_delta") return &this->standby_temperature_delta; if (opt_key == "temperature") return &this->temperature; if (opt_key == "threads") return &this->threads; if (opt_key == "vibration_limit") return &this->vibration_limit; if (opt_key == "wipe") return &this->wipe; if (opt_key == "z_offset") return &this->z_offset; // look in parent class ConfigOption* opt; if ((opt = GCodeConfig::option(opt_key, create)) != NULL) return opt; return NULL; }; }; class HostConfig : public virtual StaticPrintConfig { public: ConfigOptionString octoprint_host; ConfigOptionString octoprint_apikey; HostConfig() : StaticPrintConfig() { this->octoprint_host.value = ""; this->octoprint_apikey.value = ""; }; ConfigOption* option(const t_config_option_key opt_key, bool create = false) { if (opt_key == "octoprint_host") return &this->octoprint_host; if (opt_key == "octoprint_apikey") return &this->octoprint_apikey; return NULL; }; }; class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig, public HostConfig { public: ConfigOption* option(const t_config_option_key opt_key, bool create = false) { ConfigOption* opt; if ((opt = PrintObjectConfig::option(opt_key, create)) != NULL) return opt; if ((opt = PrintRegionConfig::option(opt_key, create)) != NULL) return opt; if ((opt = PrintConfig::option(opt_key, create)) != NULL) return opt; if ((opt = HostConfig::option(opt_key, create)) != NULL) return opt; return NULL; }; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/PrintObject.cpp000066400000000000000000000407761254023100400207250ustar00rootroot00000000000000#include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" namespace Slic3r { PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) : _print(print), _model_object(model_object), typed_slices(false) { // Compute the translation to be applied to our meshes so that we work with smaller coordinates { // Translate meshes so that our toolpath generation algorithms work with smaller // XY coordinates; this translation is an optimization and not strictly required. // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we // don't assume it's already aligned and we don't alter the original position in model. // 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). this->_copies_shift = Point( scale_(modobj_bbox.min.x), scale_(modobj_bbox.min.y)); // Scale the object size and store it Pointf3 size = modobj_bbox.size(); this->size = Point3(scale_(size.x), scale_(size.y), scale_(size.z)); } this->reload_model_instances(); this->layer_height_ranges = model_object->layer_height_ranges; } PrintObject::~PrintObject() { } Print* PrintObject::print() { return this->_print; } ModelObject* PrintObject::model_object() { return this->_model_object; } Points PrintObject::copies() const { return this->_copies; } bool PrintObject::add_copy(const Pointf &point) { Points points = this->_copies; points.push_back(Point::new_scale(point.x, point.y)); return this->set_copies(points); } bool PrintObject::delete_last_copy() { Points points = this->_copies; points.pop_back(); return this->set_copies(points); } bool PrintObject::delete_all_copies() { Points points; return this->set_copies(points); } bool PrintObject::set_copies(const Points &points) { this->_copies = points; // order copies with a nearest neighbor search and translate them by _copies_shift this->_shifted_copies.clear(); this->_shifted_copies.reserve(points.size()); // order copies with a nearest-neighbor search std::vector ordered_copies; Slic3r::Geometry::chained_path(points, ordered_copies); for (std::vector::const_iterator it = ordered_copies.begin(); it != ordered_copies.end(); ++it) { Point copy = points[*it]; copy.translate(this->_copies_shift); this->_shifted_copies.push_back(copy); } bool invalidated = false; if (this->_print->invalidate_step(psSkirt)) invalidated = true; if (this->_print->invalidate_step(psBrim)) invalidated = true; return invalidated; } bool PrintObject::reload_model_instances() { Points copies; for (ModelInstancePtrs::const_iterator i = this->_model_object->instances.begin(); i != this->_model_object->instances.end(); ++i) { copies.push_back(Point::new_scale((*i)->offset.x, (*i)->offset.y)); } return this->set_copies(copies); } BoundingBox PrintObject::bounding_box() const { // since the object is aligned to origin, bounding box coincides with size Points pp; pp.push_back(Point(0,0)); pp.push_back(this->size); return BoundingBox(pp); } void PrintObject::add_region_volume(int region_id, int volume_id) { region_volumes[region_id].push_back(volume_id); } /* 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 */ size_t PrintObject::total_layer_count() const { return this->layer_count() + this->support_layer_count(); } size_t PrintObject::layer_count() const { return this->layers.size(); } void PrintObject::clear_layers() { for (int i = this->layers.size()-1; i >= 0; --i) this->delete_layer(i); } Layer* PrintObject::get_layer(int idx) { return this->layers.at(idx); } Layer* PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z) { Layer* layer = new Layer(id, this, height, print_z, slice_z); layers.push_back(layer); return layer; } void PrintObject::delete_layer(int idx) { LayerPtrs::iterator i = this->layers.begin() + idx; delete *i; this->layers.erase(i); } size_t PrintObject::support_layer_count() const { return this->support_layers.size(); } void PrintObject::clear_support_layers() { for (int i = this->support_layers.size()-1; i >= 0; --i) this->delete_support_layer(i); } SupportLayer* PrintObject::get_support_layer(int idx) { return this->support_layers.at(idx); } SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z) { SupportLayer* layer = new SupportLayer(id, this, height, print_z, -1); support_layers.push_back(layer); return layer; } void PrintObject::delete_support_layer(int idx) { SupportLayerPtrs::iterator i = this->support_layers.begin() + idx; delete *i; this->support_layers.erase(i); } bool PrintObject::invalidate_state_by_config_options(const std::vector &opt_keys) { std::set steps; // this method only accepts PrintObjectConfig and PrintRegionConfig option keys for (std::vector::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) { if (*opt_key == "perimeters" || *opt_key == "extra_perimeters" || *opt_key == "gap_fill_speed" || *opt_key == "overhangs" || *opt_key == "first_layer_extrusion_width" || *opt_key == "perimeter_extrusion_width" || *opt_key == "infill_overlap" || *opt_key == "thin_walls" || *opt_key == "external_perimeters_first") { steps.insert(posPerimeters); } else if (*opt_key == "layer_height" || *opt_key == "first_layer_height" || *opt_key == "xy_size_compensation" || *opt_key == "raft_layers") { steps.insert(posSlice); } else if (*opt_key == "support_material" || *opt_key == "support_material_angle" || *opt_key == "support_material_extruder" || *opt_key == "support_material_extrusion_width" || *opt_key == "support_material_interface_layers" || *opt_key == "support_material_interface_extruder" || *opt_key == "support_material_interface_spacing" || *opt_key == "support_material_interface_speed" || *opt_key == "support_material_pattern" || *opt_key == "support_material_spacing" || *opt_key == "support_material_threshold" || *opt_key == "dont_support_bridges" || *opt_key == "first_layer_extrusion_width") { steps.insert(posSupportMaterial); } else if (*opt_key == "interface_shells" || *opt_key == "infill_only_where_needed" || *opt_key == "infill_every_layers" || *opt_key == "solid_infill_every_layers" || *opt_key == "bottom_solid_layers" || *opt_key == "top_solid_layers" || *opt_key == "solid_infill_below_area" || *opt_key == "infill_extruder" || *opt_key == "solid_infill_extruder" || *opt_key == "infill_extrusion_width") { steps.insert(posPrepareInfill); } else if (*opt_key == "external_fill_pattern" || *opt_key == "fill_angle" || *opt_key == "fill_pattern" || *opt_key == "top_infill_extrusion_width" || *opt_key == "first_layer_extrusion_width") { steps.insert(posInfill); } else if (*opt_key == "fill_density" || *opt_key == "solid_infill_extrusion_width") { steps.insert(posPerimeters); steps.insert(posPrepareInfill); } else if (*opt_key == "external_perimeter_extrusion_width" || *opt_key == "perimeter_extruder") { steps.insert(posPerimeters); steps.insert(posSupportMaterial); } else if (*opt_key == "bridge_flow_ratio") { steps.insert(posPerimeters); steps.insert(posInfill); } else if (*opt_key == "seam_position" || *opt_key == "support_material_speed" || *opt_key == "bridge_speed" || *opt_key == "external_perimeter_speed" || *opt_key == "infill_speed" || *opt_key == "perimeter_speed" || *opt_key == "small_perimeter_speed" || *opt_key == "solid_infill_speed" || *opt_key == "top_solid_infill_speed") { // these options only affect G-code export, so nothing to invalidate } else { // for legacy, if we can't handle this option let's invalidate all steps return this->invalidate_all_steps(); } } bool invalidated = false; for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { if (this->invalidate_step(*step)) invalidated = true; } return invalidated; } bool PrintObject::invalidate_step(PrintObjectStep step) { bool invalidated = this->state.invalidate(step); // propagate to dependent steps if (step == posPerimeters) { this->invalidate_step(posPrepareInfill); this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); } else if (step == posPrepareInfill) { this->invalidate_step(posInfill); } else if (step == posInfill) { this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); } else if (step == posSlice) { this->invalidate_step(posPerimeters); this->invalidate_step(posSupportMaterial); } else if (step == posSupportMaterial) { this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); } return invalidated; } bool PrintObject::invalidate_all_steps() { // make a copy because when invalidating steps the iterators are not working anymore std::set steps = this->state.started; bool invalidated = false; for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { if (this->invalidate_step(*step)) invalidated = true; } return invalidated; } bool PrintObject::has_support_material() const { return this->config.support_material || this->config.raft_layers > 0 || this->config.support_material_enforce_layers > 0; } void PrintObject::bridge_over_infill() { FOREACH_REGION(this->_print, region) { size_t region_id = region - this->_print->regions.begin(); double fill_density = (*region)->config.fill_density.value; if (fill_density == 100) continue; // get bridge flow Flow bridge_flow = (*region)->flow( frSolidInfill, -1, // layer height, not relevant for bridge flow true, // bridge false, // first layer -1, // custom width, not relevant for bridge flow *this ); FOREACH_LAYER(this, layer_it) { if (layer_it == this->layers.begin()) continue; Layer* layer = *layer_it; LayerRegion* layerm = layer->get_region(region_id); // extract the stInternalSolid surfaces that might be transformed into bridges Polygons internal_solid; layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); // check whether the lower area is deep enough for absorbing the extra flow // (for obvious physical reasons but also for preventing the bridge extrudates // from overflowing in 3D preview) ExPolygons to_bridge; { Polygons to_bridge_pp = internal_solid; // iterate through lower layers spanned by bridge_flow double bottom_z = layer->print_z - bridge_flow.height; for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) { Layer* lower_layer = this->layers[i]; // stop iterating if layer is lower than bottom_z if (lower_layer->print_z < bottom_z) break; // iterate through regions and collect internal surfaces Polygons lower_internal; FOREACH_LAYERREGION(lower_layer, lower_layerm_it) (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); // intersect such lower internal surfaces with the candidate solid surfaces intersection(to_bridge_pp, lower_internal, &to_bridge_pp); } // there's no point in bridging too thin/short regions { double min_width = bridge_flow.scaled_width() * 3; offset2(to_bridge_pp, &to_bridge_pp, -min_width, +min_width); } if (to_bridge_pp.empty()) continue; // convert into ExPolygons union_(to_bridge_pp, &to_bridge); } #ifdef SLIC3R_DEBUG printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif // compute the remaning internal solid surfaces as difference ExPolygons not_to_bridge; diff(internal_solid, to_bridge, ¬_to_bridge, true); // build the new collection of fill_surfaces { Surfaces new_surfaces; for (Surfaces::const_iterator surface = layerm->fill_surfaces.surfaces.begin(); surface != layerm->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type != stInternalSolid) new_surfaces.push_back(*surface); } for (ExPolygons::const_iterator ex = to_bridge.begin(); ex != to_bridge.end(); ++ex) new_surfaces.push_back(Surface(stInternalBridge, *ex)); for (ExPolygons::const_iterator ex = not_to_bridge.begin(); ex != not_to_bridge.end(); ++ex) new_surfaces.push_back(Surface(stInternalSolid, *ex)); layerm->fill_surfaces.surfaces = new_surfaces; } /* # exclude infill from the layers below if needed # see discussion at https://github.com/alexrj/Slic3r/issues/240 # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. if (0) { my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { my @new_surfaces = (); # subtract the area from all types of surfaces foreach my $group (@{$lower_layerm->fill_surfaces->group}) { push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( [ map $_->p, @$group ], [ map @$_, @$to_bridge ], )}; push @new_surfaces, map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNALVOID, ), @{intersection_ex( [ map $_->p, @$group ], [ map @$_, @$to_bridge ], )}; } $lower_layerm->fill_surfaces->clear; $lower_layerm->fill_surfaces->append($_) for @new_surfaces; } $excess -= $self->get_layer($i)->height; } } */ } } } #ifdef SLIC3RXS REGISTER_CLASS(PrintObject, "Print::Object"); #endif } Slic3r-1.2.9/xs/src/libslic3r/PrintRegion.cpp000066400000000000000000000045511254023100400207310ustar00rootroot00000000000000#include "Print.hpp" namespace Slic3r { PrintRegion::PrintRegion(Print* print) : _print(print) { } PrintRegion::~PrintRegion() { } Print* PrintRegion::print() { return this->_print; } Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const { ConfigOptionFloatOrPercent config_width; if (width != -1) { // use the supplied custom width, if any config_width.value = width; config_width.percent = false; } else { // otherwise, get extrusion width from configuration // (might be an absolute value, or a percent value, or zero for auto) if (first_layer && this->_print->config.first_layer_extrusion_width.value > 0) { config_width = this->_print->config.first_layer_extrusion_width; } else if (role == frExternalPerimeter) { config_width = this->config.external_perimeter_extrusion_width; } else if (role == frPerimeter) { config_width = this->config.perimeter_extrusion_width; } else if (role == frInfill) { config_width = this->config.infill_extrusion_width; } else if (role == frSolidInfill) { config_width = this->config.solid_infill_extrusion_width; } else if (role == frTopSolidInfill) { config_width = this->config.top_infill_extrusion_width; } else { CONFESS("Unknown role"); } } if (config_width.value == 0) { config_width = object.config.extrusion_width; } // get the configured nozzle_diameter for the extruder associated // to the flow role requested size_t extruder; // 1-based if (role == frPerimeter || role == frExternalPerimeter) { extruder = this->config.perimeter_extruder; } else if (role == frInfill) { extruder = this->config.infill_extruder; } else if (role == frSolidInfill || role == frTopSolidInfill) { extruder = this->config.solid_infill_extruder; } else { CONFESS("Unknown role $role"); } double nozzle_diameter = this->_print->config.nozzle_diameter.get_at(extruder-1); return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)this->config.bridge_flow_ratio : 0.0); } #ifdef SLIC3RXS REGISTER_CLASS(PrintRegion, "Print::Region"); #endif } Slic3r-1.2.9/xs/src/libslic3r/SVG.cpp000066400000000000000000000101371254023100400171250ustar00rootroot00000000000000#include "SVG.hpp" #include #define COORD(x) ((float)unscale(x)*10) namespace Slic3r { SVG::SVG(const char* filename) : arrows(true), filename(filename), fill("grey"), stroke("black") { this->f = fopen(filename, "w"); fprintf(this->f, "\n" "\n" "\n" " \n" " \n" " \n" ); } void SVG::draw(const Line &line, std::string stroke) { fprintf(this->f, " arrows) fprintf(this->f, " marker-end=\"url(#endArrow)\""); fprintf(this->f, "/>\n"); } void SVG::draw(const Lines &lines, std::string stroke) { for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) this->draw(*it, stroke); } void SVG::draw(const IntersectionLines &lines, std::string stroke) { for (IntersectionLines::const_iterator it = lines.begin(); it != lines.end(); ++it) this->draw((Line)*it, stroke); } void SVG::draw(const ExPolygon &expolygon, std::string fill) { this->fill = fill; std::string d; Polygons pp = expolygon; for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { d += this->get_path_d(*p, true) + " "; } this->path(d, true); } void SVG::draw(const ExPolygons &expolygons, std::string fill) { for (ExPolygons::const_iterator it = expolygons.begin(); it != expolygons.end(); ++it) this->draw(*it, fill); } void SVG::draw(const Polygon &polygon, std::string fill) { this->fill = fill; this->path(this->get_path_d(polygon, true), !fill.empty()); } void SVG::draw(const Polygons &polygons, std::string fill) { for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) this->draw(*it, fill); } void SVG::draw(const Polyline &polyline, std::string stroke) { this->stroke = stroke; this->path(this->get_path_d(polyline, false), false); } void SVG::draw(const Polylines &polylines, std::string stroke) { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) this->draw(*it, fill); } void SVG::draw(const Point &point, std::string fill, unsigned int radius) { std::ostringstream svg; svg << " "; fprintf(this->f, "%s\n", svg.str().c_str()); } void SVG::draw(const Points &points, std::string fill, unsigned int radius) { for (Points::const_iterator it = points.begin(); it != points.end(); ++it) this->draw(*it, fill, radius); } void SVG::path(const std::string &d, bool fill) { fprintf( this->f, " \n", d.c_str(), fill ? this->fill.c_str() : "none", this->stroke.c_str(), fill ? "0" : "2", (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : "" ); } std::string SVG::get_path_d(const MultiPoint &mp, bool closed) const { std::ostringstream d; d << "M "; for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) { d << COORD(p->x) << " "; d << COORD(p->y) << " "; } if (closed) d << "z"; return d.str(); } void SVG::Close() { fprintf(this->f, "\n"); fclose(this->f); printf("SVG written to %s\n", this->filename.c_str()); } } Slic3r-1.2.9/xs/src/libslic3r/SVG.hpp000066400000000000000000000024141254023100400171310ustar00rootroot00000000000000#ifndef slic3r_SVG_hpp_ #define slic3r_SVG_hpp_ #include #include "ExPolygon.hpp" #include "Line.hpp" #include "TriangleMesh.hpp" namespace Slic3r { class SVG { public: bool arrows; std::string fill, stroke; SVG(const char* filename); void draw(const Line &line, std::string stroke = "black"); void draw(const Lines &lines, std::string stroke = "black"); void draw(const IntersectionLines &lines, std::string stroke = "black"); void draw(const ExPolygon &expolygon, std::string fill = "grey"); void draw(const ExPolygons &expolygons, std::string fill = "grey"); void draw(const Polygon &polygon, std::string fill = "grey"); void draw(const Polygons &polygons, std::string fill = "grey"); void draw(const Polyline &polyline, std::string stroke = "black"); void draw(const Polylines &polylines, std::string stroke = "black"); void draw(const Point &point, std::string fill = "black", unsigned int radius = 3); void draw(const Points &points, std::string fill = "black", unsigned int radius = 3); void Close(); private: std::string filename; FILE* f; void path(const std::string &d, bool fill); std::string get_path_d(const MultiPoint &mp, bool closed = false) const; }; } #endif Slic3r-1.2.9/xs/src/libslic3r/SupportMaterial.hpp000066400000000000000000000003111254023100400216170ustar00rootroot00000000000000#ifndef slic3r_SupportMaterial_hpp_ #define slic3r_SupportMaterial_hpp_ namespace Slic3r { // how much we extend support around the actual contact area #define SUPPORT_MATERIAL_MARGIN 1.5 } #endif Slic3r-1.2.9/xs/src/libslic3r/Surface.cpp000066400000000000000000000026011254023100400200530ustar00rootroot00000000000000#include "Surface.hpp" namespace Slic3r { double Surface::area() const { return this->expolygon.area(); } bool Surface::is_solid() const { return this->surface_type == stTop || this->surface_type == stBottom || this->surface_type == stBottomBridge || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } bool Surface::is_external() const { return this->surface_type == stTop || this->surface_type == stBottom || this->surface_type == stBottomBridge; } bool Surface::is_internal() const { return this->surface_type == stInternal || this->surface_type == stInternalBridge || this->surface_type == stInternalSolid || this->surface_type == stInternalVoid; } bool Surface::is_bottom() const { return this->surface_type == stBottom || this->surface_type == stBottomBridge; } bool Surface::is_bridge() const { return this->surface_type == stBottomBridge || this->surface_type == stInternalBridge; } #ifdef SLIC3RXS REGISTER_CLASS(Surface, "Surface"); void Surface::from_SV_check(SV* surface_sv) { if (!sv_isa(surface_sv, perl_class_name(this)) && !sv_isa(surface_sv, perl_class_name_ref(this))) CONFESS("Not a valid %s object", perl_class_name(this)); // a XS Surface was supplied *this = *(Surface *)SvIV((SV*)SvRV( surface_sv )); } #endif } Slic3r-1.2.9/xs/src/libslic3r/Surface.hpp000066400000000000000000000021551254023100400200640ustar00rootroot00000000000000#ifndef slic3r_Surface_hpp_ #define slic3r_Surface_hpp_ #include "ExPolygon.hpp" namespace Slic3r { enum SurfaceType { stTop, stBottom, stBottomBridge, stInternal, stInternalSolid, stInternalBridge, stInternalVoid }; class Surface { public: SurfaceType surface_type; ExPolygon expolygon; double thickness; // in mm unsigned short thickness_layers; // in layers double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined) unsigned short extra_perimeters; Surface(SurfaceType _surface_type, const ExPolygon &_expolygon) : surface_type(_surface_type), expolygon(_expolygon), thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) {}; double area() const; bool is_solid() const; bool is_external() const; bool is_internal() const; bool is_bottom() const; bool is_bridge() const; #ifdef SLIC3RXS void from_SV_check(SV* surface_sv); #endif }; typedef std::vector Surfaces; typedef std::vector SurfacesPtr; } #endif Slic3r-1.2.9/xs/src/libslic3r/SurfaceCollection.cpp000066400000000000000000000072751254023100400221030ustar00rootroot00000000000000#include "SurfaceCollection.hpp" #include namespace Slic3r { SurfaceCollection::operator Polygons() const { Polygons polygons; for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { Polygons surface_p = surface->expolygon; polygons.insert(polygons.end(), surface_p.begin(), surface_p.end()); } return polygons; } SurfaceCollection::operator ExPolygons() const { ExPolygons expp; expp.reserve(this->surfaces.size()); for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { expp.push_back(surface->expolygon); } return expp; } void SurfaceCollection::simplify(double tolerance) { Surfaces ss; for (Surfaces::const_iterator it_s = this->surfaces.begin(); it_s != this->surfaces.end(); ++it_s) { ExPolygons expp; it_s->expolygon.simplify(tolerance, expp); for (ExPolygons::const_iterator it_e = expp.begin(); it_e != expp.end(); ++it_e) { Surface s = *it_s; s.expolygon = *it_e; ss.push_back(s); } } this->surfaces = ss; } /* group surfaces by common properties */ void SurfaceCollection::group(std::vector *retval) { for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { // find a group with the same properties SurfacesPtr* group = NULL; for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) { Surface* gkey = git->front(); if ( gkey->surface_type == it->surface_type && gkey->thickness == it->thickness && gkey->thickness_layers == it->thickness_layers && gkey->bridge_angle == it->bridge_angle) { group = &*git; break; } } // if no group with these properties exists, add one if (group == NULL) { retval->resize(retval->size() + 1); group = &retval->back(); } // append surface to group group->push_back(&*it); } } template bool SurfaceCollection::any_internal_contains(const T &item) const { for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->is_internal() && surface->expolygon.contains(item)) return true; } return false; } template bool SurfaceCollection::any_internal_contains(const Polyline &item) const; template bool SurfaceCollection::any_bottom_contains(const T &item) const { for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->is_bottom() && surface->expolygon.contains(item)) return true; } return false; } template bool SurfaceCollection::any_bottom_contains(const Polyline &item) const; SurfacesPtr SurfaceCollection::filter_by_type(SurfaceType type) { SurfacesPtr ss; for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->surface_type == type) ss.push_back(&*surface); } return ss; } void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) { for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->surface_type == type) { Polygons pp = surface->expolygon; polygons->insert(polygons->end(), pp.begin(), pp.end()); } } } #ifdef SLIC3RXS REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); #endif } Slic3r-1.2.9/xs/src/libslic3r/SurfaceCollection.hpp000066400000000000000000000011611254023100400220740ustar00rootroot00000000000000#ifndef slic3r_SurfaceCollection_hpp_ #define slic3r_SurfaceCollection_hpp_ #include "Surface.hpp" #include namespace Slic3r { class SurfaceCollection { public: Surfaces surfaces; operator Polygons() const; operator ExPolygons() const; void simplify(double tolerance); void group(std::vector *retval); template bool any_internal_contains(const T &item) const; template bool any_bottom_contains(const T &item) const; SurfacesPtr filter_by_type(SurfaceType type); void filter_by_type(SurfaceType type, Polygons* polygons); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/TriangleMesh.cpp000066400000000000000000001226261254023100400210570ustar00rootroot00000000000000#include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include #include #include #include #include #include #include #include #include #include #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif namespace Slic3r { TriangleMesh::TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } TriangleMesh::TriangleMesh(const TriangleMesh &other) : stl(other.stl), repaired(other.repaired) { this->stl.heads = NULL; this->stl.tail = NULL; this->stl.error = other.stl.error; if (other.stl.facet_start != NULL) { this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet)); std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start); } if (other.stl.neighbors_start != NULL) { this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors)); std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start); } if (other.stl.v_indices != NULL) { this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct)); std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices); } if (other.stl.v_shared != NULL) { this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex)); std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared); } } TriangleMesh& TriangleMesh::operator= (TriangleMesh other) { this->swap(other); return *this; } void TriangleMesh::swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); std::swap(this->stl.facet_start, other.stl.facet_start); std::swap(this->stl.neighbors_start, other.stl.neighbors_start); std::swap(this->stl.v_indices, other.stl.v_indices); std::swap(this->stl.v_shared, other.stl.v_shared); } TriangleMesh::~TriangleMesh() { stl_close(&this->stl); } void TriangleMesh::ReadSTLFile(char* input_file) { stl_open(&stl, input_file); } void TriangleMesh::write_ascii(char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } void TriangleMesh::write_binary(char* output_file) { stl_write_binary(&this->stl, output_file, ""); } void TriangleMesh::repair() { if (this->repaired) return; // admesh fails when repairing empty meshes if (this->stl.stats.number_of_facets == 0) return; // checking exact stl_check_facets_exact(&stl); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); // checking nearby int last_edges_fixed = 0; float tolerance = stl.stats.shortest_edge; float increment = stl.stats.bounding_diameter / 10000.0; int iterations = 2; if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; } else { break; } } } // remove_unconnected if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { stl_remove_unconnected_facets(&stl); } // fill_holes if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { stl_fill_holes(&stl); stl_clear_error(&stl); } // normal_directions stl_fix_normal_directions(&stl); // normal_values stl_fix_normal_values(&stl); // always calculate the volume and reverse all normals if volume is negative stl_calculate_volume(&stl); // neighbors stl_verify_neighbors(&stl); this->repaired = true; } void TriangleMesh::reset_repair_stats() { this->stl.stats.degenerate_facets = 0; this->stl.stats.edges_fixed = 0; this->stl.stats.facets_removed = 0; this->stl.stats.facets_added = 0; this->stl.stats.facets_reversed = 0; this->stl.stats.backwards_edges = 0; this->stl.stats.normals_fixed = 0; } bool TriangleMesh::needed_repair() const { return this->stl.stats.degenerate_facets > 0 || this->stl.stats.edges_fixed > 0 || this->stl.stats.facets_removed > 0 || this->stl.stats.facets_added > 0 || this->stl.stats.facets_reversed > 0 || this->stl.stats.backwards_edges > 0; } size_t TriangleMesh::facets_count() const { return this->stl.stats.number_of_facets; } void TriangleMesh::WriteOBJFile(char* output_file) { stl_generate_shared_vertices(&stl); stl_write_obj(&stl, output_file); } void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::scale(const Pointf3 &versor) { float fversor[3]; fversor[0] = versor.x; fversor[1] = versor.y; fversor[2] = versor.z; stl_scale_versor(&this->stl, fversor); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::translate(float x, float y, float z) { stl_translate_relative(&(this->stl), x, y, z); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::rotate(float angle, const Axis &axis) { // admesh uses degrees angle = Slic3r::Geometry::rad2deg(angle); if (axis == X) { stl_rotate_x(&(this->stl), angle); } else if (axis == Y) { stl_rotate_y(&(this->stl), angle); } else if (axis == Z) { stl_rotate_z(&(this->stl), angle); } stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::rotate_x(float angle) { this->rotate(angle, X); } void TriangleMesh::rotate_y(float angle) { this->rotate(angle, Y); } void TriangleMesh::rotate_z(float angle) { this->rotate(angle, Z); } void TriangleMesh::flip(const Axis &axis) { if (axis == X) { stl_mirror_yz(&this->stl); } else if (axis == Y) { stl_mirror_xz(&this->stl); } else if (axis == Z) { stl_mirror_xy(&this->stl); } stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::flip_x() { this->flip(X); } void TriangleMesh::flip_y() { this->flip(Y); } void TriangleMesh::flip_z() { this->flip(Z); } void TriangleMesh::align_to_origin() { this->translate( -(this->stl.stats.min.x), -(this->stl.stats.min.y), -(this->stl.stats.min.z) ); } void TriangleMesh::rotate(double angle, Point* center) { this->translate(-center->x, -center->y, 0); stl_rotate_z(&(this->stl), (float)angle); this->translate(+center->x, +center->y, 0); } TriangleMeshPtrs TriangleMesh::split() const { TriangleMeshPtrs meshes; std::set seen_facets; // we need neighbors if (!this->repaired) CONFESS("split() requires repair()"); // loop while we have remaining facets while (1) { // get the first facet std::queue facet_queue; std::deque facets; for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) { if (seen_facets.find(facet_idx) == seen_facets.end()) { // if facet was not seen put it into queue and start searching facet_queue.push(facet_idx); break; } } if (facet_queue.empty()) break; while (!facet_queue.empty()) { int facet_idx = facet_queue.front(); facet_queue.pop(); if (seen_facets.find(facet_idx) != seen_facets.end()) continue; facets.push_back(facet_idx); for (int j = 0; j <= 2; j++) { facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]); } seen_facets.insert(facet_idx); } TriangleMesh* mesh = new TriangleMesh; meshes.push_back(mesh); mesh->stl.stats.type = inmemory; mesh->stl.stats.number_of_facets = facets.size(); mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); int first = 1; for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); facet++) { mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet]; stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first); first = 0; } } return meshes; } void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; stl_invalidate_shared_vertices(&this->stl); this->repaired = false; // update facet count and allocate more memory this->stl.stats.number_of_facets = number_of_facets + mesh.stl.stats.number_of_facets; this->stl.stats.original_num_facets = this->stl.stats.number_of_facets; stl_reallocate(&this->stl); // copy facets for (int i = 0; i < mesh.stl.stats.number_of_facets; i++) { this->stl.facet_start[number_of_facets + i] = mesh.stl.facet_start[i]; } // update size stl_get_size(&this->stl); } /* this will return scaled ExPolygons */ ExPolygons TriangleMesh::horizontal_projection() const { Polygons pp; pp.reserve(this->stl.stats.number_of_facets); for (int i = 0; i < this->stl.stats.number_of_facets; i++) { stl_facet* facet = &this->stl.facet_start[i]; Polygon p; p.points.resize(3); p.points[0] = Point(facet->vertex[0].x / SCALING_FACTOR, facet->vertex[0].y / SCALING_FACTOR); p.points[1] = Point(facet->vertex[1].x / SCALING_FACTOR, facet->vertex[1].y / SCALING_FACTOR); p.points[2] = Point(facet->vertex[2].x / SCALING_FACTOR, facet->vertex[2].y / SCALING_FACTOR); p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that pp.push_back(p); } // the offset factor was tuned using groovemount.stl offset(pp, &pp, 0.01 / SCALING_FACTOR); ExPolygons retval; union_(pp, &retval, true); return retval; } Polygon TriangleMesh::convex_hull() { this->require_shared_vertices(); Points pp; pp.reserve(this->stl.stats.shared_vertices); for (int i = 0; i < this->stl.stats.shared_vertices; i++) { stl_vertex* v = &this->stl.v_shared[i]; pp.push_back(Point(v->x / SCALING_FACTOR, v->y / SCALING_FACTOR)); } return Slic3r::Geometry::convex_hull(pp); } BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.min.x = this->stl.stats.min.x; bb.min.y = this->stl.stats.min.y; bb.min.z = this->stl.stats.min.z; bb.max.x = this->stl.stats.max.x; bb.max.y = this->stl.stats.max.y; bb.max.z = this->stl.stats.max.z; return bb; } void TriangleMesh::require_shared_vertices() { if (!this->repaired) this->repair(); if (this->stl.v_shared == NULL) stl_generate_shared_vertices(&(this->stl)); } #ifdef SLIC3RXS REGISTER_CLASS(TriangleMesh, "TriangleMesh"); SV* TriangleMesh::to_SV() { SV* sv = newSV(0); sv_setref_pv( sv, perl_class_name(this), (void*)this ); return sv; } void TriangleMesh::ReadFromPerl(SV* vertices, SV* facets) { stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory AV* facets_av = (AV*)SvRV(facets); stl.stats.number_of_facets = av_len(facets_av)+1; stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); // read geometry AV* vertices_av = (AV*)SvRV(vertices); for (unsigned int i = 0; i < stl.stats.number_of_facets; i++) { AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = 0; for (unsigned int v = 0; v <= 2; v++) { AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0)); facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0)); facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0)); facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0)); } facet.extra[0] = 0; facet.extra[1] = 0; stl.facet_start[i] = facet; } stl_get_size(&(this->stl)); } #endif void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) { /* This method gets called with a list of unscaled Z coordinates and outputs a vector pointer having the same number of items as the original list. Each item is a vector of polygons created by slicing our mesh at the given heights. This method should basically combine the behavior of the existing Perl methods defined in lib/Slic3r/TriangleMesh.pm: - analyze(): this creates the 'facets_edges' and the 'edges_facets' tables (we don't need the 'edges' table) - slice_facet(): this has to be done for each facet. It generates intersection lines with each plane identified by the Z list. The get_layer_range() binary search used to identify the Z range of the facet is already ported to C++ (see Object.xsp) - make_loops(): this has to be done for each layer. It creates polygons from the lines generated by the previous step. At the end, we free the tables generated by analyze() as we don't need them anymore. FUTURE: parallelize slice_facet() and make_loops() NOTE: this method accepts a vector of floats because the mesh coordinate type is float. */ std::vector lines(z.size()); for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); #ifdef SLIC3R_DEBUG printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, facet->vertex[0].x, facet->vertex[0].y, facet->vertex[0].z, facet->vertex[1].x, facet->vertex[1].y, facet->vertex[1].z, facet->vertex[2].x, facet->vertex[2].y, facet->vertex[2].z); printf("z: min = %.2f, max = %.2f\n", min_z, max_z); #endif // find layer extents std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z #ifdef SLIC3R_DEBUG printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); #endif for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); this->slice_facet(*it / SCALING_FACTOR, *facet, facet_idx, min_z, max_z, &lines[layer_idx]); } } // v_scaled_shared could be freed here // build loops layers->resize(z.size()); for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { int layer_idx = it - lines.begin(); #ifdef SLIC3R_DEBUG printf("Layer %d:\n", layer_idx); #endif this->make_loops(*it, &(*layers)[layer_idx]); } } void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) { std::vector layers_p; this->slice(z, &layers_p); layers->resize(z.size()); for (std::vector::const_iterator loops = layers_p.begin(); loops != layers_p.end(); ++loops) { #ifdef SLIC3R_DEBUG size_t layer_id = loops - layers_p.begin(); printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif this->make_expolygons(*loops, &(*layers)[ loops - layers_p.begin() ]); } } void TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const { std::vector points; std::vector< std::vector::size_type > points_on_layer; bool found_horizontal_edge = false; /* reorder vertices so that the first one is the one with lowest Z this is needed to get all intersection lines in a consistent order (external on the right of the line) */ int i = 0; if (facet.vertex[1].z == min_z) { // vertex 1 has lowest Z i = 1; } else if (facet.vertex[2].z == min_z) { // vertex 2 has lowest Z i = 2; } for (int j = i; (j-i) < 3; j++) { // loop through facet edges int edge_id = this->facets_edges[facet_idx][j % 3]; int a_id = this->mesh->stl.v_indices[facet_idx].vertex[j % 3]; int b_id = this->mesh->stl.v_indices[facet_idx].vertex[(j+1) % 3]; stl_vertex* a = &this->v_scaled_shared[a_id]; stl_vertex* b = &this->v_scaled_shared[b_id]; if (a->z == b->z && a->z == slice_z) { // edge is horizontal and belongs to the current layer stl_vertex &v0 = this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[0] ]; stl_vertex &v1 = this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[1] ]; stl_vertex &v2 = this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[2] ]; IntersectionLine line; if (min_z == max_z) { line.edge_type = feHorizontal; if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { /* if normal points downwards this is a bottom horizontal facet so we reverse its point order */ std::swap(a, b); std::swap(a_id, b_id); } } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { line.edge_type = feTop; std::swap(a, b); std::swap(a_id, b_id); } else { line.edge_type = feBottom; } line.a.x = a->x; line.a.y = a->y; line.b.x = b->x; line.b.y = b->y; line.a_id = a_id; line.b_id = b_id; lines->push_back(line); found_horizontal_edge = true; // if this is a top or bottom edge, we can stop looping through edges // because we won't find anything interesting if (line.edge_type != feHorizontal) return; } else if (a->z == slice_z) { IntersectionPoint point; point.x = a->x; point.y = a->y; point.point_id = a_id; points.push_back(point); points_on_layer.push_back(points.size()-1); } else if (b->z == slice_z) { IntersectionPoint point; point.x = b->x; point.y = b->y; point.point_id = b_id; points.push_back(point); points_on_layer.push_back(points.size()-1); } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { // edge intersects the current layer; calculate intersection IntersectionPoint point; point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); point.edge_id = edge_id; points.push_back(point); } } if (found_horizontal_edge) return; if (!points_on_layer.empty()) { // we can't have only one point on layer because each vertex gets detected // twice (once for each edge), and we can't have three points on layer because // we assume this code is not getting called for horizontal facets assert(points_on_layer.size() == 2); assert( points[ points_on_layer[0] ].point_id == points[ points_on_layer[1] ].point_id ); if (points.size() < 3) return; // no intersection point, this is a V-shaped facet tangent to plane points.erase( points.begin() + points_on_layer[1] ); } if (!points.empty()) { assert(points.size() == 2); // facets must intersect each plane 0 or 2 times IntersectionLine line; line.a = (Point)points[1]; line.b = (Point)points[0]; line.a_id = points[1].point_id; line.b_id = points[0].point_id; line.edge_a_id = points[1].edge_id; line.edge_b_id = points[0].edge_id; lines->push_back(line); return; } } void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) { /* SVG svg("lines.svg"); svg.draw(lines); svg.Close(); */ // remove tangent edges for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { if (line->skip || line->edge_type == feNone) continue; /* if the line is a facet edge, find another facet edge having the same endpoints but in reverse order */ for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++line2) { if (line2->skip || line2->edge_type == feNone) continue; // are these facets adjacent? (sharing a common edge on this layer) if (line->a_id == line2->a_id && line->b_id == line2->b_id) { line2->skip = true; /* if they are both oriented upwards or downwards (like a 'V') then we can remove both edges from this layer since it won't affect the sliced shape */ /* if one of them is oriented upwards and the other is oriented downwards, let's only keep one of them (it doesn't matter which one since all 'top' lines were reversed at slicing) */ if (line->edge_type == line2->edge_type) { line->skip = true; break; } } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { /* if this edge joins two horizontal facets, remove both of them */ if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { line->skip = true; line2->skip = true; break; } } } } // build a map of lines by edge_a_id and a_id std::vector by_edge_a_id, by_a_id; by_edge_a_id.resize(this->mesh->stl.stats.number_of_facets * 3); by_a_id.resize(this->mesh->stl.stats.shared_vertices); for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { if (line->skip) continue; if (line->edge_a_id != -1) by_edge_a_id[line->edge_a_id].push_back(&(*line)); if (line->a_id != -1) by_a_id[line->a_id].push_back(&(*line)); } CYCLE: while (1) { // take first spare line and start a new loop IntersectionLine* first_line = NULL; for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { if (line->skip) continue; first_line = &(*line); break; } if (first_line == NULL) break; first_line->skip = true; IntersectionLinePtrs loop; loop.push_back(first_line); /* printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); */ while (1) { // find a line starting where last one finishes IntersectionLine* next_line = NULL; if (loop.back()->edge_b_id != -1) { IntersectionLinePtrs &candidates = by_edge_a_id[loop.back()->edge_b_id]; for (IntersectionLinePtrs::iterator lineptr = candidates.begin(); lineptr != candidates.end(); ++lineptr) { if ((*lineptr)->skip) continue; next_line = *lineptr; break; } } if (next_line == NULL && loop.back()->b_id != -1) { IntersectionLinePtrs &candidates = by_a_id[loop.back()->b_id]; for (IntersectionLinePtrs::iterator lineptr = candidates.begin(); lineptr != candidates.end(); ++lineptr) { if ((*lineptr)->skip) continue; next_line = *lineptr; break; } } if (next_line == NULL) { // check whether we closed this loop if ((loop.front()->edge_a_id != -1 && loop.front()->edge_a_id == loop.back()->edge_b_id) || (loop.front()->a_id != -1 && loop.front()->a_id == loop.back()->b_id)) { // loop is complete Polygon p; p.points.reserve(loop.size()); for (IntersectionLinePtrs::iterator lineptr = loop.begin(); lineptr != loop.end(); ++lineptr) { p.points.push_back((*lineptr)->a); } loops->push_back(p); #ifdef SLIC3R_DEBUG printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); #endif goto CYCLE; } // we can't close this loop! //// push @failed_loops, [@loop]; //#ifdef SLIC3R_DEBUG printf(" Unable to close this loop having %d points\n", (int)loop.size()); //#endif goto CYCLE; } /* printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); */ loop.push_back(next_line); next_line->skip = true; } } } class _area_comp { public: _area_comp(std::vector* _aa) : abs_area(_aa) {}; bool operator() (const size_t &a, const size_t &b) { return (*this->abs_area)[a] > (*this->abs_area)[b]; } private: std::vector* abs_area; }; void TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) { Polygons loops; this->make_loops(lines, &loops); Polygons cw; for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++loop) { if (loop->area() >= 0) { ExPolygon ex; ex.contour = *loop; slices->push_back(ex); } else { cw.push_back(*loop); } } // assign holes to contours for (Polygons::const_iterator loop = cw.begin(); loop != cw.end(); ++loop) { int slice_idx = -1; double current_contour_area = -1; for (ExPolygons::iterator slice = slices->begin(); slice != slices->end(); ++slice) { if (slice->contour.contains(loop->points.front())) { double area = slice->contour.area(); if (area < current_contour_area || current_contour_area == -1) { slice_idx = slice - slices->begin(); current_contour_area = area; } } } (*slices)[slice_idx].holes.push_back(*loop); } } void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) { /* Input loops are not suitable for evenodd nor nonzero fill types, as we might get two consecutive concentric loops having the same winding order - and we have to respect such order. In that case, evenodd would create wrong inversions, and nonzero would ignore holes inside two concentric contours. So we're ordering loops and collapse consecutive concentric loops having the same winding order. TODO: find a faster algorithm for this, maybe with some sort of binary search. If we computed a "nesting tree" we could also just remove the consecutive loops having the same winding order, and remove the extra one(s) so that we could just supply everything to offset() instead of performing several union/diff calls. we sort by area assuming that the outermost loops have larger area; the previous sorting method, based on $b->contains($a->[0]), failed to nest loops correctly in some edge cases when original model had overlapping facets */ std::vector area; std::vector abs_area; std::vector sorted_area; // vector of indices for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++loop) { double a = loop->area(); area.push_back(a); abs_area.push_back(std::fabs(a)); sorted_area.push_back(loop - loops.begin()); } std::sort(sorted_area.begin(), sorted_area.end(), _area_comp(&abs_area)); // outer first // we don't perform a safety offset now because it might reverse cw loops Polygons p_slices; for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++loop_idx) { /* we rely on the already computed area to determine the winding order of the loops, since the Orientation() function provided by Clipper would do the same, thus repeating the calculation */ Polygons::const_iterator loop = loops.begin() + *loop_idx; if (area[*loop_idx] > +EPSILON) { p_slices.push_back(*loop); } else if (area[*loop_idx] < -EPSILON) { diff(p_slices, *loop, &p_slices); } } // perform a safety offset to merge very close facets (TODO: find test case for this) double safety_offset = scale_(0.0499); ExPolygons ex_slices; offset2(p_slices, &ex_slices, +safety_offset, -safety_offset); #ifdef SLIC3R_DEBUG size_t holes_count = 0; for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++e) { holes_count += e->holes.size(); } printf("%zu surface(s) having %zu holes detected from %zu polylines\n", ex_slices.size(), holes_count, loops.size()); #endif // append to the supplied collection slices->insert(slices->end(), ex_slices.begin(), ex_slices.end()); } void TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) { Polygons pp; this->make_loops(lines, &pp); this->make_expolygons(pp, slices); } void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) { std::vector upper_lines, lower_lines; float scaled_z = scale_(z); for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); // intersect facet with cutting plane std::vector lines; this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &lines); // save intersection lines for generating correct triangulations for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { if (it->edge_type == feTop) { lower_lines.push_back(*it); } else if (it->edge_type == feBottom) { upper_lines.push_back(*it); } else if (it->edge_type != feHorizontal) { lower_lines.push_back(*it); upper_lines.push_back(*it); } } if (min_z > z || (min_z == z && max_z > min_z)) { // facet is above the cut plane and does not belong to it if (upper != NULL) stl_add_facet(&upper->stl, facet); } else if (max_z < z || (max_z == z && max_z > min_z)) { // facet is below the cut plane and does not belong to it if (lower != NULL) stl_add_facet(&lower->stl, facet); } else if (min_z < z && max_z > z) { // facet is cut by the slicing plane // look for the vertex on whose side of the slicing plane there are no other vertices int isolated_vertex; if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) { isolated_vertex = 2; } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) { isolated_vertex = 0; } else { isolated_vertex = 1; } // get vertices starting from the isolated one stl_vertex* v0 = &facet->vertex[isolated_vertex]; stl_vertex* v1 = &facet->vertex[(isolated_vertex+1) % 3]; stl_vertex* v2 = &facet->vertex[(isolated_vertex+2) % 3]; // intersect v0-v1 and v2-v0 with cutting plane and make new vertices stl_vertex v0v1, v2v0; v0v1.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z); v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z); v0v1.z = z; v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z); v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z); v2v0.z = z; // build the triangular facet stl_facet triangle; triangle.normal = facet->normal; triangle.vertex[0] = *v0; triangle.vertex[1] = v0v1; triangle.vertex[2] = v2v0; // build the facets forming a quadrilateral on the other side stl_facet quadrilateral[2]; quadrilateral[0].normal = facet->normal; quadrilateral[0].vertex[0] = *v1; quadrilateral[0].vertex[1] = *v2; quadrilateral[0].vertex[2] = v0v1; quadrilateral[1].normal = facet->normal; quadrilateral[1].vertex[0] = *v2; quadrilateral[1].vertex[1] = v2v0; quadrilateral[1].vertex[2] = v0v1; if (v0->z > z) { if (upper != NULL) stl_add_facet(&upper->stl, &triangle); if (lower != NULL) { stl_add_facet(&lower->stl, &quadrilateral[0]); stl_add_facet(&lower->stl, &quadrilateral[1]); } } else { if (upper != NULL) { stl_add_facet(&upper->stl, &quadrilateral[0]); stl_add_facet(&upper->stl, &quadrilateral[1]); } if (lower != NULL) stl_add_facet(&lower->stl, &triangle); } } } // triangulate holes of upper mesh if (upper != NULL) { // compute shape of section ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); // triangulate section Polygons triangles; for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon) expolygon->triangulate_p2t(&triangles); // convert triangles to facets and append them to mesh for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { Polygon p = *polygon; p.reverse(); stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = -1; for (size_t i = 0; i <= 2; ++i) { facet.vertex[i].x = unscale(p.points[i].x); facet.vertex[i].y = unscale(p.points[i].y); facet.vertex[i].z = z; } stl_add_facet(&upper->stl, &facet); } } // triangulate holes of lower mesh if (lower != NULL) { // compute shape of section ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); // triangulate section Polygons triangles; for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon) expolygon->triangulate_p2t(&triangles); // convert triangles to facets and append them to mesh for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = 1; for (size_t i = 0; i <= 2; ++i) { facet.vertex[i].x = unscale(polygon->points[i].x); facet.vertex[i].y = unscale(polygon->points[i].y); facet.vertex[i].z = z; } stl_add_facet(&lower->stl, &facet); } } stl_get_size(&(upper->stl)); stl_get_size(&(lower->stl)); } TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh), v_scaled_shared(NULL) { // build a table to map a facet_idx to its three edge indices this->mesh->require_shared_vertices(); typedef std::pair t_edge; typedef std::vector t_edges; // edge_idx => a_id,b_id typedef std::map t_edges_map; // a_id,b_id => edge_idx this->facets_edges.resize(this->mesh->stl.stats.number_of_facets); { t_edges edges; // reserve() instad of resize() because otherwise we couldn't read .size() below to assign edge_idx edges.reserve(this->mesh->stl.stats.number_of_facets * 3); // number of edges = number of facets * 3 t_edges_map edges_map; for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { this->facets_edges[facet_idx].resize(3); for (int i = 0; i <= 2; i++) { int a_id = this->mesh->stl.v_indices[facet_idx].vertex[i]; int b_id = this->mesh->stl.v_indices[facet_idx].vertex[(i+1) % 3]; int edge_idx; t_edges_map::const_iterator my_edge = edges_map.find(std::make_pair(b_id,a_id)); if (my_edge != edges_map.end()) { edge_idx = my_edge->second; } else { /* admesh can assign the same edge ID to more than two facets (which is still topologically correct), so we have to search for a duplicate of this edge too in case it was already seen in this orientation */ my_edge = edges_map.find(std::make_pair(a_id,b_id)); if (my_edge != edges_map.end()) { edge_idx = my_edge->second; } else { // edge isn't listed in table, so we insert it edge_idx = edges.size(); edges.push_back(std::make_pair(a_id,b_id)); edges_map[ edges[edge_idx] ] = edge_idx; } } this->facets_edges[facet_idx][i] = edge_idx; #ifdef SLIC3R_DEBUG printf(" [facet %d, edge %d] a_id = %d, b_id = %d --> edge %d\n", facet_idx, i, a_id, b_id, edge_idx); #endif } } } // clone shared vertices coordinates and scale them this->v_scaled_shared = (stl_vertex*)calloc(this->mesh->stl.stats.shared_vertices, sizeof(stl_vertex)); std::copy(this->mesh->stl.v_shared, this->mesh->stl.v_shared + this->mesh->stl.stats.shared_vertices, this->v_scaled_shared); for (int i = 0; i < this->mesh->stl.stats.shared_vertices; i++) { this->v_scaled_shared[i].x /= SCALING_FACTOR; this->v_scaled_shared[i].y /= SCALING_FACTOR; this->v_scaled_shared[i].z /= SCALING_FACTOR; } } TriangleMeshSlicer::~TriangleMeshSlicer() { if (this->v_scaled_shared != NULL) free(this->v_scaled_shared); } } Slic3r-1.2.9/xs/src/libslic3r/TriangleMesh.hpp000066400000000000000000000064441254023100400210630ustar00rootroot00000000000000#ifndef slic3r_TriangleMesh_hpp_ #define slic3r_TriangleMesh_hpp_ #include #include #include #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "ExPolygon.hpp" namespace Slic3r { class TriangleMesh; class TriangleMeshSlicer; typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: TriangleMesh(); TriangleMesh(const TriangleMesh &other); TriangleMesh& operator= (TriangleMesh other); void swap(TriangleMesh &other); ~TriangleMesh(); void ReadSTLFile(char* input_file); void write_ascii(char* output_file); void write_binary(char* output_file); void repair(); void WriteOBJFile(char* output_file); void scale(float factor); void scale(const Pointf3 &versor); void translate(float x, float y, float z); void rotate(float angle, const Axis &axis); void rotate_x(float angle); void rotate_y(float angle); void rotate_z(float angle); void flip(const Axis &axis); void flip_x(); void flip_y(); void flip_z(); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; Polygon convex_hull(); BoundingBoxf3 bounding_box() const; void reset_repair_stats(); bool needed_repair() const; size_t facets_count() const; stl_file stl; bool repaired; #ifdef SLIC3RXS SV* to_SV(); void ReadFromPerl(SV* vertices, SV* facets); #endif private: void require_shared_vertices(); friend class TriangleMeshSlicer; }; enum FacetEdgeType { feNone, feTop, feBottom, feHorizontal }; class IntersectionPoint : public Point { public: int point_id; int edge_id; IntersectionPoint() : point_id(-1), edge_id(-1) {}; }; class IntersectionLine : public Line { public: int a_id; int b_id; int edge_a_id; int edge_b_id; FacetEdgeType edge_type; bool skip; IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; }; typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; class TriangleMeshSlicer { public: TriangleMesh* mesh; TriangleMeshSlicer(TriangleMesh* _mesh); ~TriangleMeshSlicer(); void slice(const std::vector &z, std::vector* layers); void slice(const std::vector &z, std::vector* layers); void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower); private: typedef std::vector< std::vector > t_facets_edges; t_facets_edges facets_edges; stl_vertex* v_scaled_shared; void make_loops(std::vector &lines, Polygons* loops); void make_expolygons(const Polygons &loops, ExPolygons* slices); void make_expolygons_simple(std::vector &lines, ExPolygons* slices); void make_expolygons(std::vector &lines, ExPolygons* slices); }; } #endif Slic3r-1.2.9/xs/src/libslic3r/libslic3r.h000066400000000000000000000014721254023100400200230ustar00rootroot00000000000000#ifndef _libslic3r_h_ #define _libslic3r_h_ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include #include #include #define SLIC3R_VERSION "1.2.9" #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 #define PI 3.141592653589793238 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) typedef long coord_t; typedef double coordf_t; namespace Slic3r { // TODO: make sure X = 0 enum Axis { X, Y, Z }; } using namespace Slic3r; /* Implementation of CONFESS("foo"): */ #define CONFESS(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) void confess_at(const char *file, int line, const char *func, const char *pat, ...); /* End implementation of CONFESS("foo"): */ #endif Slic3r-1.2.9/xs/src/libslic3r/utils.cpp000066400000000000000000000010631254023100400176240ustar00rootroot00000000000000#include void confess_at(const char *file, int line, const char *func, const char *pat, ...) { #ifdef SLIC3RXS va_list args; SV *error_sv = newSVpvf("Error in function %s at %s:%d: ", func, file, line); va_start(args, pat); sv_vcatpvf(error_sv, pat, &args); va_end(args); sv_catpvn(error_sv, "\n\t", 2); dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( sv_2mortal(error_sv) ); PUTBACK; call_pv("Carp::confess", G_DISCARD); FREETMPS; LEAVE; #endif } Slic3r-1.2.9/xs/src/myinit.h000066400000000000000000000006431254023100400155570ustar00rootroot00000000000000#ifndef _myinit_h_ #define _myinit_h_ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #undef read #undef seekdir #include #include #include #ifdef SLIC3RXS extern "C" { #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #undef do_open #undef do_close } #include "perlglue.hpp" #endif #include "libslic3r/libslic3r.h" #endif Slic3r-1.2.9/xs/src/perlglue.hpp000066400000000000000000000032771254023100400164330ustar00rootroot00000000000000#ifndef slic3r_perlglue_hpp_ #define slic3r_perlglue_hpp_ namespace Slic3r { template struct ClassTraits { static const char* name; static const char* name_ref; }; // use this for typedefs for which the forward prototype // in REGISTER_CLASS won't work #define __REGISTER_CLASS(cname, perlname) \ template <>const char* ClassTraits::name = "Slic3r::" perlname; \ template <>const char* ClassTraits::name_ref = "Slic3r::" perlname "::Ref"; #define REGISTER_CLASS(cname,perlname) \ class cname; \ __REGISTER_CLASS(cname, perlname); template const char* perl_class_name(const T*) { return ClassTraits::name; } template const char* perl_class_name_ref(const T*) { return ClassTraits::name_ref; } template SV* perl_to_SV_ref(T &t) { SV* sv = newSV(0); sv_setref_pv( sv, perl_class_name_ref(&t), &t ); return sv; } template SV* perl_to_SV_clone_ref(const T &t) { SV* sv = newSV(0); sv_setref_pv( sv, perl_class_name(&t), new T(t) ); return sv; } template class Ref { T* val; public: Ref() : val(NULL) {} Ref(T* t) : val(t) {} operator T*() const { return val; } static const char* CLASS() { return ClassTraits::name_ref; } }; template class Clone { T* val; public: Clone() : val(NULL) {} Clone(T* t) : val(new T(*t)) {} Clone(const T& t) : val(new T(t)) {} operator T*() const { return val; } static const char* CLASS() { return ClassTraits::name; } }; }; #endif Slic3r-1.2.9/xs/src/poly2tri/000077500000000000000000000000001254023100400156565ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/poly2tri/common/000077500000000000000000000000001254023100400171465ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/poly2tri/common/shapes.cc000066400000000000000000000217241254023100400207460ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "shapes.h" #include namespace p2t { Triangle::Triangle(Point& a, Point& b, Point& c) { points_[0] = &a; points_[1] = &b; points_[2] = &c; neighbors_[0] = NULL; neighbors_[1] = NULL; neighbors_[2] = NULL; constrained_edge[0] = constrained_edge[1] = constrained_edge[2] = false; delaunay_edge[0] = delaunay_edge[1] = delaunay_edge[2] = false; interior_ = false; } // Update neighbor pointers void Triangle::MarkNeighbor(Point* p1, Point* p2, Triangle* t) { if ((p1 == points_[2] && p2 == points_[1]) || (p1 == points_[1] && p2 == points_[2])) neighbors_[0] = t; else if ((p1 == points_[0] && p2 == points_[2]) || (p1 == points_[2] && p2 == points_[0])) neighbors_[1] = t; else if ((p1 == points_[0] && p2 == points_[1]) || (p1 == points_[1] && p2 == points_[0])) neighbors_[2] = t; else assert(0); } // Exhaustive search to update neighbor pointers void Triangle::MarkNeighbor(Triangle& t) { if (t.Contains(points_[1], points_[2])) { neighbors_[0] = &t; t.MarkNeighbor(points_[1], points_[2], this); } else if (t.Contains(points_[0], points_[2])) { neighbors_[1] = &t; t.MarkNeighbor(points_[0], points_[2], this); } else if (t.Contains(points_[0], points_[1])) { neighbors_[2] = &t; t.MarkNeighbor(points_[0], points_[1], this); } } /** * Clears all references to all other triangles and points */ void Triangle::Clear() { Triangle *t; for( int i=0; i<3; i++ ) { t = neighbors_[i]; if( t != NULL ) { t->ClearNeighbor( this ); } } ClearNeighbors(); points_[0]=points_[1]=points_[2] = NULL; } void Triangle::ClearNeighbor(const Triangle *triangle ) { if( neighbors_[0] == triangle ) { neighbors_[0] = NULL; } else if( neighbors_[1] == triangle ) { neighbors_[1] = NULL; } else { neighbors_[2] = NULL; } } void Triangle::ClearNeighbors() { neighbors_[0] = NULL; neighbors_[1] = NULL; neighbors_[2] = NULL; } void Triangle::ClearDelunayEdges() { delaunay_edge[0] = delaunay_edge[1] = delaunay_edge[2] = false; } Point* Triangle::OppositePoint(Triangle& t, const Point& p) { Point *cw = t.PointCW(p); return PointCW(*cw); } // Legalized triangle by rotating clockwise around point(0) void Triangle::Legalize(Point& point) { points_[1] = points_[0]; points_[0] = points_[2]; points_[2] = &point; } // Legalize triagnle by rotating clockwise around oPoint void Triangle::Legalize(Point& opoint, Point& npoint) { if (&opoint == points_[0]) { points_[1] = points_[0]; points_[0] = points_[2]; points_[2] = &npoint; } else if (&opoint == points_[1]) { points_[2] = points_[1]; points_[1] = points_[0]; points_[0] = &npoint; } else if (&opoint == points_[2]) { points_[0] = points_[2]; points_[2] = points_[1]; points_[1] = &npoint; } else { assert(0); } } int Triangle::Index(const Point* p) { if (p == points_[0]) { return 0; } else if (p == points_[1]) { return 1; } else if (p == points_[2]) { return 2; } assert(0); return -1; } int Triangle::EdgeIndex(const Point* p1, const Point* p2) { if (points_[0] == p1) { if (points_[1] == p2) { return 2; } else if (points_[2] == p2) { return 1; } } else if (points_[1] == p1) { if (points_[2] == p2) { return 0; } else if (points_[0] == p2) { return 2; } } else if (points_[2] == p1) { if (points_[0] == p2) { return 1; } else if (points_[1] == p2) { return 0; } } return -1; } void Triangle::MarkConstrainedEdge(int index) { constrained_edge[index] = true; } void Triangle::MarkConstrainedEdge(Edge& edge) { MarkConstrainedEdge(edge.p, edge.q); } // Mark edge as constrained void Triangle::MarkConstrainedEdge(Point* p, Point* q) { if ((q == points_[0] && p == points_[1]) || (q == points_[1] && p == points_[0])) { constrained_edge[2] = true; } else if ((q == points_[0] && p == points_[2]) || (q == points_[2] && p == points_[0])) { constrained_edge[1] = true; } else if ((q == points_[1] && p == points_[2]) || (q == points_[2] && p == points_[1])) { constrained_edge[0] = true; } } // The point counter-clockwise to given point Point* Triangle::PointCW(const Point& point) { if (&point == points_[0]) { return points_[2]; } else if (&point == points_[1]) { return points_[0]; } else if (&point == points_[2]) { return points_[1]; } assert(0); return NULL; } // The point counter-clockwise to given point Point* Triangle::PointCCW(const Point& point) { if (&point == points_[0]) { return points_[1]; } else if (&point == points_[1]) { return points_[2]; } else if (&point == points_[2]) { return points_[0]; } assert(0); return NULL; } // The neighbor clockwise to given point Triangle* Triangle::NeighborCW(const Point& point) { if (&point == points_[0]) { return neighbors_[1]; } else if (&point == points_[1]) { return neighbors_[2]; } return neighbors_[0]; } // The neighbor counter-clockwise to given point Triangle* Triangle::NeighborCCW(const Point& point) { if (&point == points_[0]) { return neighbors_[2]; } else if (&point == points_[1]) { return neighbors_[0]; } return neighbors_[1]; } bool Triangle::GetConstrainedEdgeCCW(const Point& p) { if (&p == points_[0]) { return constrained_edge[2]; } else if (&p == points_[1]) { return constrained_edge[0]; } return constrained_edge[1]; } bool Triangle::GetConstrainedEdgeCW(const Point& p) { if (&p == points_[0]) { return constrained_edge[1]; } else if (&p == points_[1]) { return constrained_edge[2]; } return constrained_edge[0]; } void Triangle::SetConstrainedEdgeCCW(const Point& p, bool ce) { if (&p == points_[0]) { constrained_edge[2] = ce; } else if (&p == points_[1]) { constrained_edge[0] = ce; } else { constrained_edge[1] = ce; } } void Triangle::SetConstrainedEdgeCW(const Point& p, bool ce) { if (&p == points_[0]) { constrained_edge[1] = ce; } else if (&p == points_[1]) { constrained_edge[2] = ce; } else { constrained_edge[0] = ce; } } bool Triangle::GetDelunayEdgeCCW(const Point& p) { if (&p == points_[0]) { return delaunay_edge[2]; } else if (&p == points_[1]) { return delaunay_edge[0]; } return delaunay_edge[1]; } bool Triangle::GetDelunayEdgeCW(const Point& p) { if (&p == points_[0]) { return delaunay_edge[1]; } else if (&p == points_[1]) { return delaunay_edge[2]; } return delaunay_edge[0]; } void Triangle::SetDelunayEdgeCCW(const Point& p, bool e) { if (&p == points_[0]) { delaunay_edge[2] = e; } else if (&p == points_[1]) { delaunay_edge[0] = e; } else { delaunay_edge[1] = e; } } void Triangle::SetDelunayEdgeCW(const Point& p, bool e) { if (&p == points_[0]) { delaunay_edge[1] = e; } else if (&p == points_[1]) { delaunay_edge[2] = e; } else { delaunay_edge[0] = e; } } // The neighbor across to given point Triangle& Triangle::NeighborAcross(const Point& opoint) { if (&opoint == points_[0]) { return *neighbors_[0]; } else if (&opoint == points_[1]) { return *neighbors_[1]; } return *neighbors_[2]; } void Triangle::DebugPrint() { using namespace std; cout << points_[0]->x << "," << points_[0]->y << " "; cout << points_[1]->x << "," << points_[1]->y << " "; cout << points_[2]->x << "," << points_[2]->y << endl; } }Slic3r-1.2.9/xs/src/poly2tri/common/shapes.h000066400000000000000000000165761254023100400206210ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Include guard #ifndef SHAPES_H #define SHAPES_H #include #include #include #include namespace p2t { struct Edge; struct Point { double x, y; /// Default constructor does nothing (for performance). Point() { x = 0.0; y = 0.0; } /// The edges this point constitutes an upper ending point std::vector edge_list; /// Construct using coordinates. Point(double x, double y) : x(x), y(y) {} /// Set this point to all zeros. void set_zero() { x = 0.0; y = 0.0; } /// Set this point to some specified coordinates. void set(double x_, double y_) { x = x_; y = y_; } /// Negate this point. Point operator -() const { Point v; v.set(-x, -y); return v; } /// Add a point to this point. void operator +=(const Point& v) { x += v.x; y += v.y; } /// Subtract a point from this point. void operator -=(const Point& v) { x -= v.x; y -= v.y; } /// Multiply this point by a scalar. void operator *=(double a) { x *= a; y *= a; } /// Get the length of this point (the norm). double Length() const { return sqrt(x * x + y * y); } /// Convert this point into a unit point. Returns the Length. double Normalize() { const double len = Length(); x /= len; y /= len; return len; } }; // Represents a simple polygon's edge struct Edge { Point* p, *q; /// Constructor Edge(Point& p1, Point& p2) : p(&p1), q(&p2) { if (p1.y > p2.y) { q = &p1; p = &p2; } else if (p1.y == p2.y) { if (p1.x > p2.x) { q = &p1; p = &p2; } else if (p1.x == p2.x) { // Repeat points assert(false); } } q->edge_list.push_back(this); } }; // Triangle-based data structures are know to have better performance than quad-edge structures // See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator" // "Triangulations in CGAL" class Triangle { public: /// Constructor Triangle(Point& a, Point& b, Point& c); /// Flags to determine if an edge is a Constrained edge bool constrained_edge[3]; /// Flags to determine if an edge is a Delauney edge bool delaunay_edge[3]; Point* GetPoint(int index); Point* PointCW(const Point& point); Point* PointCCW(const Point& point); Point* OppositePoint(Triangle& t, const Point& p); Triangle* GetNeighbor(int index); void MarkNeighbor(Point* p1, Point* p2, Triangle* t); void MarkNeighbor(Triangle& t); void MarkConstrainedEdge(int index); void MarkConstrainedEdge(Edge& edge); void MarkConstrainedEdge(Point* p, Point* q); int Index(const Point* p); int EdgeIndex(const Point* p1, const Point* p2); Triangle* NeighborCW(const Point& point); Triangle* NeighborCCW(const Point& point); bool GetConstrainedEdgeCCW(const Point& p); bool GetConstrainedEdgeCW(const Point& p); void SetConstrainedEdgeCCW(const Point& p, bool ce); void SetConstrainedEdgeCW(const Point& p, bool ce); bool GetDelunayEdgeCCW(const Point& p); bool GetDelunayEdgeCW(const Point& p); void SetDelunayEdgeCCW(const Point& p, bool e); void SetDelunayEdgeCW(const Point& p, bool e); bool Contains(const Point* p); bool Contains(const Edge& e); bool Contains(const Point* p, const Point* q); void Legalize(Point& point); void Legalize(Point& opoint, Point& npoint); /** * Clears all references to all other triangles and points */ void Clear(); void ClearNeighbor(const Triangle *triangle); void ClearNeighbors(); void ClearDelunayEdges(); inline bool IsInterior(); inline void IsInterior(bool b); Triangle& NeighborAcross(const Point& opoint); void DebugPrint(); private: /// Triangle points Point* points_[3]; /// Neighbor list Triangle* neighbors_[3]; /// Has this triangle been marked as an interior triangle? bool interior_; }; inline bool cmp(const Point* a, const Point* b) { if (a->y < b->y) { return true; } else if (a->y == b->y) { // Make sure q is point with greater x value if (a->x < b->x) { return true; } } return false; } /// Add two points_ component-wise. inline Point operator +(const Point& a, const Point& b) { return Point(a.x + b.x, a.y + b.y); } /// Subtract two points_ component-wise. inline Point operator -(const Point& a, const Point& b) { return Point(a.x - b.x, a.y - b.y); } /// Multiply point by scalar inline Point operator *(double s, const Point& a) { return Point(s * a.x, s * a.y); } inline bool operator ==(const Point& a, const Point& b) { return a.x == b.x && a.y == b.y; } inline bool operator !=(const Point& a, const Point& b) { return !(a.x == b.x) && !(a.y == b.y); } /// Peform the dot product on two vectors. inline double Dot(const Point& a, const Point& b) { return a.x * b.x + a.y * b.y; } /// Perform the cross product on two vectors. In 2D this produces a scalar. inline double Cross(const Point& a, const Point& b) { return a.x * b.y - a.y * b.x; } /// Perform the cross product on a point and a scalar. In 2D this produces /// a point. inline Point Cross(const Point& a, double s) { return Point(s * a.y, -s * a.x); } /// Perform the cross product on a scalar and a point. In 2D this produces /// a point. inline Point Cross(double s, const Point& a) { return Point(-s * a.y, s * a.x); } inline Point* Triangle::GetPoint(int index) { return points_[index]; } inline Triangle* Triangle::GetNeighbor(int index) { return neighbors_[index]; } inline bool Triangle::Contains(const Point* p) { return p == points_[0] || p == points_[1] || p == points_[2]; } inline bool Triangle::Contains(const Edge& e) { return Contains(e.p) && Contains(e.q); } inline bool Triangle::Contains(const Point* p, const Point* q) { return Contains(p) && Contains(q); } inline bool Triangle::IsInterior() { return interior_; } inline void Triangle::IsInterior(bool b) { interior_ = b; } } #endifSlic3r-1.2.9/xs/src/poly2tri/common/utils.h000066400000000000000000000066371254023100400204730ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTILS_H #define UTILS_H // Otherwise #defines like M_PI are undeclared under Visual Studio #define _USE_MATH_DEFINES #include #include namespace p2t { const double PI_3div4 = 3 * M_PI / 4; const double PI_div2 = 1.57079632679489661923; const double EPSILON = 1e-12; enum Orientation { CW, CCW, COLLINEAR }; /** * Forumla to calculate signed area
* Positive if CCW
* Negative if CW
* 0 if collinear
*
 * A[P1,P2,P3]  =  (x1*y2 - y1*x2) + (x2*y3 - y2*x3) + (x3*y1 - y3*x1)
 *              =  (x1-x3)*(y2-y3) - (y1-y3)*(x2-x3)
 * 
*/ Orientation Orient2d(const Point& pa, const Point& pb, const Point& pc) { double detleft = (pa.x - pc.x) * (pb.y - pc.y); double detright = (pa.y - pc.y) * (pb.x - pc.x); double val = detleft - detright; if (val > -EPSILON && val < EPSILON) { return COLLINEAR; } else if (val > 0) { return CCW; } return CW; } /* bool InScanArea(Point& pa, Point& pb, Point& pc, Point& pd) { double pdx = pd.x; double pdy = pd.y; double adx = pa.x - pdx; double ady = pa.y - pdy; double bdx = pb.x - pdx; double bdy = pb.y - pdy; double adxbdy = adx * bdy; double bdxady = bdx * ady; double oabd = adxbdy - bdxady; if (oabd <= EPSILON) { return false; } double cdx = pc.x - pdx; double cdy = pc.y - pdy; double cdxady = cdx * ady; double adxcdy = adx * cdy; double ocad = cdxady - adxcdy; if (ocad <= EPSILON) { return false; } return true; } */ bool InScanArea(const Point& pa, const Point& pb, const Point& pc, const Point& pd) { double oadb = (pa.x - pb.x)*(pd.y - pb.y) - (pd.x - pb.x)*(pa.y - pb.y); if (oadb >= -EPSILON) { return false; } double oadc = (pa.x - pc.x)*(pd.y - pc.y) - (pd.x - pc.x)*(pa.y - pc.y); if (oadc <= EPSILON) { return false; } return true; } } #endifSlic3r-1.2.9/xs/src/poly2tri/poly2tri.h000066400000000000000000000032661254023100400176220ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef POLY2TRI_H #define POLY2TRI_H #include "common/shapes.h" #include "sweep/cdt.h" #endifSlic3r-1.2.9/xs/src/poly2tri/sweep/000077500000000000000000000000001254023100400170015ustar00rootroot00000000000000Slic3r-1.2.9/xs/src/poly2tri/sweep/advancing_front.cc000066400000000000000000000061361254023100400224600ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "advancing_front.h" namespace p2t { AdvancingFront::AdvancingFront(Node& head, Node& tail) { head_ = &head; tail_ = &tail; search_node_ = &head; } Node* AdvancingFront::LocateNode(double x) { Node* node = search_node_; if (x < node->value) { while ((node = node->prev) != NULL) { if (x >= node->value) { search_node_ = node; return node; } } } else { while ((node = node->next) != NULL) { if (x < node->value) { search_node_ = node->prev; return node->prev; } } } return NULL; } Node* AdvancingFront::FindSearchNode(double x) { (void)x; // suppress compiler warnings "unused parameter 'x'" // TODO: implement BST index return search_node_; } Node* AdvancingFront::LocatePoint(const Point* point) { const double px = point->x; Node* node = FindSearchNode(px); const double nx = node->point->x; if (px == nx) { if (point != node->point) { // We might have two nodes with same x value for a short time if (point == node->prev->point) { node = node->prev; } else if (point == node->next->point) { node = node->next; } else { assert(0); } } } else if (px < nx) { while ((node = node->prev) != NULL) { if (point == node->point) { break; } } } else { while ((node = node->next) != NULL) { if (point == node->point) break; } } if(node) search_node_ = node; return node; } AdvancingFront::~AdvancingFront() { } }Slic3r-1.2.9/xs/src/poly2tri/sweep/advancing_front.h000066400000000000000000000055411254023100400223210ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef ADVANCED_FRONT_H #define ADVANCED_FRONT_H #include "../common/shapes.h" namespace p2t { struct Node; // Advancing front node struct Node { Point* point; Triangle* triangle; Node* next; Node* prev; double value; Node(Point& p) : point(&p), triangle(NULL), next(NULL), prev(NULL), value(p.x) { } Node(Point& p, Triangle& t) : point(&p), triangle(&t), next(NULL), prev(NULL), value(p.x) { } }; // Advancing front class AdvancingFront { public: AdvancingFront(Node& head, Node& tail); // Destructor ~AdvancingFront(); Node* head(); void set_head(Node* node); Node* tail(); void set_tail(Node* node); Node* search(); void set_search(Node* node); /// Locate insertion point along advancing front Node* LocateNode(double x); Node* LocatePoint(const Point* point); private: Node* head_, *tail_, *search_node_; Node* FindSearchNode(double x); }; inline Node* AdvancingFront::head() { return head_; } inline void AdvancingFront::set_head(Node* node) { head_ = node; } inline Node* AdvancingFront::tail() { return tail_; } inline void AdvancingFront::set_tail(Node* node) { tail_ = node; } inline Node* AdvancingFront::search() { return search_node_; } inline void AdvancingFront::set_search(Node* node) { search_node_ = node; } } #endifSlic3r-1.2.9/xs/src/poly2tri/sweep/cdt.cc000066400000000000000000000043111254023100400200610ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "cdt.h" namespace p2t { CDT::CDT(const std::vector& polyline) { sweep_context_ = new SweepContext(polyline); sweep_ = new Sweep; } void CDT::AddHole(const std::vector& polyline) { sweep_context_->AddHole(polyline); } void CDT::AddPoint(Point* point) { sweep_context_->AddPoint(point); } void CDT::Triangulate() { sweep_->Triangulate(*sweep_context_); } std::vector CDT::GetTriangles() { return sweep_context_->GetTriangles(); } std::list CDT::GetMap() { return sweep_context_->GetMap(); } CDT::~CDT() { delete sweep_context_; delete sweep_; } }Slic3r-1.2.9/xs/src/poly2tri/sweep/cdt.h000066400000000000000000000050431254023100400177260ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CDT_H #define CDT_H #include "advancing_front.h" #include "sweep_context.h" #include "sweep.h" /** * * @author Mason Green * */ namespace p2t { class CDT { public: /** * Constructor - add polyline with non repeating points * * @param polyline */ CDT(const std::vector& polyline); /** * Destructor - clean up memory */ ~CDT(); /** * Add a hole * * @param polyline */ void AddHole(const std::vector& polyline); /** * Add a steiner point * * @param point */ void AddPoint(Point* point); /** * Triangulate - do this AFTER you've added the polyline, holes, and Steiner points */ void Triangulate(); /** * Get CDT triangles */ std::vector GetTriangles(); /** * Get triangle map */ std::list GetMap(); private: /** * Internals */ SweepContext* sweep_context_; Sweep* sweep_; }; } #endifSlic3r-1.2.9/xs/src/poly2tri/sweep/sweep.cc000066400000000000000000000572201254023100400204410ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "sweep.h" #include "sweep_context.h" #include "advancing_front.h" #include "../common/utils.h" namespace p2t { // Triangulate simple polygon with holes void Sweep::Triangulate(SweepContext& tcx) { tcx.InitTriangulation(); tcx.CreateAdvancingFront(nodes_); // Sweep points; build mesh SweepPoints(tcx); // Clean up FinalizationPolygon(tcx); } void Sweep::SweepPoints(SweepContext& tcx) { for (size_t i = 1; i < tcx.point_count(); i++) { Point& point = *tcx.GetPoint(i); Node* node = &PointEvent(tcx, point); for (unsigned int i = 0; i < point.edge_list.size(); i++) { EdgeEvent(tcx, point.edge_list[i], node); } } } void Sweep::FinalizationPolygon(SweepContext& tcx) { // Get an Internal triangle to start with Triangle* t = tcx.front()->head()->next->triangle; Point* p = tcx.front()->head()->next->point; while (!t->GetConstrainedEdgeCW(*p)) { t = t->NeighborCCW(*p); } // Collect interior triangles constrained by edges tcx.MeshClean(*t); } Node& Sweep::PointEvent(SweepContext& tcx, Point& point) { Node& node = tcx.LocateNode(point); Node& new_node = NewFrontTriangle(tcx, point, node); // Only need to check +epsilon since point never have smaller // x value than node due to how we fetch nodes from the front if (point.x <= node.point->x + EPSILON) { Fill(tcx, node); } //tcx.AddNode(new_node); FillAdvancingFront(tcx, new_node); return new_node; } void Sweep::EdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { tcx.edge_event.constrained_edge = edge; tcx.edge_event.right = (edge->p->x > edge->q->x); if (IsEdgeSideOfTriangle(*node->triangle, *edge->p, *edge->q)) { return; } // For now we will do all needed filling // TODO: integrate with flip process might give some better performance // but for now this avoid the issue with cases that needs both flips and fills FillEdgeEvent(tcx, edge, node); EdgeEvent(tcx, *edge->p, *edge->q, node->triangle, *edge->q); } void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point) { if (IsEdgeSideOfTriangle(*triangle, ep, eq)) { return; } Point* p1 = triangle->PointCCW(point); Orientation o1 = Orient2d(eq, *p1, ep); if (o1 == COLLINEAR) { if( triangle->Contains(&eq, p1)) { triangle->MarkConstrainedEdge(&eq, p1 ); // We are modifying the constraint maybe it would be better to // not change the given constraint and just keep a variable for the new constraint tcx.edge_event.constrained_edge->q = p1; triangle = &triangle->NeighborAcross(point); EdgeEvent( tcx, ep, *p1, triangle, *p1 ); } else { std::runtime_error("EdgeEvent - collinear points not supported"); assert(0); } return; } Point* p2 = triangle->PointCW(point); Orientation o2 = Orient2d(eq, *p2, ep); if (o2 == COLLINEAR) { if( triangle->Contains(&eq, p2)) { triangle->MarkConstrainedEdge(&eq, p2 ); // We are modifying the constraint maybe it would be better to // not change the given constraint and just keep a variable for the new constraint tcx.edge_event.constrained_edge->q = p2; triangle = &triangle->NeighborAcross(point); EdgeEvent( tcx, ep, *p2, triangle, *p2 ); } else { std::runtime_error("EdgeEvent - collinear points not supported"); assert(0); } return; } if (o1 == o2) { // Need to decide if we are rotating CW or CCW to get to a triangle // that will cross edge if (o1 == CW) { triangle = triangle->NeighborCCW(point); } else{ triangle = triangle->NeighborCW(point); } EdgeEvent(tcx, ep, eq, triangle, point); } else { // This triangle crosses constraint so lets flippin start! FlipEdgeEvent(tcx, ep, eq, triangle, point); } } bool Sweep::IsEdgeSideOfTriangle(Triangle& triangle, Point& ep, Point& eq) { const int index = triangle.EdgeIndex(&ep, &eq); if (index != -1) { triangle.MarkConstrainedEdge(index); Triangle* t = triangle.GetNeighbor(index); if (t) { t->MarkConstrainedEdge(&ep, &eq); } return true; } return false; } Node& Sweep::NewFrontTriangle(SweepContext& tcx, Point& point, Node& node) { Triangle* triangle = new Triangle(point, *node.point, *node.next->point); triangle->MarkNeighbor(*node.triangle); tcx.AddToMap(triangle); Node* new_node = new Node(point); nodes_.push_back(new_node); new_node->next = node.next; new_node->prev = &node; node.next->prev = new_node; node.next = new_node; if (!Legalize(tcx, *triangle)) { tcx.MapTriangleToNodes(*triangle); } return *new_node; } void Sweep::Fill(SweepContext& tcx, Node& node) { Triangle* triangle = new Triangle(*node.prev->point, *node.point, *node.next->point); // TODO: should copy the constrained_edge value from neighbor triangles // for now constrained_edge values are copied during the legalize triangle->MarkNeighbor(*node.prev->triangle); triangle->MarkNeighbor(*node.triangle); tcx.AddToMap(triangle); // Update the advancing front node.prev->next = node.next; node.next->prev = node.prev; // If it was legalized the triangle has already been mapped if (!Legalize(tcx, *triangle)) { tcx.MapTriangleToNodes(*triangle); } } void Sweep::FillAdvancingFront(SweepContext& tcx, Node& n) { // Fill right holes Node* node = n.next; while (node->next) { // if HoleAngle exceeds 90 degrees then break. if (LargeHole_DontFill(node)) break; Fill(tcx, *node); node = node->next; } // Fill left holes node = n.prev; while (node->prev) { // if HoleAngle exceeds 90 degrees then break. if (LargeHole_DontFill(node)) break; Fill(tcx, *node); node = node->prev; } // Fill right basins if (n.next && n.next->next) { const double angle = BasinAngle(n); if (angle < PI_3div4) { FillBasin(tcx, n); } } } // True if HoleAngle exceeds 90 degrees. bool Sweep::LargeHole_DontFill(const Node* node) const { const Node* nextNode = node->next; const Node* prevNode = node->prev; if (!AngleExceeds90Degrees(node->point, nextNode->point, prevNode->point)) return false; // Check additional points on front. const Node* next2Node = nextNode->next; // "..Plus.." because only want angles on same side as point being added. if ((next2Node != NULL) && !AngleExceedsPlus90DegreesOrIsNegative(node->point, next2Node->point, prevNode->point)) return false; const Node* prev2Node = prevNode->prev; // "..Plus.." because only want angles on same side as point being added. if ((prev2Node != NULL) && !AngleExceedsPlus90DegreesOrIsNegative(node->point, nextNode->point, prev2Node->point)) return false; return true; } bool Sweep::AngleExceeds90Degrees(const Point* origin, const Point* pa, const Point* pb) const { const double angle = Angle(origin, pa, pb); return ((angle > PI_div2) || (angle < -PI_div2)); } bool Sweep::AngleExceedsPlus90DegreesOrIsNegative(const Point* origin, const Point* pa, const Point* pb) const { const double angle = Angle(origin, pa, pb); return (angle > PI_div2) || (angle < 0); } double Sweep::Angle(const Point* origin, const Point* pa, const Point* pb) const { /* Complex plane * ab = cosA +i*sinA * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) * atan2(y,x) computes the principal value of the argument function * applied to the complex number x+iy * Where x = ax*bx + ay*by * y = ax*by - ay*bx */ const double px = origin->x; const double py = origin->y; const double ax = pa->x- px; const double ay = pa->y - py; const double bx = pb->x - px; const double by = pb->y - py; const double x = ax * by - ay * bx; const double y = ax * bx + ay * by; return atan2(x, y); } double Sweep::BasinAngle(const Node& node) const { const double ax = node.point->x - node.next->next->point->x; const double ay = node.point->y - node.next->next->point->y; return atan2(ay, ax); } double Sweep::HoleAngle(const Node& node) const { /* Complex plane * ab = cosA +i*sinA * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) * atan2(y,x) computes the principal value of the argument function * applied to the complex number x+iy * Where x = ax*bx + ay*by * y = ax*by - ay*bx */ const double ax = node.next->point->x - node.point->x; const double ay = node.next->point->y - node.point->y; const double bx = node.prev->point->x - node.point->x; const double by = node.prev->point->y - node.point->y; return atan2(ax * by - ay * bx, ax * bx + ay * by); } bool Sweep::Legalize(SweepContext& tcx, Triangle& t) { // To legalize a triangle we start by finding if any of the three edges // violate the Delaunay condition for (int i = 0; i < 3; i++) { if (t.delaunay_edge[i]) continue; Triangle* ot = t.GetNeighbor(i); if (ot) { Point* p = t.GetPoint(i); Point* op = ot->OppositePoint(t, *p); int oi = ot->Index(op); // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) // then we should not try to legalize if (ot->constrained_edge[oi] || ot->delaunay_edge[oi]) { t.constrained_edge[i] = ot->constrained_edge[oi]; continue; } bool inside = Incircle(*p, *t.PointCCW(*p), *t.PointCW(*p), *op); if (inside) { // Lets mark this shared edge as Delaunay t.delaunay_edge[i] = true; ot->delaunay_edge[oi] = true; // Lets rotate shared edge one vertex CW to legalize it RotateTrianglePair(t, *p, *ot, *op); // We now got one valid Delaunay Edge shared by two triangles // This gives us 4 new edges to check for Delaunay // Make sure that triangle to node mapping is done only one time for a specific triangle bool not_legalized = !Legalize(tcx, t); if (not_legalized) { tcx.MapTriangleToNodes(t); } not_legalized = !Legalize(tcx, *ot); if (not_legalized) tcx.MapTriangleToNodes(*ot); // Reset the Delaunay edges, since they only are valid Delaunay edges // until we add a new triangle or point. // XXX: need to think about this. Can these edges be tried after we // return to previous recursive level? t.delaunay_edge[i] = false; ot->delaunay_edge[oi] = false; // If triangle have been legalized no need to check the other edges since // the recursive legalization will handles those so we can end here. return true; } } } return false; } bool Sweep::Incircle(const Point& pa, const Point& pb, const Point& pc, const Point& pd) const { const double adx = pa.x - pd.x; const double ady = pa.y - pd.y; const double bdx = pb.x - pd.x; const double bdy = pb.y - pd.y; const double adxbdy = adx * bdy; const double bdxady = bdx * ady; const double oabd = adxbdy - bdxady; if (oabd <= 0) return false; const double cdx = pc.x - pd.x; const double cdy = pc.y - pd.y; const double cdxady = cdx * ady; const double adxcdy = adx * cdy; const double ocad = cdxady - adxcdy; if (ocad <= 0) return false; const double bdxcdy = bdx * cdy; const double cdxbdy = cdx * bdy; const double alift = adx * adx + ady * ady; const double blift = bdx * bdx + bdy * bdy; const double clift = cdx * cdx + cdy * cdy; const double det = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd; return det > 0; } void Sweep::RotateTrianglePair(Triangle& t, Point& p, Triangle& ot, Point& op) const { Triangle* n1, *n2, *n3, *n4; n1 = t.NeighborCCW(p); n2 = t.NeighborCW(p); n3 = ot.NeighborCCW(op); n4 = ot.NeighborCW(op); bool ce1, ce2, ce3, ce4; ce1 = t.GetConstrainedEdgeCCW(p); ce2 = t.GetConstrainedEdgeCW(p); ce3 = ot.GetConstrainedEdgeCCW(op); ce4 = ot.GetConstrainedEdgeCW(op); bool de1, de2, de3, de4; de1 = t.GetDelunayEdgeCCW(p); de2 = t.GetDelunayEdgeCW(p); de3 = ot.GetDelunayEdgeCCW(op); de4 = ot.GetDelunayEdgeCW(op); t.Legalize(p, op); ot.Legalize(op, p); // Remap delaunay_edge ot.SetDelunayEdgeCCW(p, de1); t.SetDelunayEdgeCW(p, de2); t.SetDelunayEdgeCCW(op, de3); ot.SetDelunayEdgeCW(op, de4); // Remap constrained_edge ot.SetConstrainedEdgeCCW(p, ce1); t.SetConstrainedEdgeCW(p, ce2); t.SetConstrainedEdgeCCW(op, ce3); ot.SetConstrainedEdgeCW(op, ce4); // Remap neighbors // XXX: might optimize the markNeighbor by keeping track of // what side should be assigned to what neighbor after the // rotation. Now mark neighbor does lots of testing to find // the right side. t.ClearNeighbors(); ot.ClearNeighbors(); if (n1) ot.MarkNeighbor(*n1); if (n2) t.MarkNeighbor(*n2); if (n3) t.MarkNeighbor(*n3); if (n4) ot.MarkNeighbor(*n4); t.MarkNeighbor(ot); } void Sweep::FillBasin(SweepContext& tcx, Node& node) { if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { tcx.basin.left_node = node.next->next; } else { tcx.basin.left_node = node.next; } // Find the bottom and right node tcx.basin.bottom_node = tcx.basin.left_node; while (tcx.basin.bottom_node->next && tcx.basin.bottom_node->point->y >= tcx.basin.bottom_node->next->point->y) { tcx.basin.bottom_node = tcx.basin.bottom_node->next; } if (tcx.basin.bottom_node == tcx.basin.left_node) { // No valid basin return; } tcx.basin.right_node = tcx.basin.bottom_node; while (tcx.basin.right_node->next && tcx.basin.right_node->point->y < tcx.basin.right_node->next->point->y) { tcx.basin.right_node = tcx.basin.right_node->next; } if (tcx.basin.right_node == tcx.basin.bottom_node) { // No valid basins return; } tcx.basin.width = tcx.basin.right_node->point->x - tcx.basin.left_node->point->x; tcx.basin.left_highest = tcx.basin.left_node->point->y > tcx.basin.right_node->point->y; FillBasinReq(tcx, tcx.basin.bottom_node); } void Sweep::FillBasinReq(SweepContext& tcx, Node* node) { // if shallow stop filling if (IsShallow(tcx, *node)) { return; } Fill(tcx, *node); if (node->prev == tcx.basin.left_node && node->next == tcx.basin.right_node) { return; } else if (node->prev == tcx.basin.left_node) { Orientation o = Orient2d(*node->point, *node->next->point, *node->next->next->point); if (o == CW) { return; } node = node->next; } else if (node->next == tcx.basin.right_node) { Orientation o = Orient2d(*node->point, *node->prev->point, *node->prev->prev->point); if (o == CCW) { return; } node = node->prev; } else { // Continue with the neighbor node with lowest Y value if (node->prev->point->y < node->next->point->y) { node = node->prev; } else { node = node->next; } } FillBasinReq(tcx, node); } bool Sweep::IsShallow(SweepContext& tcx, Node& node) { double height; if (tcx.basin.left_highest) { height = tcx.basin.left_node->point->y - node.point->y; } else { height = tcx.basin.right_node->point->y - node.point->y; } // if shallow stop filling if (tcx.basin.width > height) { return true; } return false; } void Sweep::FillEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { if (tcx.edge_event.right) { FillRightAboveEdgeEvent(tcx, edge, node); } else { FillLeftAboveEdgeEvent(tcx, edge, node); } } void Sweep::FillRightAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { while (node->next->point->x < edge->p->x) { // Check if next node is below the edge if (Orient2d(*edge->q, *node->next->point, *edge->p) == CCW) { FillRightBelowEdgeEvent(tcx, edge, *node); } else { node = node->next; } } } void Sweep::FillRightBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { if (node.point->x < edge->p->x) { if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { // Concave FillRightConcaveEdgeEvent(tcx, edge, node); } else{ // Convex FillRightConvexEdgeEvent(tcx, edge, node); // Retry this one FillRightBelowEdgeEvent(tcx, edge, node); } } } void Sweep::FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { Fill(tcx, *node.next); if (node.next->point != edge->p) { // Next above or below edge? if (Orient2d(*edge->q, *node.next->point, *edge->p) == CCW) { // Below if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { // Next is concave FillRightConcaveEdgeEvent(tcx, edge, node); } else { // Next is convex } } } } void Sweep::FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { // Next concave or convex? if (Orient2d(*node.next->point, *node.next->next->point, *node.next->next->next->point) == CCW) { // Concave FillRightConcaveEdgeEvent(tcx, edge, *node.next); } else{ // Convex // Next above or below edge? if (Orient2d(*edge->q, *node.next->next->point, *edge->p) == CCW) { // Below FillRightConvexEdgeEvent(tcx, edge, *node.next); } else{ // Above } } } void Sweep::FillLeftAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { while (node->prev->point->x > edge->p->x) { // Check if next node is below the edge if (Orient2d(*edge->q, *node->prev->point, *edge->p) == CW) { FillLeftBelowEdgeEvent(tcx, edge, *node); } else { node = node->prev; } } } void Sweep::FillLeftBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { if (node.point->x > edge->p->x) { if (Orient2d(*node.point, *node.prev->point, *node.prev->prev->point) == CW) { // Concave FillLeftConcaveEdgeEvent(tcx, edge, node); } else { // Convex FillLeftConvexEdgeEvent(tcx, edge, node); // Retry this one FillLeftBelowEdgeEvent(tcx, edge, node); } } } void Sweep::FillLeftConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { // Next concave or convex? if (Orient2d(*node.prev->point, *node.prev->prev->point, *node.prev->prev->prev->point) == CW) { // Concave FillLeftConcaveEdgeEvent(tcx, edge, *node.prev); } else{ // Convex // Next above or below edge? if (Orient2d(*edge->q, *node.prev->prev->point, *edge->p) == CW) { // Below FillLeftConvexEdgeEvent(tcx, edge, *node.prev); } else{ // Above } } } void Sweep::FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { Fill(tcx, *node.prev); if (node.prev->point != edge->p) { // Next above or below edge? if (Orient2d(*edge->q, *node.prev->point, *edge->p) == CW) { // Below if (Orient2d(*node.point, *node.prev->point, *node.prev->prev->point) == CW) { // Next is concave FillLeftConcaveEdgeEvent(tcx, edge, node); } else{ // Next is convex } } } } void Sweep::FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p) { Triangle& ot = t->NeighborAcross(p); Point& op = *ot.OppositePoint(*t, p); if (&ot == NULL) { // If we want to integrate the fillEdgeEvent do it here // With current implementation we should never get here //throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); assert(0); } if (InScanArea(p, *t->PointCCW(p), *t->PointCW(p), op)) { // Lets rotate shared edge one vertex CW RotateTrianglePair(*t, p, ot, op); tcx.MapTriangleToNodes(*t); tcx.MapTriangleToNodes(ot); if (p == eq && op == ep) { if (eq == *tcx.edge_event.constrained_edge->q && ep == *tcx.edge_event.constrained_edge->p) { t->MarkConstrainedEdge(&ep, &eq); ot.MarkConstrainedEdge(&ep, &eq); Legalize(tcx, *t); Legalize(tcx, ot); } else { // XXX: I think one of the triangles should be legalized here? } } else { Orientation o = Orient2d(eq, op, ep); t = &NextFlipTriangle(tcx, (int)o, *t, ot, p, op); FlipEdgeEvent(tcx, ep, eq, t, p); } } else { Point& newP = NextFlipPoint(ep, eq, ot, op); FlipScanEdgeEvent(tcx, ep, eq, *t, ot, newP); EdgeEvent(tcx, ep, eq, t, p); } } Triangle& Sweep::NextFlipTriangle(SweepContext& tcx, int o, Triangle& t, Triangle& ot, Point& p, Point& op) { if (o == CCW) { // ot is not crossing edge after flip int edge_index = ot.EdgeIndex(&p, &op); ot.delaunay_edge[edge_index] = true; Legalize(tcx, ot); ot.ClearDelunayEdges(); return t; } // t is not crossing edge after flip int edge_index = t.EdgeIndex(&p, &op); t.delaunay_edge[edge_index] = true; Legalize(tcx, t); t.ClearDelunayEdges(); return ot; } Point& Sweep::NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op) { Orientation o2d = Orient2d(eq, op, ep); if (o2d == CW) { // Right return *ot.PointCCW(op); } else if (o2d == CCW) { // Left return *ot.PointCW(op); } throw std::runtime_error("[Unsupported] Opposing point on constrained edge"); } void Sweep::FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, Triangle& t, Point& p) { Triangle& ot = t.NeighborAcross(p); Point& op = *ot.OppositePoint(t, p); if (&t.NeighborAcross(p) == NULL) { // If we want to integrate the fillEdgeEvent do it here // With current implementation we should never get here //throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); assert(0); } if (InScanArea(eq, *flip_triangle.PointCCW(eq), *flip_triangle.PointCW(eq), op)) { // flip with new edge op->eq FlipEdgeEvent(tcx, eq, op, &ot, op); // TODO: Actually I just figured out that it should be possible to // improve this by getting the next ot and op before the the above // flip and continue the flipScanEdgeEvent here // set new ot and op here and loop back to inScanArea test // also need to set a new flip_triangle first // Turns out at first glance that this is somewhat complicated // so it will have to wait. } else{ Point& newP = NextFlipPoint(ep, eq, ot, op); FlipScanEdgeEvent(tcx, ep, eq, flip_triangle, ot, newP); } } Sweep::~Sweep() { // Clean up memory for(size_t i = 0; i < nodes_.size(); i++) { delete nodes_[i]; } } } Slic3r-1.2.9/xs/src/poly2tri/sweep/sweep.h000066400000000000000000000210611254023100400202750ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * Sweep-line, Constrained Delauney Triangulation (CDT) See: Domiter, V. and * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', * International Journal of Geographical Information Science * * "FlipScan" Constrained Edge Algorithm invented by Thomas ?hl?n, thahlen@gmail.com */ #ifndef SWEEP_H #define SWEEP_H #include namespace p2t { class SweepContext; struct Node; struct Point; struct Edge; class Triangle; class Sweep { public: /** * Triangulate * * @param tcx */ void Triangulate(SweepContext& tcx); /** * Destructor - clean up memory */ ~Sweep(); private: /** * Start sweeping the Y-sorted point set from bottom to top * * @param tcx */ void SweepPoints(SweepContext& tcx); /** * Find closes node to the left of the new point and * create a new triangle. If needed new holes and basins * will be filled to. * * @param tcx * @param point * @return */ Node& PointEvent(SweepContext& tcx, Point& point); /** * * * @param tcx * @param edge * @param node */ void EdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point); /** * Creates a new front triangle and legalize it * * @param tcx * @param point * @param node * @return */ Node& NewFrontTriangle(SweepContext& tcx, Point& point, Node& node); /** * Adds a triangle to the advancing front to fill a hole. * @param tcx * @param node - middle node, that is the bottom of the hole */ void Fill(SweepContext& tcx, Node& node); /** * Returns true if triangle was legalized */ bool Legalize(SweepContext& tcx, Triangle& t); /** * Requirement:
* 1. a,b and c form a triangle.
* 2. a and d is know to be on opposite side of bc
*
   *                a
   *                +
   *               / \
   *              /   \
   *            b/     \c
   *            +-------+
   *           /    d    \
   *          /           \
   * 
* Fact: d has to be in area B to have a chance to be inside the circle formed by * a,b and c
* d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW
* This preknowledge gives us a way to optimize the incircle test * @param a - triangle point, opposite d * @param b - triangle point * @param c - triangle point * @param d - point opposite a * @return true if d is inside circle, false if on circle edge */ bool Incircle(const Point& pa, const Point& pb, const Point& pc, const Point& pd) const; /** * Rotates a triangle pair one vertex CW *
   *       n2                    n2
   *  P +-----+             P +-----+
   *    | t  /|               |\  t |
   *    |   / |               | \   |
   *  n1|  /  |n3           n1|  \  |n3
   *    | /   |    after CW   |   \ |
   *    |/ oT |               | oT \|
   *    +-----+ oP            +-----+
   *       n4                    n4
   * 
*/ void RotateTrianglePair(Triangle& t, Point& p, Triangle& ot, Point& op) const; /** * Fills holes in the Advancing Front * * * @param tcx * @param n */ void FillAdvancingFront(SweepContext& tcx, Node& n); // Decision-making about when to Fill hole. // Contributed by ToolmakerSteve2 bool LargeHole_DontFill(const Node* node) const; bool AngleExceeds90Degrees(const Point* origin, const Point* pa, const Point* pb) const; bool AngleExceedsPlus90DegreesOrIsNegative(const Point* origin, const Point* pa, const Point* pb) const; double Angle(const Point* origin, const Point* pa, const Point* pb) const; /** * * @param node - middle node * @return the angle between 3 front nodes */ double HoleAngle(const Node& node) const; /** * The basin angle is decided against the horizontal line [1,0] */ double BasinAngle(const Node& node) const; /** * Fills a basin that has formed on the Advancing Front to the right * of given node.
* First we decide a left,bottom and right node that forms the * boundaries of the basin. Then we do a reqursive fill. * * @param tcx * @param node - starting node, this or next node will be left node */ void FillBasin(SweepContext& tcx, Node& node); /** * Recursive algorithm to fill a Basin with triangles * * @param tcx * @param node - bottom_node * @param cnt - counter used to alternate on even and odd numbers */ void FillBasinReq(SweepContext& tcx, Node* node); bool IsShallow(SweepContext& tcx, Node& node); bool IsEdgeSideOfTriangle(Triangle& triangle, Point& ep, Point& eq); void FillEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void FillRightAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void FillRightBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillLeftAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void FillLeftBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillLeftConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p); /** * After a flip we have two triangles and know that only one will still be * intersecting the edge. So decide which to contiune with and legalize the other * * @param tcx * @param o - should be the result of an orient2d( eq, op, ep ) * @param t - triangle 1 * @param ot - triangle 2 * @param p - a point shared by both triangles * @param op - another point shared by both triangles * @return returns the triangle still intersecting the edge */ Triangle& NextFlipTriangle(SweepContext& tcx, int o, Triangle& t, Triangle& ot, Point& p, Point& op); /** * When we need to traverse from one triangle to the next we need * the point in current triangle that is the opposite point to the next * triangle. * * @param ep * @param eq * @param ot * @param op * @return */ Point& NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op); /** * Scan part of the FlipScan algorithm
* When a triangle pair isn't flippable we will scan for the next * point that is inside the flip triangle scan area. When found * we generate a new flipEdgeEvent * * @param tcx * @param ep - last point on the edge we are traversing * @param eq - first point on the edge we are traversing * @param flipTriangle - the current triangle sharing the point eq with edge * @param t * @param p */ void FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, Triangle& t, Point& p); void FinalizationPolygon(SweepContext& tcx); std::vector nodes_; }; } #endifSlic3r-1.2.9/xs/src/poly2tri/sweep/sweep_context.cc000066400000000000000000000125261254023100400222050ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sweep_context.h" #include #include "advancing_front.h" namespace p2t { SweepContext::SweepContext(const std::vector& polyline) : points_(polyline), front_(0), head_(0), tail_(0), af_head_(0), af_middle_(0), af_tail_(0) { InitEdges(points_); } void SweepContext::AddHole(const std::vector& polyline) { InitEdges(polyline); for(unsigned int i = 0; i < polyline.size(); i++) { points_.push_back(polyline[i]); } } void SweepContext::AddPoint(Point* point) { points_.push_back(point); } std::vector &SweepContext::GetTriangles() { return triangles_; } std::list &SweepContext::GetMap() { return map_; } void SweepContext::InitTriangulation() { double xmax(points_[0]->x), xmin(points_[0]->x); double ymax(points_[0]->y), ymin(points_[0]->y); // Calculate bounds. for (unsigned int i = 0; i < points_.size(); i++) { Point& p = *points_[i]; if (p.x > xmax) xmax = p.x; if (p.x < xmin) xmin = p.x; if (p.y > ymax) ymax = p.y; if (p.y < ymin) ymin = p.y; } double dx = kAlpha * (xmax - xmin); double dy = kAlpha * (ymax - ymin); head_ = new Point(xmax + dx, ymin - dy); tail_ = new Point(xmin - dx, ymin - dy); // Sort points along y-axis std::sort(points_.begin(), points_.end(), cmp); } void SweepContext::InitEdges(const std::vector& polyline) { size_t num_points = polyline.size(); for (size_t i = 0; i < num_points; i++) { size_t j = i < num_points - 1 ? i + 1 : 0; edge_list.push_back(new Edge(*polyline[i], *polyline[j])); } } Point* SweepContext::GetPoint(size_t index) { return points_[index]; } void SweepContext::AddToMap(Triangle* triangle) { map_.push_back(triangle); } Node& SweepContext::LocateNode(const Point& point) { // TODO implement search tree return *front_->LocateNode(point.x); } void SweepContext::CreateAdvancingFront(const std::vector& nodes) { (void) nodes; // Initial triangle Triangle* triangle = new Triangle(*points_[0], *tail_, *head_); map_.push_back(triangle); af_head_ = new Node(*triangle->GetPoint(1), *triangle); af_middle_ = new Node(*triangle->GetPoint(0), *triangle); af_tail_ = new Node(*triangle->GetPoint(2)); front_ = new AdvancingFront(*af_head_, *af_tail_); // TODO: More intuitive if head is middles next and not previous? // so swap head and tail af_head_->next = af_middle_; af_middle_->next = af_tail_; af_middle_->prev = af_head_; af_tail_->prev = af_middle_; } void SweepContext::RemoveNode(Node* node) { delete node; } void SweepContext::MapTriangleToNodes(Triangle& t) { for (int i = 0; i < 3; i++) { if (!t.GetNeighbor(i)) { Node* n = front_->LocatePoint(t.PointCW(*t.GetPoint(i))); if (n) n->triangle = &t; } } } void SweepContext::RemoveFromMap(Triangle* triangle) { map_.remove(triangle); } void SweepContext::MeshClean(Triangle& triangle) { std::vector triangles; triangles.push_back(&triangle); while(!triangles.empty()){ Triangle *t = triangles.back(); triangles.pop_back(); if (t != NULL && !t->IsInterior()) { t->IsInterior(true); triangles_.push_back(t); for (int i = 0; i < 3; i++) { if (!t->constrained_edge[i]) triangles.push_back(t->GetNeighbor(i)); } } } } SweepContext::~SweepContext() { // Clean up memory delete head_; delete tail_; delete front_; delete af_head_; delete af_middle_; delete af_tail_; typedef std::list type_list; for(type_list::iterator iter = map_.begin(); iter != map_.end(); ++iter) { Triangle* ptr = *iter; delete ptr; } for(unsigned int i = 0; i < edge_list.size(); i++) { delete edge_list[i]; } } } Slic3r-1.2.9/xs/src/poly2tri/sweep/sweep_context.h000066400000000000000000000101201254023100400220330ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef SWEEP_CONTEXT_H #define SWEEP_CONTEXT_H #include #include #include namespace p2t { // Inital triangle factor, seed triangle will extend 30% of // PointSet width to both left and right. const double kAlpha = 0.3; struct Point; class Triangle; struct Node; struct Edge; class AdvancingFront; class SweepContext { public: /// Constructor SweepContext(const std::vector& polyline); /// Destructor ~SweepContext(); void set_head(Point* p1); Point* head() const; void set_tail(Point* p1); Point* tail() const; size_t point_count() const; Node& LocateNode(const Point& point); void RemoveNode(Node* node); void CreateAdvancingFront(const std::vector& nodes); /// Try to map a node to all sides of this triangle that don't have a neighbor void MapTriangleToNodes(Triangle& t); void AddToMap(Triangle* triangle); Point* GetPoint(size_t index); Point* GetPoints(); void RemoveFromMap(Triangle* triangle); void AddHole(const std::vector& polyline); void AddPoint(Point* point); AdvancingFront* front() const; void MeshClean(Triangle& triangle); std::vector &GetTriangles(); std::list &GetMap(); std::vector edge_list; struct Basin { Node* left_node; Node* bottom_node; Node* right_node; double width; bool left_highest; Basin() : left_node(NULL), bottom_node(NULL), right_node(NULL), width(0.0), left_highest(false) { } void Clear() { left_node = NULL; bottom_node = NULL; right_node = NULL; width = 0.0; left_highest = false; } }; struct EdgeEvent { Edge* constrained_edge; bool right; EdgeEvent() : constrained_edge(NULL), right(false) { } }; Basin basin; EdgeEvent edge_event; private: friend class Sweep; std::vector triangles_; std::list map_; std::vector points_; // Advancing front AdvancingFront* front_; // head point used with advancing front Point* head_; // tail point used with advancing front Point* tail_; Node *af_head_, *af_middle_, *af_tail_; void InitTriangulation(); void InitEdges(const std::vector& polyline); }; inline AdvancingFront* SweepContext::front() const { return front_; } inline size_t SweepContext::point_count() const { return points_.size(); } inline void SweepContext::set_head(Point* p1) { head_ = p1; } inline Point* SweepContext::head() const { return head_; } inline void SweepContext::set_tail(Point* p1) { tail_ = p1; } inline Point* SweepContext::tail() const { return tail_; } } #endif Slic3r-1.2.9/xs/src/polypartition.cpp000066400000000000000000001262401254023100400175200ustar00rootroot00000000000000//Copyright (C) 2011 by Ivan Fratric // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. #include #include #include #include #include #include using namespace std; #include "polypartition.h" #define TPPL_VERTEXTYPE_REGULAR 0 #define TPPL_VERTEXTYPE_START 1 #define TPPL_VERTEXTYPE_END 2 #define TPPL_VERTEXTYPE_SPLIT 3 #define TPPL_VERTEXTYPE_MERGE 4 TPPLPoly::TPPLPoly() { hole = false; numpoints = 0; points = NULL; } TPPLPoly::~TPPLPoly() { if(points) delete [] points; } void TPPLPoly::Clear() { if(points) delete [] points; hole = false; numpoints = 0; points = NULL; } void TPPLPoly::Init(long numpoints) { Clear(); this->numpoints = numpoints; points = new TPPLPoint[numpoints]; } void TPPLPoly::Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3) { Init(3); points[0] = p1; points[1] = p2; points[2] = p3; } TPPLPoly::TPPLPoly(const TPPLPoly &src) { hole = src.hole; numpoints = src.numpoints; points = new TPPLPoint[numpoints]; memcpy(points, src.points, numpoints*sizeof(TPPLPoint)); } TPPLPoly& TPPLPoly::operator=(const TPPLPoly &src) { Clear(); hole = src.hole; numpoints = src.numpoints; points = new TPPLPoint[numpoints]; memcpy(points, src.points, numpoints*sizeof(TPPLPoint)); return *this; } int TPPLPoly::GetOrientation() { long i1,i2; tppl_float area = 0; for(i1=0; i10) return TPPL_CCW; if(area<0) return TPPL_CW; return 0; } void TPPLPoly::SetOrientation(int orientation) { int polyorientation = GetOrientation(); if(polyorientation&&(polyorientation!=orientation)) { Invert(); } } void TPPLPoly::Invert() { long i; TPPLPoint *invpoints; invpoints = new TPPLPoint[numpoints]; for(i=0;i0) return 0; if(dot21*dot22>0) return 0; return 1; } //removes holes from inpolys by merging them with non-holes int TPPLPartition::RemoveHoles(list *inpolys, list *outpolys) { list polys; list::iterator holeiter,polyiter,iter,iter2; long i,i2,holepointindex,polypointindex; TPPLPoint holepoint,polypoint,bestpolypoint; TPPLPoint linep1,linep2; TPPLPoint v1,v2; TPPLPoly newpoly; bool hasholes; bool pointvisible; bool pointfound; //check for trivial case (no holes) hasholes = false; for(iter = inpolys->begin(); iter!=inpolys->end(); iter++) { if(iter->IsHole()) { hasholes = true; break; } } if(!hasholes) { for(iter = inpolys->begin(); iter!=inpolys->end(); iter++) { outpolys->push_back(*iter); } return 1; } polys = *inpolys; while(1) { //find the hole point with the largest x hasholes = false; for(iter = polys.begin(); iter!=polys.end(); iter++) { if(!iter->IsHole()) continue; if(!hasholes) { hasholes = true; holeiter = iter; holepointindex = 0; } for(i=0; i < iter->GetNumPoints(); i++) { if(iter->GetPoint(i).x > holeiter->GetPoint(holepointindex).x) { holeiter = iter; holepointindex = i; } } } if(!hasholes) break; holepoint = holeiter->GetPoint(holepointindex); pointfound = false; for(iter = polys.begin(); iter!=polys.end(); iter++) { if(iter->IsHole()) continue; for(i=0; i < iter->GetNumPoints(); i++) { if(iter->GetPoint(i).x <= holepoint.x) continue; if(!InCone(iter->GetPoint((i+iter->GetNumPoints()-1)%(iter->GetNumPoints())), iter->GetPoint(i), iter->GetPoint((i+1)%(iter->GetNumPoints())), holepoint)) continue; polypoint = iter->GetPoint(i); if(pointfound) { v1 = Normalize(polypoint-holepoint); v2 = Normalize(bestpolypoint-holepoint); if(v2.x > v1.x) continue; } pointvisible = true; for(iter2 = polys.begin(); iter2!=polys.end(); iter2++) { if(iter2->IsHole()) continue; for(i2=0; i2 < iter2->GetNumPoints(); i2++) { linep1 = iter2->GetPoint(i2); linep2 = iter2->GetPoint((i2+1)%(iter2->GetNumPoints())); if(Intersects(holepoint,polypoint,linep1,linep2)) { pointvisible = false; break; } } if(!pointvisible) break; } if(pointvisible) { pointfound = true; bestpolypoint = polypoint; polyiter = iter; polypointindex = i; } } } if(!pointfound) return 0; newpoly.Init(holeiter->GetNumPoints() + polyiter->GetNumPoints() + 2); i2 = 0; for(i=0;i<=polypointindex;i++) { newpoly[i2] = polyiter->GetPoint(i); i2++; } for(i=0;i<=holeiter->GetNumPoints();i++) { newpoly[i2] = holeiter->GetPoint((i+holepointindex)%holeiter->GetNumPoints()); i2++; } for(i=polypointindex;iGetNumPoints();i++) { newpoly[i2] = polyiter->GetPoint(i); i2++; } polys.erase(holeiter); polys.erase(polyiter); polys.push_back(newpoly); } for(iter = polys.begin(); iter!=polys.end(); iter++) { outpolys->push_back(*iter); } return 1; } bool TPPLPartition::IsConvex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3) { tppl_float tmp; tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); if(tmp>0) return 1; else return 0; } bool TPPLPartition::IsReflex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3) { tppl_float tmp; tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); if(tmp<0) return 1; else return 0; } bool TPPLPartition::IsInside(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3, TPPLPoint &p) { if(IsConvex(p1,p,p2)) return false; if(IsConvex(p2,p,p3)) return false; if(IsConvex(p3,p,p1)) return false; return true; } bool TPPLPartition::InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p) { bool convex; convex = IsConvex(p1,p2,p3); if(convex) { if(!IsConvex(p1,p2,p)) return false; if(!IsConvex(p2,p3,p)) return false; return true; } else { if(IsConvex(p1,p2,p)) return true; if(IsConvex(p2,p3,p)) return true; return false; } } bool TPPLPartition::InCone(PartitionVertex *v, TPPLPoint &p) { TPPLPoint p1,p2,p3; p1 = v->previous->p; p2 = v->p; p3 = v->next->p; return InCone(p1,p2,p3,p); } void TPPLPartition::UpdateVertexReflexity(PartitionVertex *v) { PartitionVertex *v1,*v3; v1 = v->previous; v3 = v->next; v->isConvex = !IsReflex(v1->p,v->p,v3->p); } void TPPLPartition::UpdateVertex(PartitionVertex *v, PartitionVertex *vertices, long numvertices) { long i; PartitionVertex *v1,*v3; TPPLPoint vec1,vec3; v1 = v->previous; v3 = v->next; v->isConvex = IsConvex(v1->p,v->p,v3->p); vec1 = Normalize(v1->p - v->p); vec3 = Normalize(v3->p - v->p); v->angle = vec1.x*vec3.x + vec1.y*vec3.y; if(v->isConvex) { v->isEar = true; for(i=0;ip.x)&&(vertices[i].p.y==v->p.y)) continue; if((vertices[i].p.x==v1->p.x)&&(vertices[i].p.y==v1->p.y)) continue; if((vertices[i].p.x==v3->p.x)&&(vertices[i].p.y==v3->p.y)) continue; if(IsInside(v1->p,v->p,v3->p,vertices[i].p)) { v->isEar = false; break; } } } else { v->isEar = false; } } //triangulation by ear removal int TPPLPartition::Triangulate_EC(TPPLPoly *poly, list *triangles) { long numvertices; PartitionVertex *vertices; PartitionVertex *ear; TPPLPoly triangle; long i,j; bool earfound; if(poly->GetNumPoints() < 3) return 0; if(poly->GetNumPoints() == 3) { triangles->push_back(*poly); return 1; } numvertices = poly->GetNumPoints(); vertices = new PartitionVertex[numvertices]; for(i=0;iGetPoint(i); if(i==(numvertices-1)) vertices[i].next=&(vertices[0]); else vertices[i].next=&(vertices[i+1]); if(i==0) vertices[i].previous = &(vertices[numvertices-1]); else vertices[i].previous = &(vertices[i-1]); } for(i=0;i ear->angle) { ear = &(vertices[j]); } } } if(!earfound) { delete [] vertices; return 0; } triangle.Triangle(ear->previous->p,ear->p,ear->next->p); triangles->push_back(triangle); ear->isActive = false; ear->previous->next = ear->next; ear->next->previous = ear->previous; if(i==numvertices-4) break; UpdateVertex(ear->previous,vertices,numvertices); UpdateVertex(ear->next,vertices,numvertices); } for(i=0;ip,vertices[i].p,vertices[i].next->p); triangles->push_back(triangle); break; } } delete [] vertices; return 1; } int TPPLPartition::Triangulate_EC(list *inpolys, list *triangles) { list outpolys; list::iterator iter; if(!RemoveHoles(inpolys,&outpolys)) return 0; for(iter=outpolys.begin();iter!=outpolys.end();iter++) { if(!Triangulate_EC(&(*iter),triangles)) return 0; } return 1; } int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, list *parts) { list triangles; list::iterator iter1,iter2; TPPLPoly *poly1,*poly2; TPPLPoly newpoly; TPPLPoint d1,d2,p1,p2,p3; long i11,i12,i21,i22,i13,i23,j,k; bool isdiagonal; long numreflex; //check if the poly is already convex numreflex = 0; for(i11=0;i11GetNumPoints();i11++) { if(i11==0) i12 = poly->GetNumPoints()-1; else i12=i11-1; if(i11==(poly->GetNumPoints()-1)) i13=0; else i13=i11+1; if(IsReflex(poly->GetPoint(i12),poly->GetPoint(i11),poly->GetPoint(i13))) { numreflex = 1; break; } } if(numreflex == 0) { parts->push_back(*poly); return 1; } if(!Triangulate_EC(poly,&triangles)) return 0; for(iter1 = triangles.begin(); iter1 != triangles.end(); iter1++) { poly1 = &(*iter1); for(i11=0;i11GetNumPoints();i11++) { d1 = poly1->GetPoint(i11); i12 = (i11+1)%(poly1->GetNumPoints()); d2 = poly1->GetPoint(i12); isdiagonal = false; for(iter2 = iter1; iter2 != triangles.end(); iter2++) { if(iter1 == iter2) continue; poly2 = &(*iter2); for(i21=0;i21GetNumPoints();i21++) { if((d2.x != poly2->GetPoint(i21).x)||(d2.y != poly2->GetPoint(i21).y)) continue; i22 = (i21+1)%(poly2->GetNumPoints()); if((d1.x != poly2->GetPoint(i22).x)||(d1.y != poly2->GetPoint(i22).y)) continue; isdiagonal = true; break; } if(isdiagonal) break; } if(!isdiagonal) continue; p2 = poly1->GetPoint(i11); if(i11 == 0) i13 = poly1->GetNumPoints()-1; else i13 = i11-1; p1 = poly1->GetPoint(i13); if(i22 == (poly2->GetNumPoints()-1)) i23 = 0; else i23 = i22+1; p3 = poly2->GetPoint(i23); if(!IsConvex(p1,p2,p3)) continue; p2 = poly1->GetPoint(i12); if(i12 == (poly1->GetNumPoints()-1)) i13 = 0; else i13 = i12+1; p3 = poly1->GetPoint(i13); if(i21 == 0) i23 = poly2->GetNumPoints()-1; else i23 = i21-1; p1 = poly2->GetPoint(i23); if(!IsConvex(p1,p2,p3)) continue; newpoly.Init(poly1->GetNumPoints()+poly2->GetNumPoints()-2); k = 0; for(j=i12;j!=i11;j=(j+1)%(poly1->GetNumPoints())) { newpoly[k] = poly1->GetPoint(j); k++; } for(j=i22;j!=i21;j=(j+1)%(poly2->GetNumPoints())) { newpoly[k] = poly2->GetPoint(j); k++; } triangles.erase(iter2); *iter1 = newpoly; poly1 = &(*iter1); i11 = -1; continue; } } for(iter1 = triangles.begin(); iter1 != triangles.end(); iter1++) { parts->push_back(*iter1); } return 1; } int TPPLPartition::ConvexPartition_HM(list *inpolys, list *parts) { list outpolys; list::iterator iter; if(!RemoveHoles(inpolys,&outpolys)) return 0; for(iter=outpolys.begin();iter!=outpolys.end();iter++) { if(!ConvexPartition_HM(&(*iter),parts)) return 0; } return 1; } //minimum-weight polygon triangulation by dynamic programming //O(n^3) time complexity //O(n^2) space complexity int TPPLPartition::Triangulate_OPT(TPPLPoly *poly, list *triangles) { long i,j,k,gap,n; DPState **dpstates; TPPLPoint p1,p2,p3,p4; long bestvertex; tppl_float weight,minweight,d1,d2; Diagonal diagonal,newdiagonal; list diagonals; TPPLPoly triangle; int ret = 1; n = poly->GetNumPoints(); dpstates = new DPState *[n]; for(i=1;iGetPoint(i); for(j=i+1;jGetPoint(j); //visibility check if(i==0) p3 = poly->GetPoint(n-1); else p3 = poly->GetPoint(i-1); if(i==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(i+1); if(!InCone(p3,p1,p4,p2)) { dpstates[j][i].visible = false; continue; } if(j==0) p3 = poly->GetPoint(n-1); else p3 = poly->GetPoint(j-1); if(j==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(j+1); if(!InCone(p3,p2,p4,p1)) { dpstates[j][i].visible = false; continue; } for(k=0;kGetPoint(k); if(k==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(k+1); if(Intersects(p1,p2,p3,p4)) { dpstates[j][i].visible = false; break; } } } } } dpstates[n-1][0].visible = true; dpstates[n-1][0].weight = 0; dpstates[n-1][0].bestvertex = -1; for(gap = 2; gapGetPoint(i),poly->GetPoint(k)); if(j<=(k+1)) d2=0; else d2 = Distance(poly->GetPoint(k),poly->GetPoint(j)); weight = dpstates[k][i].weight + dpstates[j][k].weight + d1 + d2; if((bestvertex == -1)||(weightGetPoint(diagonal.index1),poly->GetPoint(bestvertex),poly->GetPoint(diagonal.index2)); triangles->push_back(triangle); if(bestvertex > (diagonal.index1+1)) { newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = bestvertex; diagonals.push_back(newdiagonal); } if(diagonal.index2 > (bestvertex+1)) { newdiagonal.index1 = bestvertex; newdiagonal.index2 = diagonal.index2; diagonals.push_back(newdiagonal); } } for(i=1;i *pairs; long w2; w2 = dpstates[a][b].weight; if(w>w2) return; pairs = &(dpstates[a][b].pairs); newdiagonal.index1 = i; newdiagonal.index2 = j; if(wclear(); pairs->push_front(newdiagonal); dpstates[a][b].weight = w; } else { if((!pairs->empty())&&(i <= pairs->begin()->index1)) return; while((!pairs->empty())&&(pairs->begin()->index2 >= j)) pairs->pop_front(); pairs->push_front(newdiagonal); } } void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { list *pairs; list::iterator iter,lastiter; long top; long w; if(!dpstates[i][j].visible) return; top = j; w = dpstates[i][j].weight; if(k-j > 1) { if (!dpstates[j][k].visible) return; w += dpstates[j][k].weight + 1; } if(j-i > 1) { pairs = &(dpstates[i][j].pairs); iter = pairs->end(); lastiter = pairs->end(); while(iter!=pairs->begin()) { iter--; if(!IsReflex(vertices[iter->index2].p,vertices[j].p,vertices[k].p)) lastiter = iter; else break; } if(lastiter == pairs->end()) w++; else { if(IsReflex(vertices[k].p,vertices[i].p,vertices[lastiter->index1].p)) w++; else top = lastiter->index1; } } UpdateState(i,k,w,top,j,dpstates); } void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { list *pairs; list::iterator iter,lastiter; long top; long w; if(!dpstates[j][k].visible) return; top = j; w = dpstates[j][k].weight; if (j-i > 1) { if (!dpstates[i][j].visible) return; w += dpstates[i][j].weight + 1; } if (k-j > 1) { pairs = &(dpstates[j][k].pairs); iter = pairs->begin(); if((!pairs->empty())&&(!IsReflex(vertices[i].p,vertices[j].p,vertices[iter->index1].p))) { lastiter = iter; while(iter!=pairs->end()) { if(!IsReflex(vertices[i].p,vertices[j].p,vertices[iter->index1].p)) { lastiter = iter; iter++; } else break; } if(IsReflex(vertices[lastiter->index2].p,vertices[k].p,vertices[i].p)) w++; else top = lastiter->index2; } else w++; } UpdateState(i,k,w,j,top,dpstates); } int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, list *parts) { TPPLPoint p1,p2,p3,p4; PartitionVertex *vertices; DPState2 **dpstates; long i,j,k,n,gap; list diagonals,diagonals2; Diagonal diagonal,newdiagonal; list *pairs,*pairs2; list::iterator iter,iter2; int ret; TPPLPoly newpoly; list indices; list::iterator iiter; bool ijreal,jkreal; n = poly->GetNumPoints(); vertices = new PartitionVertex[n]; dpstates = new DPState2 *[n]; for(i=0;iGetPoint(i); vertices[i].isActive = true; if(i==0) vertices[i].previous = &(vertices[n-1]); else vertices[i].previous = &(vertices[i-1]); if(i==(poly->GetNumPoints()-1)) vertices[i].next = &(vertices[0]); else vertices[i].next = &(vertices[i+1]); } for(i=1;iGetPoint(i); for(j=i+1;jGetPoint(j); //visibility check if(!InCone(&vertices[i],p2)) { dpstates[i][j].visible = false; continue; } if(!InCone(&vertices[j],p1)) { dpstates[i][j].visible = false; continue; } for(k=0;kGetPoint(k); if(k==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(k+1); if(Intersects(p1,p2,p3,p4)) { dpstates[i][j].visible = false; break; } } } } } for(i=0;i<(n-2);i++) { j = i+2; if(dpstates[i][j].visible) { dpstates[i][j].weight = 0; newdiagonal.index1 = i+1; newdiagonal.index2 = i+1; dpstates[i][j].pairs.push_back(newdiagonal); } } dpstates[0][n-1].visible = true; vertices[0].isConvex = false; //by convention for(gap=3; gapempty()) { ret = 0; break; } if(!vertices[diagonal.index1].isConvex) { iter = pairs->end(); iter--; j = iter->index2; newdiagonal.index1 = j; newdiagonal.index2 = diagonal.index2; diagonals.push_front(newdiagonal); if((j - diagonal.index1)>1) { if(iter->index1 != iter->index2) { pairs2 = &(dpstates[diagonal.index1][j].pairs); while(1) { if(pairs2->empty()) { ret = 0; break; } iter2 = pairs2->end(); iter2--; if(iter->index1 != iter2->index1) pairs2->pop_back(); else break; } if(ret == 0) break; } newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = j; diagonals.push_front(newdiagonal); } } else { iter = pairs->begin(); j = iter->index1; newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = j; diagonals.push_front(newdiagonal); if((diagonal.index2 - j) > 1) { if(iter->index1 != iter->index2) { pairs2 = &(dpstates[j][diagonal.index2].pairs); while(1) { if(pairs2->empty()) { ret = 0; break; } iter2 = pairs2->begin(); if(iter->index2 != iter2->index2) pairs2->pop_front(); else break; } if(ret == 0) break; } newdiagonal.index1 = j; newdiagonal.index2 = diagonal.index2; diagonals.push_front(newdiagonal); } } } if(ret == 0) { for(i=0;iend(); iter--; j = iter->index2; if(iter->index1 != iter->index2) ijreal = false; } else { iter = pairs->begin(); j = iter->index1; if(iter->index1 != iter->index2) jkreal = false; } newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = j; if(ijreal) { diagonals.push_back(newdiagonal); } else { diagonals2.push_back(newdiagonal); } newdiagonal.index1 = j; newdiagonal.index2 = diagonal.index2; if(jkreal) { diagonals.push_back(newdiagonal); } else { diagonals2.push_back(newdiagonal); } indices.push_back(j); } indices.sort(); newpoly.Init((long)indices.size()); k=0; for(iiter = indices.begin();iiter!=indices.end();iiter++) { newpoly[k] = vertices[*iiter].p; k++; } parts->push_back(newpoly); } for(i=0;i *inpolys, list *monotonePolys) { list::iterator iter; MonotoneVertex *vertices; long i,numvertices,vindex,vindex2,newnumvertices,maxnumvertices; long polystartindex, polyendindex; TPPLPoly *poly; MonotoneVertex *v,*v2,*vprev,*vnext; ScanLineEdge newedge; bool error = false; numvertices = 0; for(iter = inpolys->begin(); iter != inpolys->end(); iter++) { numvertices += iter->GetNumPoints(); } maxnumvertices = numvertices*3; vertices = new MonotoneVertex[maxnumvertices]; newnumvertices = numvertices; polystartindex = 0; for(iter = inpolys->begin(); iter != inpolys->end(); iter++) { poly = &(*iter); polyendindex = polystartindex + poly->GetNumPoints()-1; for(i=0;iGetNumPoints();i++) { vertices[i+polystartindex].p = poly->GetPoint(i); if(i==0) vertices[i+polystartindex].previous = polyendindex; else vertices[i+polystartindex].previous = i+polystartindex-1; if(i==(poly->GetNumPoints()-1)) vertices[i+polystartindex].next = polystartindex; else vertices[i+polystartindex].next = i+polystartindex+1; } polystartindex = polyendindex+1; } //construct the priority queue long *priority = new long [numvertices]; for(i=0;iprevious]); vnext = &(vertices[v->next]); if(Below(vprev->p,v->p)&&Below(vnext->p,v->p)) { if(IsConvex(vnext->p,vprev->p,v->p)) { vertextypes[i] = TPPL_VERTEXTYPE_START; } else { vertextypes[i] = TPPL_VERTEXTYPE_SPLIT; } } else if(Below(v->p,vprev->p)&&Below(v->p,vnext->p)) { if(IsConvex(vnext->p,vprev->p,v->p)) { vertextypes[i] = TPPL_VERTEXTYPE_END; } else { vertextypes[i] = TPPL_VERTEXTYPE_MERGE; } } else { vertextypes[i] = TPPL_VERTEXTYPE_REGULAR; } } //helpers long *helpers = new long[maxnumvertices]; //binary search tree that holds edges intersecting the scanline //note that while set doesn't actually have to be implemented as a tree //complexity requirements for operations are the same as for the balanced binary search tree set edgeTree; //store iterators to the edge tree elements //this makes deleting existing edges much faster set::iterator *edgeTreeIterators,edgeIter; edgeTreeIterators = new set::iterator[maxnumvertices]; pair::iterator,bool> edgeTreeRet; //for each vertex for(i=0;ip; newedge.p2 = vertices[v->next].p; newedge.index = vindex; edgeTreeRet = edgeTree.insert(newedge); edgeTreeIterators[vindex] = edgeTreeRet.first; helpers[vindex] = vindex; break; case TPPL_VERTEXTYPE_END: //if helper(ei-1) is a merge vertex if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(ei-1) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[v->previous]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[v->previous]]; helpers[newnumvertices-1] = helpers[helpers[v->previous]]; } //Delete ei-1 from T edgeTree.erase(edgeTreeIterators[v->previous]); break; case TPPL_VERTEXTYPE_SPLIT: //Search in T to find the edge e j directly left of vi. newedge.p1 = v->p; newedge.p2 = v->p; edgeIter = edgeTree.lower_bound(newedge); if(edgeIter == edgeTree.begin()) { error = true; break; } edgeIter--; //Insert the diagonal connecting vi to helper(ej) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[edgeIter->index]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[edgeIter->index]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[edgeIter->index]]; helpers[newnumvertices-1] = helpers[helpers[edgeIter->index]]; vindex2 = newnumvertices-2; v2 = &(vertices[vindex2]); //helper(e j)�vi helpers[edgeIter->index] = vindex; //Insert ei in T and set helper(ei) to vi. newedge.p1 = v2->p; newedge.p2 = vertices[v2->next].p; newedge.index = vindex2; edgeTreeRet = edgeTree.insert(newedge); edgeTreeIterators[vindex2] = edgeTreeRet.first; helpers[vindex2] = vindex2; break; case TPPL_VERTEXTYPE_MERGE: //if helper(ei-1) is a merge vertex if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(ei-1) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[v->previous]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[v->previous]]; helpers[newnumvertices-1] = helpers[helpers[v->previous]]; vindex2 = newnumvertices-2; v2 = &(vertices[vindex2]); } //Delete ei-1 from T. edgeTree.erase(edgeTreeIterators[v->previous]); //Search in T to find the edge e j directly left of vi. newedge.p1 = v->p; newedge.p2 = v->p; edgeIter = edgeTree.lower_bound(newedge); if(edgeIter == edgeTree.begin()) { error = true; break; } edgeIter--; //if helper(ej) is a merge vertex if(vertextypes[helpers[edgeIter->index]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(e j) in D. AddDiagonal(vertices,&newnumvertices,vindex2,helpers[edgeIter->index]); vertextypes[newnumvertices-2] = vertextypes[vindex2]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex2]; helpers[newnumvertices-2] = helpers[vindex2]; vertextypes[newnumvertices-1] = vertextypes[helpers[edgeIter->index]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[edgeIter->index]]; helpers[newnumvertices-1] = helpers[helpers[edgeIter->index]]; } //helper(e j)�vi helpers[edgeIter->index] = vindex2; break; case TPPL_VERTEXTYPE_REGULAR: //if the interior of P lies to the right of vi if(Below(v->p,vertices[v->previous].p)) { //if helper(ei-1) is a merge vertex if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(ei-1) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[v->previous]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[v->previous]]; helpers[newnumvertices-1] = helpers[helpers[v->previous]]; vindex2 = newnumvertices-2; v2 = &(vertices[vindex2]); } //Delete ei-1 from T. edgeTree.erase(edgeTreeIterators[v->previous]); //Insert ei in T and set helper(ei) to vi. newedge.p1 = v2->p; newedge.p2 = vertices[v2->next].p; newedge.index = vindex2; edgeTreeRet = edgeTree.insert(newedge); edgeTreeIterators[vindex2] = edgeTreeRet.first; helpers[vindex2] = vindex; } else { //Search in T to find the edge ej directly left of vi. newedge.p1 = v->p; newedge.p2 = v->p; edgeIter = edgeTree.lower_bound(newedge); if(edgeIter == edgeTree.begin()) { error = true; break; } edgeIter--; //if helper(ej) is a merge vertex if(vertextypes[helpers[edgeIter->index]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(e j) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[edgeIter->index]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[edgeIter->index]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[edgeIter->index]]; helpers[newnumvertices-1] = helpers[helpers[edgeIter->index]]; } //helper(e j)�vi helpers[edgeIter->index] = vindex; } break; } if(error) break; } char *used = new char[newnumvertices]; memset(used,0,newnumvertices*sizeof(char)); if(!error) { //return result long size; TPPLPoly mpoly; for(i=0;inext]); size = 1; while(vnext!=v) { vnext = &(vertices[vnext->next]); size++; } mpoly.Init(size); v = &(vertices[i]); mpoly[0] = v->p; vnext = &(vertices[v->next]); size = 1; used[i] = 1; used[v->next] = 1; while(vnext!=v) { mpoly[size] = vnext->p; used[vnext->next] = 1; vnext = &(vertices[vnext->next]); size++; } monotonePolys->push_back(mpoly); } } //cleanup delete [] vertices; delete [] priority; delete [] vertextypes; delete [] edgeTreeIterators; delete [] helpers; delete [] used; if(error) { return 0; } else { return 1; } } //adds a diagonal to the doubly-connected list of vertices void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2) { long newindex1,newindex2; newindex1 = *numvertices; (*numvertices)++; newindex2 = *numvertices; (*numvertices)++; vertices[newindex1].p = vertices[index1].p; vertices[newindex2].p = vertices[index2].p; vertices[newindex2].next = vertices[index2].next; vertices[newindex1].next = vertices[index1].next; vertices[vertices[index2].next].previous = newindex2; vertices[vertices[index1].next].previous = newindex1; vertices[index1].next = newindex2; vertices[newindex2].previous = index1; vertices[index2].next = newindex1; vertices[newindex1].previous = index2; } bool TPPLPartition::Below(TPPLPoint &p1, TPPLPoint &p2) { if(p1.y < p2.y) return true; else if(p1.y == p2.y) { if(p1.x < p2.x) return true; } return false; } //sorts in the falling order of y values, if y is equal, x is used instead bool TPPLPartition::VertexSorter::operator() (long index1, long index2) { if(vertices[index1].p.y > vertices[index2].p.y) return true; else if(vertices[index1].p.y == vertices[index2].p.y) { if(vertices[index1].p.x > vertices[index2].p.x) return true; } return false; } bool TPPLPartition::ScanLineEdge::IsConvex(const TPPLPoint& p1, const TPPLPoint& p2, const TPPLPoint& p3) const { tppl_float tmp; tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); if(tmp>0) return 1; else return 0; } bool TPPLPartition::ScanLineEdge::operator < (const ScanLineEdge & other) const { if(other.p1.y == other.p2.y) { if(p1.y == p2.y) { if(p1.y < other.p1.y) return true; else return false; } if(IsConvex(p1,p2,other.p1)) return true; else return false; } else if(p1.y == p2.y) { if(IsConvex(other.p1,other.p2,p1)) return false; else return true; } else if(p1.y < other.p1.y) { if(IsConvex(other.p1,other.p2,p1)) return false; else return true; } else { if(IsConvex(p1,p2,other.p1)) return true; else return false; } } //triangulates monotone polygon //O(n) time, O(n) space complexity int TPPLPartition::TriangulateMonotone(TPPLPoly *inPoly, list *triangles) { long i,i2,j,topindex,bottomindex,leftindex,rightindex,vindex; TPPLPoint *points; long numpoints; TPPLPoly triangle; numpoints = inPoly->GetNumPoints(); points = inPoly->GetPoints(); //trivial calses if(numpoints < 3) return 0; if(numpoints == 3) { triangles->push_back(*inPoly); } topindex = 0; bottomindex=0; for(i=1;i=numpoints) i2 = 0; if(!Below(points[i2],points[i])) return 0; i = i2; } i = bottomindex; while(i!=topindex) { i2 = i+1; if(i2>=numpoints) i2 = 0; if(!Below(points[i],points[i2])) return 0; i = i2; } char *vertextypes = new char[numpoints]; long *priority = new long[numpoints]; //merge left and right vertex chains priority[0] = topindex; vertextypes[topindex] = 0; leftindex = topindex+1; if(leftindex>=numpoints) leftindex = 0; rightindex = topindex-1; if(rightindex<0) rightindex = numpoints-1; for(i=1;i<(numpoints-1);i++) { if(leftindex==bottomindex) { priority[i] = rightindex; rightindex--; if(rightindex<0) rightindex = numpoints-1; vertextypes[priority[i]] = -1; } else if(rightindex==bottomindex) { priority[i] = leftindex; leftindex++; if(leftindex>=numpoints) leftindex = 0; vertextypes[priority[i]] = 1; } else { if(Below(points[leftindex],points[rightindex])) { priority[i] = rightindex; rightindex--; if(rightindex<0) rightindex = numpoints-1; vertextypes[priority[i]] = -1; } else { priority[i] = leftindex; leftindex++; if(leftindex>=numpoints) leftindex = 0; vertextypes[priority[i]] = 1; } } } priority[i] = bottomindex; vertextypes[bottomindex] = 0; long *stack = new long[numpoints]; long stackptr = 0; stack[0] = priority[0]; stack[1] = priority[1]; stackptr = 2; //for each vertex from top to bottom trim as many triangles as possible for(i=2;i<(numpoints-1);i++) { vindex = priority[i]; if(vertextypes[vindex]!=vertextypes[stack[stackptr-1]]) { for(j=0;j<(stackptr-1);j++) { if(vertextypes[vindex]==1) { triangle.Triangle(points[stack[j+1]],points[stack[j]],points[vindex]); } else { triangle.Triangle(points[stack[j]],points[stack[j+1]],points[vindex]); } triangles->push_back(triangle); } stack[0] = priority[i-1]; stack[1] = priority[i]; stackptr = 2; } else { stackptr--; while(stackptr>0) { if(vertextypes[vindex]==1) { if(IsConvex(points[vindex],points[stack[stackptr-1]],points[stack[stackptr]])) { triangle.Triangle(points[vindex],points[stack[stackptr-1]],points[stack[stackptr]]); triangles->push_back(triangle); stackptr--; } else { break; } } else { if(IsConvex(points[vindex],points[stack[stackptr]],points[stack[stackptr-1]])) { triangle.Triangle(points[vindex],points[stack[stackptr]],points[stack[stackptr-1]]); triangles->push_back(triangle); stackptr--; } else { break; } } } stackptr++; stack[stackptr] = vindex; stackptr++; } } vindex = priority[i]; for(j=0;j<(stackptr-1);j++) { if(vertextypes[stack[j+1]]==1) { triangle.Triangle(points[stack[j]],points[stack[j+1]],points[vindex]); } else { triangle.Triangle(points[stack[j+1]],points[stack[j]],points[vindex]); } triangles->push_back(triangle); } delete [] priority; delete [] vertextypes; delete [] stack; return 1; } int TPPLPartition::Triangulate_MONO(list *inpolys, list *triangles) { list monotone; list::iterator iter; if(!MonotonePartition(inpolys,&monotone)) return 0; for(iter = monotone.begin(); iter!=monotone.end();iter++) { if(!TriangulateMonotone(&(*iter),triangles)) return 0; } return 1; } int TPPLPartition::Triangulate_MONO(TPPLPoly *poly, list *triangles) { list polys; polys.push_back(*poly); return Triangulate_MONO(&polys, triangles); } Slic3r-1.2.9/xs/src/polypartition.h000066400000000000000000000270771254023100400171750ustar00rootroot00000000000000//Copyright (C) 2011 by Ivan Fratric // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. #include using namespace std; typedef double tppl_float; #define TPPL_CCW 1 #define TPPL_CW -1 //2D point structure struct TPPLPoint { tppl_float x; tppl_float y; TPPLPoint operator + (const TPPLPoint& p) const { TPPLPoint r; r.x = x + p.x; r.y = y + p.y; return r; } TPPLPoint operator - (const TPPLPoint& p) const { TPPLPoint r; r.x = x - p.x; r.y = y - p.y; return r; } TPPLPoint operator * (const tppl_float f ) const { TPPLPoint r; r.x = x*f; r.y = y*f; return r; } TPPLPoint operator / (const tppl_float f ) const { TPPLPoint r; r.x = x/f; r.y = y/f; return r; } bool operator==(const TPPLPoint& p) const { if((x == p.x)&&(y==p.y)) return true; else return false; } bool operator!=(const TPPLPoint& p) const { if((x == p.x)&&(y==p.y)) return false; else return true; } }; //Polygon implemented as an array of points with a 'hole' flag class TPPLPoly { protected: TPPLPoint *points; long numpoints; bool hole; public: //constructors/destructors TPPLPoly(); ~TPPLPoly(); TPPLPoly(const TPPLPoly &src); TPPLPoly& operator=(const TPPLPoly &src); //getters and setters long GetNumPoints() { return numpoints; } bool IsHole() { return hole; } void SetHole(bool hole) { this->hole = hole; } TPPLPoint &GetPoint(long i) { return points[i]; } TPPLPoint *GetPoints() { return points; } TPPLPoint& operator[] (int i) { return points[i]; } //clears the polygon points void Clear(); //inits the polygon with numpoints vertices void Init(long numpoints); //creates a triangle with points p1,p2,p3 void Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3); //inverts the orfer of vertices void Invert(); //returns the orientation of the polygon //possible values: // TPPL_CCW : polygon vertices are in counter-clockwise order // TPPL_CW : polygon vertices are in clockwise order // 0 : the polygon has no (measurable) area int GetOrientation(); //sets the polygon orientation //orientation can be // TPPL_CCW : sets vertices in counter-clockwise order // TPPL_CW : sets vertices in clockwise order void SetOrientation(int orientation); }; class TPPLPartition { protected: struct PartitionVertex { bool isActive; bool isConvex; bool isEar; TPPLPoint p; tppl_float angle; PartitionVertex *previous; PartitionVertex *next; }; struct MonotoneVertex { TPPLPoint p; long previous; long next; }; class VertexSorter{ MonotoneVertex *vertices; public: VertexSorter(MonotoneVertex *v) : vertices(v) {} bool operator() (long index1, long index2); }; struct Diagonal { long index1; long index2; }; //dynamic programming state for minimum-weight triangulation struct DPState { bool visible; tppl_float weight; long bestvertex; }; //dynamic programming state for convex partitioning struct DPState2 { bool visible; long weight; list pairs; }; //edge that intersects the scanline struct ScanLineEdge { long index; TPPLPoint p1; TPPLPoint p2; //determines if the edge is to the left of another edge bool operator< (const ScanLineEdge & other) const; bool IsConvex(const TPPLPoint& p1, const TPPLPoint& p2, const TPPLPoint& p3) const; }; //standard helper functions bool IsConvex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3); bool IsReflex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3); bool IsInside(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3, TPPLPoint &p); bool InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p); bool InCone(PartitionVertex *v, TPPLPoint &p); int Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TPPLPoint &p22); TPPLPoint Normalize(const TPPLPoint &p); tppl_float Distance(const TPPLPoint &p1, const TPPLPoint &p2); //helper functions for Triangulate_EC void UpdateVertexReflexity(PartitionVertex *v); void UpdateVertex(PartitionVertex *v,PartitionVertex *vertices, long numvertices); //helper functions for ConvexPartition_OPT void UpdateState(long a, long b, long w, long i, long j, DPState2 **dpstates); void TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); void TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); //helper functions for MonotonePartition bool Below(TPPLPoint &p1, TPPLPoint &p2); void AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2); //triangulates a monotone polygon, used in Triangulate_MONO int TriangulateMonotone(TPPLPoly *inPoly, list *triangles); public: //simple heuristic procedure for removing holes from a list of polygons //works by creating a diagonal from the rightmost hole vertex to some visible vertex //time complexity: O(h*(n^2)), h is the number of holes, n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons that can contain holes // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // outpolys : a list of polygons without holes //returns 1 on success, 0 on failure int RemoveHoles(list *inpolys, list *outpolys); //triangulates a polygon by ear clipping //time complexity O(n^2), n is the number of vertices //space complexity: O(n) //params: // poly : an input polygon to be triangulated // vertices have to be in counter-clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_EC(TPPLPoly *poly, list *triangles); //triangulates a list of polygons that may contain holes by ear clipping algorithm //first calls RemoveHoles to get rid of the holes, and then Triangulate_EC for each resulting polygon //time complexity: O(h*(n^2)), h is the number of holes, n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons to be triangulated (can contain holes) // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_EC(list *inpolys, list *triangles); //creates an optimal polygon triangulation in terms of minimal edge length //time complexity: O(n^3), n is the number of vertices //space complexity: O(n^2) //params: // poly : an input polygon to be triangulated // vertices have to be in counter-clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_OPT(TPPLPoly *poly, list *triangles); //triangulates a polygons by firstly partitioning it into monotone polygons //time complexity: O(n*log(n)), n is the number of vertices //space complexity: O(n) //params: // poly : an input polygon to be triangulated // vertices have to be in counter-clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_MONO(TPPLPoly *poly, list *triangles); //triangulates a list of polygons by firstly partitioning them into monotone polygons //time complexity: O(n*log(n)), n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons to be triangulated (can contain holes) // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_MONO(list *inpolys, list *triangles); //creates a monotone partition of a list of polygons that can contain holes //time complexity: O(n*log(n)), n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons to be triangulated (can contain holes) // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // monotonePolys : a list of monotone polygons (result) //returns 1 on success, 0 on failure int MonotonePartition(list *inpolys, list *monotonePolys); //partitions a polygon into convex polygons by using Hertel-Mehlhorn algorithm //the algorithm gives at most four times the number of parts as the optimal algorithm //however, in practice it works much better than that and often gives optimal partition //uses triangulation obtained by ear clipping as intermediate result //time complexity O(n^2), n is the number of vertices //space complexity: O(n) //params: // poly : an input polygon to be partitioned // vertices have to be in counter-clockwise order // parts : resulting list of convex polygons //returns 1 on success, 0 on failure int ConvexPartition_HM(TPPLPoly *poly, list *parts); //partitions a list of polygons into convex parts by using Hertel-Mehlhorn algorithm //the algorithm gives at most four times the number of parts as the optimal algorithm //however, in practice it works much better than that and often gives optimal partition //uses triangulation obtained by ear clipping as intermediate result //time complexity O(n^2), n is the number of vertices //space complexity: O(n) //params: // inpolys : an input list of polygons to be partitioned // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // parts : resulting list of convex polygons //returns 1 on success, 0 on failure int ConvexPartition_HM(list *inpolys, list *parts); //optimal convex partitioning (in terms of number of resulting convex polygons) //using the Keil-Snoeyink algorithm //M. Keil, J. Snoeyink, "On the time bound for convex decomposition of simple polygons", 1998 //time complexity O(n^3), n is the number of vertices //space complexity: O(n^3) // poly : an input polygon to be partitioned // vertices have to be in counter-clockwise order // parts : resulting list of convex polygons //returns 1 on success, 0 on failure int ConvexPartition_OPT(TPPLPoly *poly, list *parts); }; Slic3r-1.2.9/xs/src/ppport.h000066400000000000000000005254061254023100400156030ustar00rootroot00000000000000#if 0 <<'SKIP'; #endif /* ---------------------------------------------------------------------- ppport.h -- Perl/Pollution/Portability Version 3.19 Automatically created by Devel::PPPort running under perl 5.010000. Do NOT edit this file directly! -- Edit PPPort_pm.PL and the includes in parts/inc/ instead. Use 'perldoc ppport.h' to view the documentation below. ---------------------------------------------------------------------- SKIP =pod =head1 NAME ppport.h - Perl/Pollution/Portability version 3.19 =head1 SYNOPSIS perl ppport.h [options] [source files] Searches current directory for files if no [source files] are given --help show short help --version show version --patch=file write one patch file with changes --copy=suffix write changed copies with suffix --diff=program use diff program and options --compat-version=version provide compatibility with Perl version --cplusplus accept C++ comments --quiet don't output anything except fatal errors --nodiag don't show diagnostics --nohints don't show hints --nochanges don't suggest changes --nofilter don't filter input files --strip strip all script and doc functionality from ppport.h --list-provided list provided API --list-unsupported list unsupported API --api-info=name show Perl API portability information =head1 COMPATIBILITY This version of F is designed to support operation with Perl installations back to 5.003, and has been tested up to 5.10.0. =head1 OPTIONS =head2 --help Display a brief usage summary. =head2 --version Display the version of F. =head2 --patch=I If this option is given, a single patch file will be created if any changes are suggested. This requires a working diff program to be installed on your system. =head2 --copy=I If this option is given, a copy of each file will be saved with the given suffix that contains the suggested changes. This does not require any external programs. Note that this does not automagially add a dot between the original filename and the suffix. If you want the dot, you have to include it in the option argument. If neither C<--patch> or C<--copy> are given, the default is to simply print the diffs for each file. This requires either C or a C program to be installed. =head2 --diff=I Manually set the diff program and options to use. The default is to use C, when installed, and output unified context diffs. =head2 --compat-version=I Tell F to check for compatibility with the given Perl version. The default is to check for compatibility with Perl version 5.003. You can use this option to reduce the output of F if you intend to be backward compatible only down to a certain Perl version. =head2 --cplusplus Usually, F will detect C++ style comments and replace them with C style comments for portability reasons. Using this option instructs F to leave C++ comments untouched. =head2 --quiet Be quiet. Don't print anything except fatal errors. =head2 --nodiag Don't output any diagnostic messages. Only portability alerts will be printed. =head2 --nohints Don't output any hints. Hints often contain useful portability notes. Warnings will still be displayed. =head2 --nochanges Don't suggest any changes. Only give diagnostic output and hints unless these are also deactivated. =head2 --nofilter Don't filter the list of input files. By default, files not looking like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped. =head2 --strip Strip all script and documentation functionality from F. This reduces the size of F dramatically and may be useful if you want to include F in smaller modules without increasing their distribution size too much. The stripped F will have a C<--unstrip> option that allows you to undo the stripping, but only if an appropriate C module is installed. =head2 --list-provided Lists the API elements for which compatibility is provided by F. Also lists if it must be explicitly requested, if it has dependencies, and if there are hints or warnings for it. =head2 --list-unsupported Lists the API elements that are known not to be supported by F and below which version of Perl they probably won't be available or work. =head2 --api-info=I Show portability information for API elements matching I. If I is surrounded by slashes, it is interpreted as a regular expression. =head1 DESCRIPTION In order for a Perl extension (XS) module to be as portable as possible across differing versions of Perl itself, certain steps need to be taken. =over 4 =item * Including this header is the first major one. This alone will give you access to a large part of the Perl API that hasn't been available in earlier Perl releases. Use perl ppport.h --list-provided to see which API elements are provided by ppport.h. =item * You should avoid using deprecated parts of the API. For example, using global Perl variables without the C prefix is deprecated. Also, some API functions used to have a C prefix. Using this form is also deprecated. You can safely use the supported API, as F will provide wrappers for older Perl versions. =item * If you use one of a few functions or variables that were not present in earlier versions of Perl, and that can't be provided using a macro, you have to explicitly request support for these functions by adding one or more C<#define>s in your source code before the inclusion of F. These functions or variables will be marked C in the list shown by C<--list-provided>. Depending on whether you module has a single or multiple files that use such functions or variables, you want either C or global variants. For a C function or variable (used only in a single source file), use: #define NEED_function #define NEED_variable For a global function or variable (used in multiple source files), use: #define NEED_function_GLOBAL #define NEED_variable_GLOBAL Note that you mustn't have more than one global request for the same function or variable in your project. Function / Variable Static Request Global Request ----------------------------------------------------------------------------------------- PL_parser NEED_PL_parser NEED_PL_parser_GLOBAL PL_signals NEED_PL_signals NEED_PL_signals_GLOBAL eval_pv() NEED_eval_pv NEED_eval_pv_GLOBAL grok_bin() NEED_grok_bin NEED_grok_bin_GLOBAL grok_hex() NEED_grok_hex NEED_grok_hex_GLOBAL grok_number() NEED_grok_number NEED_grok_number_GLOBAL grok_numeric_radix() NEED_grok_numeric_radix NEED_grok_numeric_radix_GLOBAL grok_oct() NEED_grok_oct NEED_grok_oct_GLOBAL load_module() NEED_load_module NEED_load_module_GLOBAL my_snprintf() NEED_my_snprintf NEED_my_snprintf_GLOBAL my_sprintf() NEED_my_sprintf NEED_my_sprintf_GLOBAL my_strlcat() NEED_my_strlcat NEED_my_strlcat_GLOBAL my_strlcpy() NEED_my_strlcpy NEED_my_strlcpy_GLOBAL newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL newRV_noinc() NEED_newRV_noinc NEED_newRV_noinc_GLOBAL newSV_type() NEED_newSV_type NEED_newSV_type_GLOBAL newSVpvn_flags() NEED_newSVpvn_flags NEED_newSVpvn_flags_GLOBAL newSVpvn_share() NEED_newSVpvn_share NEED_newSVpvn_share_GLOBAL pv_display() NEED_pv_display NEED_pv_display_GLOBAL pv_escape() NEED_pv_escape NEED_pv_escape_GLOBAL pv_pretty() NEED_pv_pretty NEED_pv_pretty_GLOBAL sv_2pv_flags() NEED_sv_2pv_flags NEED_sv_2pv_flags_GLOBAL sv_2pvbyte() NEED_sv_2pvbyte NEED_sv_2pvbyte_GLOBAL sv_catpvf_mg() NEED_sv_catpvf_mg NEED_sv_catpvf_mg_GLOBAL sv_catpvf_mg_nocontext() NEED_sv_catpvf_mg_nocontext NEED_sv_catpvf_mg_nocontext_GLOBAL sv_pvn_force_flags() NEED_sv_pvn_force_flags NEED_sv_pvn_force_flags_GLOBAL sv_setpvf_mg() NEED_sv_setpvf_mg NEED_sv_setpvf_mg_GLOBAL sv_setpvf_mg_nocontext() NEED_sv_setpvf_mg_nocontext NEED_sv_setpvf_mg_nocontext_GLOBAL vload_module() NEED_vload_module NEED_vload_module_GLOBAL vnewSVpvf() NEED_vnewSVpvf NEED_vnewSVpvf_GLOBAL warner() NEED_warner NEED_warner_GLOBAL To avoid namespace conflicts, you can change the namespace of the explicitly exported functions / variables using the C macro. Just C<#define> the macro before including C: #define DPPP_NAMESPACE MyOwnNamespace_ #include "ppport.h" The default namespace is C. =back The good thing is that most of the above can be checked by running F on your source code. See the next section for details. =head1 EXAMPLES To verify whether F is needed for your module, whether you should make any changes to your code, and whether any special defines should be used, F can be run as a Perl script to check your source code. Simply say: perl ppport.h The result will usually be a list of patches suggesting changes that should at least be acceptable, if not necessarily the most efficient solution, or a fix for all possible problems. If you know that your XS module uses features only available in newer Perl releases, if you're aware that it uses C++ comments, and if you want all suggestions as a single patch file, you could use something like this: perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff If you only want your code to be scanned without any suggestions for changes, use: perl ppport.h --nochanges You can specify a different C program or options, using the C<--diff> option: perl ppport.h --diff='diff -C 10' This would output context diffs with 10 lines of context. If you want to create patched copies of your files instead, use: perl ppport.h --copy=.new To display portability information for the C function, use: perl ppport.h --api-info=newSVpvn Since the argument to C<--api-info> can be a regular expression, you can use perl ppport.h --api-info=/_nomg$/ to display portability information for all C<_nomg> functions or perl ppport.h --api-info=/./ to display information for all known API elements. =head1 BUGS If this version of F is causing failure during the compilation of this module, please check if newer versions of either this module or C are available on CPAN before sending a bug report. If F was generated using the latest version of C and is causing failure of this module, please file a bug report using the CPAN Request Tracker at L. Please include the following information: =over 4 =item 1. The complete output from running "perl -V" =item 2. This file. =item 3. The name and version of the module you were trying to build. =item 4. A full log of the build that failed. =item 5. Any other information that you think could be relevant. =back For the latest version of this code, please get the C module from CPAN. =head1 COPYRIGHT Version 3.x, Copyright (c) 2004-2009, Marcus Holland-Moritz. Version 2.x, Copyright (C) 2001, Paul Marquess. Version 1.x, Copyright (C) 1999, Kenneth Albanowski. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO See L. =cut use strict; # Disable broken TRIE-optimization BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if $] >= 5.009004 && $] <= 5.009005 } my $VERSION = 3.19; my %opt = ( quiet => 0, diag => 1, hints => 1, changes => 1, cplusplus => 0, filter => 1, strip => 0, version => 0, ); my($ppport) = $0 =~ /([\w.]+)$/; my $LF = '(?:\r\n|[\r\n])'; # line feed my $HS = "[ \t]"; # horizontal whitespace # Never use C comments in this file! my $ccs = '/'.'*'; my $cce = '*'.'/'; my $rccs = quotemeta $ccs; my $rcce = quotemeta $cce; eval { require Getopt::Long; Getopt::Long::GetOptions(\%opt, qw( help quiet diag! filter! hints! changes! cplusplus strip version patch=s copy=s diff=s compat-version=s list-provided list-unsupported api-info=s )) or usage(); }; if ($@ and grep /^-/, @ARGV) { usage() if "@ARGV" =~ /^--?h(?:elp)?$/; die "Getopt::Long not found. Please don't use any options.\n"; } if ($opt{version}) { print "This is $0 $VERSION.\n"; exit 0; } usage() if $opt{help}; strip() if $opt{strip}; if (exists $opt{'compat-version'}) { my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) }; if ($@) { die "Invalid version number format: '$opt{'compat-version'}'\n"; } die "Only Perl 5 is supported\n" if $r != 5; die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $s >= 1000; $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s; } else { $opt{'compat-version'} = 5; } my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/ ? ( $1 => { ($2 ? ( base => $2 ) : ()), ($3 ? ( todo => $3 ) : ()), (index($4, 'v') >= 0 ? ( varargs => 1 ) : ()), (index($4, 'p') >= 0 ? ( provided => 1 ) : ()), (index($4, 'n') >= 0 ? ( nothxarg => 1 ) : ()), } ) : die "invalid spec: $_" } qw( AvFILLp|5.004050||p AvFILL||| CLASS|||n CPERLscope|5.005000||p CX_CURPAD_SAVE||| CX_CURPAD_SV||| CopFILEAV|5.006000||p CopFILEGV_set|5.006000||p CopFILEGV|5.006000||p CopFILESV|5.006000||p CopFILE_set|5.006000||p CopFILE|5.006000||p CopSTASHPV_set|5.006000||p CopSTASHPV|5.006000||p CopSTASH_eq|5.006000||p CopSTASH_set|5.006000||p CopSTASH|5.006000||p CopyD|5.009002||p Copy||| CvPADLIST||| CvSTASH||| CvWEAKOUTSIDE||| DEFSV_set|5.011000||p DEFSV|5.004050||p END_EXTERN_C|5.005000||p ENTER||| ERRSV|5.004050||p EXTEND||| EXTERN_C|5.005000||p F0convert|||n FREETMPS||| GIMME_V||5.004000|n GIMME|||n GROK_NUMERIC_RADIX|5.007002||p G_ARRAY||| G_DISCARD||| G_EVAL||| G_METHOD|5.006001||p G_NOARGS||| G_SCALAR||| G_VOID||5.004000| GetVars||| GvSVn|5.009003||p GvSV||| Gv_AMupdate||| HEf_SVKEY||5.004000| HeHASH||5.004000| HeKEY||5.004000| HeKLEN||5.004000| HePV||5.004000| HeSVKEY_force||5.004000| HeSVKEY_set||5.004000| HeSVKEY||5.004000| HeUTF8||5.011000| HeVAL||5.004000| HvNAMELEN_get|5.009003||p HvNAME_get|5.009003||p HvNAME||| INT2PTR|5.006000||p IN_LOCALE_COMPILETIME|5.007002||p IN_LOCALE_RUNTIME|5.007002||p IN_LOCALE|5.007002||p IN_PERL_COMPILETIME|5.008001||p IS_NUMBER_GREATER_THAN_UV_MAX|5.007002||p IS_NUMBER_INFINITY|5.007002||p IS_NUMBER_IN_UV|5.007002||p IS_NUMBER_NAN|5.007003||p IS_NUMBER_NEG|5.007002||p IS_NUMBER_NOT_INT|5.007002||p IVSIZE|5.006000||p IVTYPE|5.006000||p IVdf|5.006000||p LEAVE||| LVRET||| MARK||| MULTICALL||5.011000| MY_CXT_CLONE|5.009002||p MY_CXT_INIT|5.007003||p MY_CXT|5.007003||p MoveD|5.009002||p Move||| NOOP|5.005000||p NUM2PTR|5.006000||p NVTYPE|5.006000||p NVef|5.006001||p NVff|5.006001||p NVgf|5.006001||p Newxc|5.009003||p Newxz|5.009003||p Newx|5.009003||p Nullav||| Nullch||| Nullcv||| Nullhv||| Nullsv||| ORIGMARK||| PAD_BASE_SV||| PAD_CLONE_VARS||| PAD_COMPNAME_FLAGS||| PAD_COMPNAME_GEN_set||| PAD_COMPNAME_GEN||| PAD_COMPNAME_OURSTASH||| PAD_COMPNAME_PV||| PAD_COMPNAME_TYPE||| PAD_DUP||| PAD_RESTORE_LOCAL||| PAD_SAVE_LOCAL||| PAD_SAVE_SETNULLPAD||| PAD_SETSV||| PAD_SET_CUR_NOSAVE||| PAD_SET_CUR||| PAD_SVl||| PAD_SV||| PERLIO_FUNCS_CAST|5.009003||p PERLIO_FUNCS_DECL|5.009003||p PERL_ABS|5.008001||p PERL_BCDVERSION|5.011000||p PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||p PERL_HASH|5.004000||p PERL_INT_MAX|5.004000||p PERL_INT_MIN|5.004000||p PERL_LONG_MAX|5.004000||p PERL_LONG_MIN|5.004000||p PERL_MAGIC_arylen|5.007002||p PERL_MAGIC_backref|5.007002||p PERL_MAGIC_bm|5.007002||p PERL_MAGIC_collxfrm|5.007002||p PERL_MAGIC_dbfile|5.007002||p PERL_MAGIC_dbline|5.007002||p PERL_MAGIC_defelem|5.007002||p PERL_MAGIC_envelem|5.007002||p PERL_MAGIC_env|5.007002||p PERL_MAGIC_ext|5.007002||p PERL_MAGIC_fm|5.007002||p PERL_MAGIC_glob|5.011000||p PERL_MAGIC_isaelem|5.007002||p PERL_MAGIC_isa|5.007002||p PERL_MAGIC_mutex|5.011000||p PERL_MAGIC_nkeys|5.007002||p PERL_MAGIC_overload_elem|5.007002||p PERL_MAGIC_overload_table|5.007002||p PERL_MAGIC_overload|5.007002||p PERL_MAGIC_pos|5.007002||p PERL_MAGIC_qr|5.007002||p PERL_MAGIC_regdata|5.007002||p PERL_MAGIC_regdatum|5.007002||p PERL_MAGIC_regex_global|5.007002||p PERL_MAGIC_shared_scalar|5.007003||p PERL_MAGIC_shared|5.007003||p PERL_MAGIC_sigelem|5.007002||p PERL_MAGIC_sig|5.007002||p PERL_MAGIC_substr|5.007002||p PERL_MAGIC_sv|5.007002||p PERL_MAGIC_taint|5.007002||p PERL_MAGIC_tiedelem|5.007002||p PERL_MAGIC_tiedscalar|5.007002||p PERL_MAGIC_tied|5.007002||p PERL_MAGIC_utf8|5.008001||p PERL_MAGIC_uvar_elem|5.007003||p PERL_MAGIC_uvar|5.007002||p PERL_MAGIC_vec|5.007002||p PERL_MAGIC_vstring|5.008001||p PERL_PV_ESCAPE_ALL|5.009004||p PERL_PV_ESCAPE_FIRSTCHAR|5.009004||p PERL_PV_ESCAPE_NOBACKSLASH|5.009004||p PERL_PV_ESCAPE_NOCLEAR|5.009004||p PERL_PV_ESCAPE_QUOTE|5.009004||p PERL_PV_ESCAPE_RE|5.009005||p PERL_PV_ESCAPE_UNI_DETECT|5.009004||p PERL_PV_ESCAPE_UNI|5.009004||p PERL_PV_PRETTY_DUMP|5.009004||p PERL_PV_PRETTY_ELLIPSES|5.010000||p PERL_PV_PRETTY_LTGT|5.009004||p PERL_PV_PRETTY_NOCLEAR|5.010000||p PERL_PV_PRETTY_QUOTE|5.009004||p PERL_PV_PRETTY_REGPROP|5.009004||p PERL_QUAD_MAX|5.004000||p PERL_QUAD_MIN|5.004000||p PERL_REVISION|5.006000||p PERL_SCAN_ALLOW_UNDERSCORES|5.007003||p PERL_SCAN_DISALLOW_PREFIX|5.007003||p PERL_SCAN_GREATER_THAN_UV_MAX|5.007003||p PERL_SCAN_SILENT_ILLDIGIT|5.008001||p PERL_SHORT_MAX|5.004000||p PERL_SHORT_MIN|5.004000||p PERL_SIGNALS_UNSAFE_FLAG|5.008001||p PERL_SUBVERSION|5.006000||p PERL_SYS_INIT3||5.006000| PERL_SYS_INIT||| PERL_SYS_TERM||5.011000| PERL_UCHAR_MAX|5.004000||p PERL_UCHAR_MIN|5.004000||p PERL_UINT_MAX|5.004000||p PERL_UINT_MIN|5.004000||p PERL_ULONG_MAX|5.004000||p PERL_ULONG_MIN|5.004000||p PERL_UNUSED_ARG|5.009003||p PERL_UNUSED_CONTEXT|5.009004||p PERL_UNUSED_DECL|5.007002||p PERL_UNUSED_VAR|5.007002||p PERL_UQUAD_MAX|5.004000||p PERL_UQUAD_MIN|5.004000||p PERL_USE_GCC_BRACE_GROUPS|5.009004||p PERL_USHORT_MAX|5.004000||p PERL_USHORT_MIN|5.004000||p PERL_VERSION|5.006000||p PL_DBsignal|5.005000||p PL_DBsingle|||pn PL_DBsub|||pn PL_DBtrace|||pn PL_Sv|5.005000||p PL_bufend|5.011000||p PL_bufptr|5.011000||p PL_compiling|5.004050||p PL_copline|5.011000||p PL_curcop|5.004050||p PL_curstash|5.004050||p PL_debstash|5.004050||p PL_defgv|5.004050||p PL_diehook|5.004050||p PL_dirty|5.004050||p PL_dowarn|||pn PL_errgv|5.004050||p PL_error_count|5.011000||p PL_expect|5.011000||p PL_hexdigit|5.005000||p PL_hints|5.005000||p PL_in_my_stash|5.011000||p PL_in_my|5.011000||p PL_last_in_gv|||n PL_laststatval|5.005000||p PL_lex_state|5.011000||p PL_lex_stuff|5.011000||p PL_linestr|5.011000||p PL_modglobal||5.005000|n PL_na|5.004050||pn PL_no_modify|5.006000||p PL_ofsgv|||n PL_parser|5.009005||p PL_perl_destruct_level|5.004050||p PL_perldb|5.004050||p PL_ppaddr|5.006000||p PL_rsfp_filters|5.004050||p PL_rsfp|5.004050||p PL_rs|||n PL_signals|5.008001||p PL_stack_base|5.004050||p PL_stack_sp|5.004050||p PL_statcache|5.005000||p PL_stdingv|5.004050||p PL_sv_arenaroot|5.004050||p PL_sv_no|5.004050||pn PL_sv_undef|5.004050||pn PL_sv_yes|5.004050||pn PL_tainted|5.004050||p PL_tainting|5.004050||p PL_tokenbuf|5.011000||p POP_MULTICALL||5.011000| POPi|||n POPl|||n POPn|||n POPpbytex||5.007001|n POPpx||5.005030|n POPp|||n POPs|||n PTR2IV|5.006000||p PTR2NV|5.006000||p PTR2UV|5.006000||p PTR2nat|5.009003||p PTR2ul|5.007001||p PTRV|5.006000||p PUSHMARK||| PUSH_MULTICALL||5.011000| PUSHi||| PUSHmortal|5.009002||p PUSHn||| PUSHp||| PUSHs||| PUSHu|5.004000||p PUTBACK||| PerlIO_clearerr||5.007003| PerlIO_close||5.007003| PerlIO_context_layers||5.009004| PerlIO_eof||5.007003| PerlIO_error||5.007003| PerlIO_fileno||5.007003| PerlIO_fill||5.007003| PerlIO_flush||5.007003| PerlIO_get_base||5.007003| PerlIO_get_bufsiz||5.007003| PerlIO_get_cnt||5.007003| PerlIO_get_ptr||5.007003| PerlIO_read||5.007003| PerlIO_seek||5.007003| PerlIO_set_cnt||5.007003| PerlIO_set_ptrcnt||5.007003| PerlIO_setlinebuf||5.007003| PerlIO_stderr||5.007003| PerlIO_stdin||5.007003| PerlIO_stdout||5.007003| PerlIO_tell||5.007003| PerlIO_unread||5.007003| PerlIO_write||5.007003| Perl_signbit||5.009005|n PoisonFree|5.009004||p PoisonNew|5.009004||p PoisonWith|5.009004||p Poison|5.008000||p RETVAL|||n Renewc||| Renew||| SAVECLEARSV||| SAVECOMPPAD||| SAVEPADSV||| SAVETMPS||| SAVE_DEFSV|5.004050||p SPAGAIN||| SP||| START_EXTERN_C|5.005000||p START_MY_CXT|5.007003||p STMT_END|||p STMT_START|||p STR_WITH_LEN|5.009003||p ST||| SV_CONST_RETURN|5.009003||p SV_COW_DROP_PV|5.008001||p SV_COW_SHARED_HASH_KEYS|5.009005||p SV_GMAGIC|5.007002||p SV_HAS_TRAILING_NUL|5.009004||p SV_IMMEDIATE_UNREF|5.007001||p SV_MUTABLE_RETURN|5.009003||p SV_NOSTEAL|5.009002||p SV_SMAGIC|5.009003||p SV_UTF8_NO_ENCODING|5.008001||p SVfARG|5.009005||p SVf_UTF8|5.006000||p SVf|5.006000||p SVt_IV||| SVt_NV||| SVt_PVAV||| SVt_PVCV||| SVt_PVHV||| SVt_PVMG||| SVt_PV||| Safefree||| Slab_Alloc||| Slab_Free||| Slab_to_rw||| StructCopy||| SvCUR_set||| SvCUR||| SvEND||| SvGAMAGIC||5.006001| SvGETMAGIC|5.004050||p SvGROW||| SvIOK_UV||5.006000| SvIOK_notUV||5.006000| SvIOK_off||| SvIOK_only_UV||5.006000| SvIOK_only||| SvIOK_on||| SvIOKp||| SvIOK||| SvIVX||| SvIV_nomg|5.009001||p SvIV_set||| SvIVx||| SvIV||| SvIsCOW_shared_hash||5.008003| SvIsCOW||5.008003| SvLEN_set||| SvLEN||| SvLOCK||5.007003| SvMAGIC_set|5.009003||p SvNIOK_off||| SvNIOKp||| SvNIOK||| SvNOK_off||| SvNOK_only||| SvNOK_on||| SvNOKp||| SvNOK||| SvNVX||| SvNV_set||| SvNVx||| SvNV||| SvOK||| SvOOK_offset||5.011000| SvOOK||| SvPOK_off||| SvPOK_only_UTF8||5.006000| SvPOK_only||| SvPOK_on||| SvPOKp||| SvPOK||| SvPVX_const|5.009003||p SvPVX_mutable|5.009003||p SvPVX||| SvPV_const|5.009003||p SvPV_flags_const_nolen|5.009003||p SvPV_flags_const|5.009003||p SvPV_flags_mutable|5.009003||p SvPV_flags|5.007002||p SvPV_force_flags_mutable|5.009003||p SvPV_force_flags_nolen|5.009003||p SvPV_force_flags|5.007002||p SvPV_force_mutable|5.009003||p SvPV_force_nolen|5.009003||p SvPV_force_nomg_nolen|5.009003||p SvPV_force_nomg|5.007002||p SvPV_force|||p SvPV_mutable|5.009003||p SvPV_nolen_const|5.009003||p SvPV_nolen|5.006000||p SvPV_nomg_const_nolen|5.009003||p SvPV_nomg_const|5.009003||p SvPV_nomg|5.007002||p SvPV_renew|5.009003||p SvPV_set||| SvPVbyte_force||5.009002| SvPVbyte_nolen||5.006000| SvPVbytex_force||5.006000| SvPVbytex||5.006000| SvPVbyte|5.006000||p SvPVutf8_force||5.006000| SvPVutf8_nolen||5.006000| SvPVutf8x_force||5.006000| SvPVutf8x||5.006000| SvPVutf8||5.006000| SvPVx||| SvPV||| SvREFCNT_dec||| SvREFCNT_inc_NN|5.009004||p SvREFCNT_inc_simple_NN|5.009004||p SvREFCNT_inc_simple_void_NN|5.009004||p SvREFCNT_inc_simple_void|5.009004||p SvREFCNT_inc_simple|5.009004||p SvREFCNT_inc_void_NN|5.009004||p SvREFCNT_inc_void|5.009004||p SvREFCNT_inc|||p SvREFCNT||| SvROK_off||| SvROK_on||| SvROK||| SvRV_set|5.009003||p SvRV||| SvRXOK||5.009005| SvRX||5.009005| SvSETMAGIC||| SvSHARED_HASH|5.009003||p SvSHARE||5.007003| SvSTASH_set|5.009003||p SvSTASH||| SvSetMagicSV_nosteal||5.004000| SvSetMagicSV||5.004000| SvSetSV_nosteal||5.004000| SvSetSV||| SvTAINTED_off||5.004000| SvTAINTED_on||5.004000| SvTAINTED||5.004000| SvTAINT||| SvTRUE||| SvTYPE||| SvUNLOCK||5.007003| SvUOK|5.007001|5.006000|p SvUPGRADE||| SvUTF8_off||5.006000| SvUTF8_on||5.006000| SvUTF8||5.006000| SvUVXx|5.004000||p SvUVX|5.004000||p SvUV_nomg|5.009001||p SvUV_set|5.009003||p SvUVx|5.004000||p SvUV|5.004000||p SvVOK||5.008001| SvVSTRING_mg|5.009004||p THIS|||n UNDERBAR|5.009002||p UTF8_MAXBYTES|5.009002||p UVSIZE|5.006000||p UVTYPE|5.006000||p UVXf|5.007001||p UVof|5.006000||p UVuf|5.006000||p UVxf|5.006000||p WARN_ALL|5.006000||p WARN_AMBIGUOUS|5.006000||p WARN_ASSERTIONS|5.011000||p WARN_BAREWORD|5.006000||p WARN_CLOSED|5.006000||p WARN_CLOSURE|5.006000||p WARN_DEBUGGING|5.006000||p WARN_DEPRECATED|5.006000||p WARN_DIGIT|5.006000||p WARN_EXEC|5.006000||p WARN_EXITING|5.006000||p WARN_GLOB|5.006000||p WARN_INPLACE|5.006000||p WARN_INTERNAL|5.006000||p WARN_IO|5.006000||p WARN_LAYER|5.008000||p WARN_MALLOC|5.006000||p WARN_MISC|5.006000||p WARN_NEWLINE|5.006000||p WARN_NUMERIC|5.006000||p WARN_ONCE|5.006000||p WARN_OVERFLOW|5.006000||p WARN_PACK|5.006000||p WARN_PARENTHESIS|5.006000||p WARN_PIPE|5.006000||p WARN_PORTABLE|5.006000||p WARN_PRECEDENCE|5.006000||p WARN_PRINTF|5.006000||p WARN_PROTOTYPE|5.006000||p WARN_QW|5.006000||p WARN_RECURSION|5.006000||p WARN_REDEFINE|5.006000||p WARN_REGEXP|5.006000||p WARN_RESERVED|5.006000||p WARN_SEMICOLON|5.006000||p WARN_SEVERE|5.006000||p WARN_SIGNAL|5.006000||p WARN_SUBSTR|5.006000||p WARN_SYNTAX|5.006000||p WARN_TAINT|5.006000||p WARN_THREADS|5.008000||p WARN_UNINITIALIZED|5.006000||p WARN_UNOPENED|5.006000||p WARN_UNPACK|5.006000||p WARN_UNTIE|5.006000||p WARN_UTF8|5.006000||p WARN_VOID|5.006000||p XCPT_CATCH|5.009002||p XCPT_RETHROW|5.009002||p XCPT_TRY_END|5.009002||p XCPT_TRY_START|5.009002||p XPUSHi||| XPUSHmortal|5.009002||p XPUSHn||| XPUSHp||| XPUSHs||| XPUSHu|5.004000||p XSPROTO|5.010000||p XSRETURN_EMPTY||| XSRETURN_IV||| XSRETURN_NO||| XSRETURN_NV||| XSRETURN_PV||| XSRETURN_UNDEF||| XSRETURN_UV|5.008001||p XSRETURN_YES||| XSRETURN|||p XST_mIV||| XST_mNO||| XST_mNV||| XST_mPV||| XST_mUNDEF||| XST_mUV|5.008001||p XST_mYES||| XS_VERSION_BOOTCHECK||| XS_VERSION||| XSprePUSH|5.006000||p XS||| ZeroD|5.009002||p Zero||| _aMY_CXT|5.007003||p _pMY_CXT|5.007003||p aMY_CXT_|5.007003||p aMY_CXT|5.007003||p aTHXR_|5.011000||p aTHXR|5.011000||p aTHX_|5.006000||p aTHX|5.006000||p add_data|||n addmad||| allocmy||| amagic_call||| amagic_cmp_locale||| amagic_cmp||| amagic_i_ncmp||| amagic_ncmp||| any_dup||| ao||| append_elem||| append_list||| append_madprops||| apply_attrs_my||| apply_attrs_string||5.006001| apply_attrs||| apply||| atfork_lock||5.007003|n atfork_unlock||5.007003|n av_arylen_p||5.009003| av_clear||| av_create_and_push||5.009005| av_create_and_unshift_one||5.009005| av_delete||5.006000| av_exists||5.006000| av_extend||| av_fetch||| av_fill||| av_iter_p||5.011000| av_len||| av_make||| av_pop||| av_push||| av_reify||| av_shift||| av_store||| av_undef||| av_unshift||| ax|||n bad_type||| bind_match||| block_end||| block_gimme||5.004000| block_start||| boolSV|5.004000||p boot_core_PerlIO||| boot_core_UNIVERSAL||| boot_core_mro||| bytes_from_utf8||5.007001| bytes_to_uni|||n bytes_to_utf8||5.006001| call_argv|5.006000||p call_atexit||5.006000| call_list||5.004000| call_method|5.006000||p call_pv|5.006000||p call_sv|5.006000||p calloc||5.007002|n cando||| cast_i32||5.006000| cast_iv||5.006000| cast_ulong||5.006000| cast_uv||5.006000| check_type_and_open||| check_uni||| checkcomma||| checkposixcc||| ckWARN|5.006000||p ck_anoncode||| ck_bitop||| ck_concat||| ck_defined||| ck_delete||| ck_die||| ck_each||| ck_eof||| ck_eval||| ck_exec||| ck_exists||| ck_exit||| ck_ftst||| ck_fun||| ck_glob||| ck_grep||| ck_index||| ck_join||| ck_lfun||| ck_listiob||| ck_match||| ck_method||| ck_null||| ck_open||| ck_readline||| ck_repeat||| ck_require||| ck_return||| ck_rfun||| ck_rvconst||| ck_sassign||| ck_select||| ck_shift||| ck_sort||| ck_spair||| ck_split||| ck_subr||| ck_substr||| ck_svconst||| ck_trunc||| ck_unpack||| ckwarn_d||5.009003| ckwarn||5.009003| cl_and|||n cl_anything|||n cl_init_zero|||n cl_init|||n cl_is_anything|||n cl_or|||n clear_placeholders||| closest_cop||| convert||| cop_free||| cr_textfilter||| create_eval_scope||| croak_nocontext|||vn croak_xs_usage||5.011000| croak|||v csighandler||5.009003|n curmad||| custom_op_desc||5.007003| custom_op_name||5.007003| cv_ckproto_len||| cv_clone||| cv_const_sv||5.004000| cv_dump||| cv_undef||| cx_dump||5.005000| cx_dup||| cxinc||| dAXMARK|5.009003||p dAX|5.007002||p dITEMS|5.007002||p dMARK||| dMULTICALL||5.009003| dMY_CXT_SV|5.007003||p dMY_CXT|5.007003||p dNOOP|5.006000||p dORIGMARK||| dSP||| dTHR|5.004050||p dTHXR|5.011000||p dTHXa|5.006000||p dTHXoa|5.006000||p dTHX|5.006000||p dUNDERBAR|5.009002||p dVAR|5.009003||p dXCPT|5.009002||p dXSARGS||| dXSI32||| dXSTARG|5.006000||p deb_curcv||| deb_nocontext|||vn deb_stack_all||| deb_stack_n||| debop||5.005000| debprofdump||5.005000| debprof||| debstackptrs||5.007003| debstack||5.007003| debug_start_match||| deb||5.007003|v del_sv||| delete_eval_scope||| delimcpy||5.004000| deprecate_old||| deprecate||| despatch_signals||5.007001| destroy_matcher||| die_nocontext|||vn die_where||| die|||v dirp_dup||| div128||| djSP||| do_aexec5||| do_aexec||| do_aspawn||| do_binmode||5.004050| do_chomp||| do_chop||| do_close||| do_dump_pad||| do_eof||| do_exec3||| do_execfree||| do_exec||| do_gv_dump||5.006000| do_gvgv_dump||5.006000| do_hv_dump||5.006000| do_ipcctl||| do_ipcget||| do_join||| do_kv||| do_magic_dump||5.006000| do_msgrcv||| do_msgsnd||| do_oddball||| do_op_dump||5.006000| do_op_xmldump||| do_open9||5.006000| do_openn||5.007001| do_open||5.004000| do_pmop_dump||5.006000| do_pmop_xmldump||| do_print||| do_readline||| do_seek||| do_semop||| do_shmio||| do_smartmatch||| do_spawn_nowait||| do_spawn||| do_sprintf||| do_sv_dump||5.006000| do_sysseek||| do_tell||| do_trans_complex_utf8||| do_trans_complex||| do_trans_count_utf8||| do_trans_count||| do_trans_simple_utf8||| do_trans_simple||| do_trans||| do_vecget||| do_vecset||| do_vop||| docatch||| doeval||| dofile||| dofindlabel||| doform||| doing_taint||5.008001|n dooneliner||| doopen_pm||| doparseform||| dopoptoeval||| dopoptogiven||| dopoptolabel||| dopoptoloop||| dopoptosub_at||| dopoptowhen||| doref||5.009003| dounwind||| dowantarray||| dump_all||5.006000| dump_eval||5.006000| dump_exec_pos||| dump_fds||| dump_form||5.006000| dump_indent||5.006000|v dump_mstats||| dump_packsubs||5.006000| dump_sub||5.006000| dump_sv_child||| dump_trie_interim_list||| dump_trie_interim_table||| dump_trie||| dump_vindent||5.006000| dumpuntil||| dup_attrlist||| emulate_cop_io||| eval_pv|5.006000||p eval_sv|5.006000||p exec_failed||| expect_number||| fbm_compile||5.005000| fbm_instr||5.005000| feature_is_enabled||| fetch_cop_label||5.011000| filter_add||| filter_del||| filter_gets||| filter_read||| find_and_forget_pmops||| find_array_subscript||| find_beginning||| find_byclass||| find_hash_subscript||| find_in_my_stash||| find_runcv||5.008001| find_rundefsvoffset||5.009002| find_script||| find_uninit_var||| first_symbol|||n fold_constants||| forbid_setid||| force_ident||| force_list||| force_next||| force_version||| force_word||| forget_pmop||| form_nocontext|||vn form||5.004000|v fp_dup||| fprintf_nocontext|||vn free_global_struct||| free_tied_hv_pool||| free_tmps||| gen_constant_list||| get_arena||| get_aux_mg||| get_av|5.006000||p get_context||5.006000|n get_cvn_flags||5.009005| get_cv|5.006000||p get_db_sub||| get_debug_opts||| get_hash_seed||| get_hv|5.006000||p get_isa_hash||| get_mstats||| get_no_modify||| get_num||| get_op_descs||5.005000| get_op_names||5.005000| get_opargs||| get_ppaddr||5.006000| get_re_arg||| get_sv|5.006000||p get_vtbl||5.005030| getcwd_sv||5.007002| getenv_len||| glob_2number||| glob_assign_glob||| glob_assign_ref||| gp_dup||| gp_free||| gp_ref||| grok_bin|5.007003||p grok_hex|5.007003||p grok_number|5.007002||p grok_numeric_radix|5.007002||p grok_oct|5.007003||p group_end||| gv_AVadd||| gv_HVadd||| gv_IOadd||| gv_SVadd||| gv_autoload4||5.004000| gv_check||| gv_const_sv||5.009003| gv_dump||5.006000| gv_efullname3||5.004000| gv_efullname4||5.006001| gv_efullname||| gv_ename||| gv_fetchfile_flags||5.009005| gv_fetchfile||| gv_fetchmeth_autoload||5.007003| gv_fetchmethod_autoload||5.004000| gv_fetchmethod_flags||5.011000| gv_fetchmethod||| gv_fetchmeth||| gv_fetchpvn_flags|5.009002||p gv_fetchpvs|5.009004||p gv_fetchpv||| gv_fetchsv||5.009002| gv_fullname3||5.004000| gv_fullname4||5.006001| gv_fullname||| gv_get_super_pkg||| gv_handler||5.007001| gv_init_sv||| gv_init||| gv_name_set||5.009004| gv_stashpvn|5.004000||p gv_stashpvs|5.009003||p gv_stashpv||| gv_stashsv||| he_dup||| hek_dup||| hfreeentries||| hsplit||| hv_assert||5.011000| hv_auxinit|||n hv_backreferences_p||| hv_clear_placeholders||5.009001| hv_clear||| hv_common_key_len||5.010000| hv_common||5.010000| hv_copy_hints_hv||| hv_delayfree_ent||5.004000| hv_delete_common||| hv_delete_ent||5.004000| hv_delete||| hv_eiter_p||5.009003| hv_eiter_set||5.009003| hv_exists_ent||5.004000| hv_exists||| hv_fetch_ent||5.004000| hv_fetchs|5.009003||p hv_fetch||| hv_free_ent||5.004000| hv_iterinit||| hv_iterkeysv||5.004000| hv_iterkey||| hv_iternext_flags||5.008000| hv_iternextsv||| hv_iternext||| hv_iterval||| hv_kill_backrefs||| hv_ksplit||5.004000| hv_magic_check|||n hv_magic||| hv_name_set||5.009003| hv_notallowed||| hv_placeholders_get||5.009003| hv_placeholders_p||5.009003| hv_placeholders_set||5.009003| hv_riter_p||5.009003| hv_riter_set||5.009003| hv_scalar||5.009001| hv_store_ent||5.004000| hv_store_flags||5.008000| hv_stores|5.009004||p hv_store||| hv_undef||| ibcmp_locale||5.004000| ibcmp_utf8||5.007003| ibcmp||| incline||| incpush_if_exists||| incpush_use_sep||| incpush||| ingroup||| init_argv_symbols||| init_debugger||| init_global_struct||| init_i18nl10n||5.006000| init_i18nl14n||5.006000| init_ids||| init_interp||| init_main_stash||| init_perllib||| init_postdump_symbols||| init_predump_symbols||| init_stacks||5.005000| init_tm||5.007002| instr||| intro_my||| intuit_method||| intuit_more||| invert||| io_close||| isALNUMC|5.006000||p isALNUM||| isALPHA||| isASCII|5.006000||p isBLANK|5.006001||p isCNTRL|5.006000||p isDIGIT||| isGRAPH|5.006000||p isGV_with_GP|5.009004||p isLOWER||| isPRINT|5.004000||p isPSXSPC|5.006001||p isPUNCT|5.006000||p isSPACE||| isUPPER||| isXDIGIT|5.006000||p is_an_int||| is_gv_magical_sv||| is_handle_constructor|||n is_list_assignment||| is_lvalue_sub||5.007001| is_uni_alnum_lc||5.006000| is_uni_alnumc_lc||5.006000| is_uni_alnumc||5.006000| is_uni_alnum||5.006000| is_uni_alpha_lc||5.006000| is_uni_alpha||5.006000| is_uni_ascii_lc||5.006000| is_uni_ascii||5.006000| is_uni_cntrl_lc||5.006000| is_uni_cntrl||5.006000| is_uni_digit_lc||5.006000| is_uni_digit||5.006000| is_uni_graph_lc||5.006000| is_uni_graph||5.006000| is_uni_idfirst_lc||5.006000| is_uni_idfirst||5.006000| is_uni_lower_lc||5.006000| is_uni_lower||5.006000| is_uni_print_lc||5.006000| is_uni_print||5.006000| is_uni_punct_lc||5.006000| is_uni_punct||5.006000| is_uni_space_lc||5.006000| is_uni_space||5.006000| is_uni_upper_lc||5.006000| is_uni_upper||5.006000| is_uni_xdigit_lc||5.006000| is_uni_xdigit||5.006000| is_utf8_alnumc||5.006000| is_utf8_alnum||5.006000| is_utf8_alpha||5.006000| is_utf8_ascii||5.006000| is_utf8_char_slow|||n is_utf8_char||5.006000| is_utf8_cntrl||5.006000| is_utf8_common||| is_utf8_digit||5.006000| is_utf8_graph||5.006000| is_utf8_idcont||5.008000| is_utf8_idfirst||5.006000| is_utf8_lower||5.006000| is_utf8_mark||5.006000| is_utf8_print||5.006000| is_utf8_punct||5.006000| is_utf8_space||5.006000| is_utf8_string_loclen||5.009003| is_utf8_string_loc||5.008001| is_utf8_string||5.006001| is_utf8_upper||5.006000| is_utf8_xdigit||5.006000| isa_lookup||| items|||n ix|||n jmaybe||| join_exact||| keyword||| leave_scope||| lex_end||| lex_start||| linklist||| listkids||| list||| load_module_nocontext|||vn load_module|5.006000||pv localize||| looks_like_bool||| looks_like_number||| lop||| mPUSHi|5.009002||p mPUSHn|5.009002||p mPUSHp|5.009002||p mPUSHs|5.011000||p mPUSHu|5.009002||p mXPUSHi|5.009002||p mXPUSHn|5.009002||p mXPUSHp|5.009002||p mXPUSHs|5.011000||p mXPUSHu|5.009002||p mad_free||| madlex||| madparse||| magic_clear_all_env||| magic_clearenv||| magic_clearhint||| magic_clearisa||| magic_clearpack||| magic_clearsig||| magic_dump||5.006000| magic_existspack||| magic_freearylen_p||| magic_freeovrld||| magic_getarylen||| magic_getdefelem||| magic_getnkeys||| magic_getpack||| magic_getpos||| magic_getsig||| magic_getsubstr||| magic_gettaint||| magic_getuvar||| magic_getvec||| magic_get||| magic_killbackrefs||| magic_len||| magic_methcall||| magic_methpack||| magic_nextpack||| magic_regdata_cnt||| magic_regdatum_get||| magic_regdatum_set||| magic_scalarpack||| magic_set_all_env||| magic_setamagic||| magic_setarylen||| magic_setcollxfrm||| magic_setdbline||| magic_setdefelem||| magic_setenv||| magic_sethint||| magic_setisa||| magic_setmglob||| magic_setnkeys||| magic_setpack||| magic_setpos||| magic_setregexp||| magic_setsig||| magic_setsubstr||| magic_settaint||| magic_setutf8||| magic_setuvar||| magic_setvec||| magic_set||| magic_sizepack||| magic_wipepack||| make_matcher||| make_trie_failtable||| make_trie||| malloc_good_size|||n malloced_size|||n malloc||5.007002|n markstack_grow||| matcher_matches_sv||| measure_struct||| memEQ|5.004000||p memNE|5.004000||p mem_collxfrm||| mem_log_common|||n mess_alloc||| mess_nocontext|||vn mess||5.006000|v method_common||| mfree||5.007002|n mg_clear||| mg_copy||| mg_dup||| mg_find||| mg_free||| mg_get||| mg_length||5.005000| mg_localize||| mg_magical||| mg_set||| mg_size||5.005000| mini_mktime||5.007002| missingterm||| mode_from_discipline||| modkids||| mod||| more_bodies||| more_sv||| moreswitches||| mro_get_from_name||5.011000| mro_get_linear_isa_dfs||| mro_get_linear_isa||5.009005| mro_get_private_data||5.011000| mro_isa_changed_in||| mro_meta_dup||| mro_meta_init||| mro_method_changed_in||5.009005| mro_register||5.011000| mro_set_mro||5.011000| mro_set_private_data||5.011000| mul128||| mulexp10|||n my_atof2||5.007002| my_atof||5.006000| my_attrs||| my_bcopy|||n my_betoh16|||n my_betoh32|||n my_betoh64|||n my_betohi|||n my_betohl|||n my_betohs|||n my_bzero|||n my_chsize||| my_clearenv||| my_cxt_index||| my_cxt_init||| my_dirfd||5.009005| my_exit_jump||| my_exit||| my_failure_exit||5.004000| my_fflush_all||5.006000| my_fork||5.007003|n my_htobe16|||n my_htobe32|||n my_htobe64|||n my_htobei|||n my_htobel|||n my_htobes|||n my_htole16|||n my_htole32|||n my_htole64|||n my_htolei|||n my_htolel|||n my_htoles|||n my_htonl||| my_kid||| my_letoh16|||n my_letoh32|||n my_letoh64|||n my_letohi|||n my_letohl|||n my_letohs|||n my_lstat||| my_memcmp||5.004000|n my_memset|||n my_ntohl||| my_pclose||5.004000| my_popen_list||5.007001| my_popen||5.004000| my_setenv||| my_snprintf|5.009004||pvn my_socketpair||5.007003|n my_sprintf|5.009003||pvn my_stat||| my_strftime||5.007002| my_strlcat|5.009004||pn my_strlcpy|5.009004||pn my_swabn|||n my_swap||| my_unexec||| my_vsnprintf||5.009004|n need_utf8|||n newANONATTRSUB||5.006000| newANONHASH||| newANONLIST||| newANONSUB||| newASSIGNOP||| newATTRSUB||5.006000| newAVREF||| newAV||| newBINOP||| newCONDOP||| newCONSTSUB|5.004050||p newCVREF||| newDEFSVOP||| newFORM||| newFOROP||| newGIVENOP||5.009003| newGIVWHENOP||| newGP||| newGVOP||| newGVREF||| newGVgen||| newHVREF||| newHVhv||5.005000| newHV||| newIO||| newLISTOP||| newLOGOP||| newLOOPEX||| newLOOPOP||| newMADPROP||| newMADsv||| newMYSUB||| newNULLLIST||| newOP||| newPADOP||| newPMOP||| newPROG||| newPVOP||| newRANGE||| newRV_inc|5.004000||p newRV_noinc|5.004000||p newRV||| newSLICEOP||| newSTATEOP||| newSUB||| newSVOP||| newSVREF||| newSV_type|5.009005||p newSVhek||5.009003| newSViv||| newSVnv||| newSVpvf_nocontext|||vn newSVpvf||5.004000|v newSVpvn_flags|5.011000||p newSVpvn_share|5.007001||p newSVpvn_utf8|5.011000||p newSVpvn|5.004050||p newSVpvs_flags|5.011000||p newSVpvs_share||5.009003| newSVpvs|5.009003||p newSVpv||| newSVrv||| newSVsv||| newSVuv|5.006000||p newSV||| newTOKEN||| newUNOP||| newWHENOP||5.009003| newWHILEOP||5.009003| newXS_flags||5.009004| newXSproto||5.006000| newXS||5.006000| new_collate||5.006000| new_constant||| new_ctype||5.006000| new_he||| new_logop||| new_numeric||5.006000| new_stackinfo||5.005000| new_version||5.009000| new_warnings_bitfield||| next_symbol||| nextargv||| nextchar||| ninstr||| no_bareword_allowed||| no_fh_allowed||| no_op||| not_a_number||| nothreadhook||5.008000| nuke_stacks||| num_overflow|||n offer_nice_chunk||| oopsAV||| oopsHV||| op_clear||| op_const_sv||| op_dump||5.006000| op_free||| op_getmad_weak||| op_getmad||| op_null||5.007002| op_refcnt_dec||| op_refcnt_inc||| op_refcnt_lock||5.009002| op_refcnt_unlock||5.009002| op_xmldump||| open_script||| pMY_CXT_|5.007003||p pMY_CXT|5.007003||p pTHX_|5.006000||p pTHX|5.006000||p packWARN|5.007003||p pack_cat||5.007003| pack_rec||| package||| packlist||5.008001| pad_add_anon||| pad_add_name||| pad_alloc||| pad_block_start||| pad_check_dup||| pad_compname_type||| pad_findlex||| pad_findmy||| pad_fixup_inner_anons||| pad_free||| pad_leavemy||| pad_new||| pad_peg|||n pad_push||| pad_reset||| pad_setsv||| pad_sv||5.011000| pad_swipe||| pad_tidy||| pad_undef||| parse_body||| parse_unicode_opts||| parser_dup||| parser_free||| path_is_absolute|||n peep||| pending_Slabs_to_ro||| perl_alloc_using|||n perl_alloc|||n perl_clone_using|||n perl_clone|||n perl_construct|||n perl_destruct||5.007003|n perl_free|||n perl_parse||5.006000|n perl_run|||n pidgone||| pm_description||| pmflag||| pmop_dump||5.006000| pmop_xmldump||| pmruntime||| pmtrans||| pop_scope||| pregcomp||5.009005| pregexec||| pregfree2||5.011000| pregfree||| prepend_elem||| prepend_madprops||| printbuf||| printf_nocontext|||vn process_special_blocks||| ptr_table_clear||5.009005| ptr_table_fetch||5.009005| ptr_table_find|||n ptr_table_free||5.009005| ptr_table_new||5.009005| ptr_table_split||5.009005| ptr_table_store||5.009005| push_scope||| put_byte||| pv_display|5.006000||p pv_escape|5.009004||p pv_pretty|5.009004||p pv_uni_display||5.007003| qerror||| qsortsvu||| re_compile||5.009005| re_croak2||| re_dup_guts||| re_intuit_start||5.009005| re_intuit_string||5.006000| readpipe_override||| realloc||5.007002|n reentrant_free||| reentrant_init||| reentrant_retry|||vn reentrant_size||| ref_array_or_hash||| refcounted_he_chain_2hv||| refcounted_he_fetch||| refcounted_he_free||| refcounted_he_new_common||| refcounted_he_new||| refcounted_he_value||| refkids||| refto||| ref||5.011000| reg_check_named_buff_matched||| reg_named_buff_all||5.009005| reg_named_buff_exists||5.009005| reg_named_buff_fetch||5.009005| reg_named_buff_firstkey||5.009005| reg_named_buff_iter||| reg_named_buff_nextkey||5.009005| reg_named_buff_scalar||5.009005| reg_named_buff||| reg_namedseq||| reg_node||| reg_numbered_buff_fetch||| reg_numbered_buff_length||| reg_numbered_buff_store||| reg_qr_package||| reg_recode||| reg_scan_name||| reg_skipcomment||| reg_temp_copy||| reganode||| regatom||| regbranch||| regclass_swash||5.009004| regclass||| regcppop||| regcppush||| regcurly|||n regdump_extflags||| regdump||5.005000| regdupe_internal||| regexec_flags||5.005000| regfree_internal||5.009005| reghop3|||n reghop4|||n reghopmaybe3|||n reginclass||| reginitcolors||5.006000| reginsert||| regmatch||| regnext||5.005000| regpiece||| regpposixcc||| regprop||| regrepeat||| regtail_study||| regtail||| regtry||| reguni||| regwhite|||n reg||| repeatcpy||| report_evil_fh||| report_uninit||| require_pv||5.006000| require_tie_mod||| restore_magic||| rninstr||| rsignal_restore||| rsignal_save||| rsignal_state||5.004000| rsignal||5.004000| run_body||| run_user_filter||| runops_debug||5.005000| runops_standard||5.005000| rvpv_dup||| rxres_free||| rxres_restore||| rxres_save||| safesyscalloc||5.006000|n safesysfree||5.006000|n safesysmalloc||5.006000|n safesysrealloc||5.006000|n same_dirent||| save_I16||5.004000| save_I32||| save_I8||5.006000| save_adelete||5.011000| save_aelem||5.004050| save_alloc||5.006000| save_aptr||| save_ary||| save_bool||5.008001| save_clearsv||| save_delete||| save_destructor_x||5.006000| save_destructor||5.006000| save_freeop||| save_freepv||| save_freesv||| save_generic_pvref||5.006001| save_generic_svref||5.005030| save_gp||5.004000| save_hash||| save_hek_flags|||n save_helem_flags||5.011000| save_helem||5.004050| save_hints||| save_hptr||| save_int||| save_item||| save_iv||5.005000| save_lines||| save_list||| save_long||| save_magic||| save_mortalizesv||5.007001| save_nogv||| save_op||| save_padsv_and_mortalize||5.011000| save_pptr||| save_pushi32ptr||| save_pushptri32ptr||| save_pushptrptr||| save_pushptr||5.011000| save_re_context||5.006000| save_scalar_at||| save_scalar||| save_set_svflags||5.009000| save_shared_pvref||5.007003| save_sptr||| save_svref||| save_vptr||5.006000| savepvn||| savepvs||5.009003| savepv||| savesharedpvn||5.009005| savesharedpv||5.007003| savestack_grow_cnt||5.008001| savestack_grow||| savesvpv||5.009002| sawparens||| scalar_mod_type|||n scalarboolean||| scalarkids||| scalarseq||| scalarvoid||| scalar||| scan_bin||5.006000| scan_commit||| scan_const||| scan_formline||| scan_heredoc||| scan_hex||| scan_ident||| scan_inputsymbol||| scan_num||5.007001| scan_oct||| scan_pat||| scan_str||| scan_subst||| scan_trans||| scan_version||5.009001| scan_vstring||5.009005| scan_word||| scope||| screaminstr||5.005000| search_const||| seed||5.008001| sequence_num||| sequence_tail||| sequence||| set_context||5.006000|n set_numeric_local||5.006000| set_numeric_radix||5.006000| set_numeric_standard||5.006000| setdefout||| share_hek_flags||| share_hek||5.004000| si_dup||| sighandler|||n simplify_sort||| skipspace0||| skipspace1||| skipspace2||| skipspace||| softref2xv||| sortcv_stacked||| sortcv_xsub||| sortcv||| sortsv_flags||5.009003| sortsv||5.007003| space_join_names_mortal||| ss_dup||| stack_grow||| start_force||| start_glob||| start_subparse||5.004000| stashpv_hvname_match||5.011000| stdize_locale||| store_cop_label||| strEQ||| strGE||| strGT||| strLE||| strLT||| strNE||| str_to_version||5.006000| strip_return||| strnEQ||| strnNE||| study_chunk||| sub_crush_depth||| sublex_done||| sublex_push||| sublex_start||| sv_2bool||| sv_2cv||| sv_2io||| sv_2iuv_common||| sv_2iuv_non_preserve||| sv_2iv_flags||5.009001| sv_2iv||| sv_2mortal||| sv_2num||| sv_2nv||| sv_2pv_flags|5.007002||p sv_2pv_nolen|5.006000||p sv_2pvbyte_nolen|5.006000||p sv_2pvbyte|5.006000||p sv_2pvutf8_nolen||5.006000| sv_2pvutf8||5.006000| sv_2pv||| sv_2uv_flags||5.009001| sv_2uv|5.004000||p sv_add_arena||| sv_add_backref||| sv_backoff||| sv_bless||| sv_cat_decode||5.008001| sv_catpv_mg|5.004050||p sv_catpvf_mg_nocontext|||pvn sv_catpvf_mg|5.006000|5.004000|pv sv_catpvf_nocontext|||vn sv_catpvf||5.004000|v sv_catpvn_flags||5.007002| sv_catpvn_mg|5.004050||p sv_catpvn_nomg|5.007002||p sv_catpvn||| sv_catpvs|5.009003||p sv_catpv||| sv_catsv_flags||5.007002| sv_catsv_mg|5.004050||p sv_catsv_nomg|5.007002||p sv_catsv||| sv_catxmlpvn||| sv_catxmlsv||| sv_chop||| sv_clean_all||| sv_clean_objs||| sv_clear||| sv_cmp_locale||5.004000| sv_cmp||| sv_collxfrm||| sv_compile_2op||5.008001| sv_copypv||5.007003| sv_dec||| sv_del_backref||| sv_derived_from||5.004000| sv_destroyable||5.010000| sv_does||5.009004| sv_dump||| sv_dup_inc_multiple||| sv_dup||| sv_eq||| sv_exp_grow||| sv_force_normal_flags||5.007001| sv_force_normal||5.006000| sv_free2||| sv_free_arenas||| sv_free||| sv_gets||5.004000| sv_grow||| sv_i_ncmp||| sv_inc||| sv_insert_flags||5.011000| sv_insert||| sv_isa||| sv_isobject||| sv_iv||5.005000| sv_kill_backrefs||| sv_len_utf8||5.006000| sv_len||| sv_magic_portable|5.011000|5.004000|p sv_magicext||5.007003| sv_magic||| sv_mortalcopy||| sv_ncmp||| sv_newmortal||| sv_newref||| sv_nolocking||5.007003| sv_nosharing||5.007003| sv_nounlocking||| sv_nv||5.005000| sv_peek||5.005000| sv_pos_b2u_midway||| sv_pos_b2u||5.006000| sv_pos_u2b_cached||| sv_pos_u2b_forwards|||n sv_pos_u2b_midway|||n sv_pos_u2b||5.006000| sv_pvbyten_force||5.006000| sv_pvbyten||5.006000| sv_pvbyte||5.006000| sv_pvn_force_flags|5.007002||p sv_pvn_force||| sv_pvn_nomg|5.007003|5.005000|p sv_pvn||5.005000| sv_pvutf8n_force||5.006000| sv_pvutf8n||5.006000| sv_pvutf8||5.006000| sv_pv||5.006000| sv_recode_to_utf8||5.007003| sv_reftype||| sv_release_COW||| sv_replace||| sv_report_used||| sv_reset||| sv_rvweaken||5.006000| sv_setiv_mg|5.004050||p sv_setiv||| sv_setnv_mg|5.006000||p sv_setnv||| sv_setpv_mg|5.004050||p sv_setpvf_mg_nocontext|||pvn sv_setpvf_mg|5.006000|5.004000|pv sv_setpvf_nocontext|||vn sv_setpvf||5.004000|v sv_setpviv_mg||5.008001| sv_setpviv||5.008001| sv_setpvn_mg|5.004050||p sv_setpvn||| sv_setpvs|5.009004||p sv_setpv||| sv_setref_iv||| sv_setref_nv||| sv_setref_pvn||| sv_setref_pv||| sv_setref_uv||5.007001| sv_setsv_cow||| sv_setsv_flags||5.007002| sv_setsv_mg|5.004050||p sv_setsv_nomg|5.007002||p sv_setsv||| sv_setuv_mg|5.004050||p sv_setuv|5.004000||p sv_tainted||5.004000| sv_taint||5.004000| sv_true||5.005000| sv_unglob||| sv_uni_display||5.007003| sv_unmagic||| sv_unref_flags||5.007001| sv_unref||| sv_untaint||5.004000| sv_upgrade||| sv_usepvn_flags||5.009004| sv_usepvn_mg|5.004050||p sv_usepvn||| sv_utf8_decode||5.006000| sv_utf8_downgrade||5.006000| sv_utf8_encode||5.006000| sv_utf8_upgrade_flags_grow||5.011000| sv_utf8_upgrade_flags||5.007002| sv_utf8_upgrade_nomg||5.007002| sv_utf8_upgrade||5.007001| sv_uv|5.005000||p sv_vcatpvf_mg|5.006000|5.004000|p sv_vcatpvfn||5.004000| sv_vcatpvf|5.006000|5.004000|p sv_vsetpvf_mg|5.006000|5.004000|p sv_vsetpvfn||5.004000| sv_vsetpvf|5.006000|5.004000|p sv_xmlpeek||| svtype||| swallow_bom||| swap_match_buff||| swash_fetch||5.007002| swash_get||| swash_init||5.006000| sys_init3||5.010000|n sys_init||5.010000|n sys_intern_clear||| sys_intern_dup||| sys_intern_init||| sys_term||5.010000|n taint_env||| taint_proper||| tmps_grow||5.006000| toLOWER||| toUPPER||| to_byte_substr||| to_uni_fold||5.007003| to_uni_lower_lc||5.006000| to_uni_lower||5.007003| to_uni_title_lc||5.006000| to_uni_title||5.007003| to_uni_upper_lc||5.006000| to_uni_upper||5.007003| to_utf8_case||5.007003| to_utf8_fold||5.007003| to_utf8_lower||5.007003| to_utf8_substr||| to_utf8_title||5.007003| to_utf8_upper||5.007003| token_free||| token_getmad||| tokenize_use||| tokeq||| tokereport||| too_few_arguments||| too_many_arguments||| uiv_2buf|||n unlnk||| unpack_rec||| unpack_str||5.007003| unpackstring||5.008001| unshare_hek_or_pvn||| unshare_hek||| unsharepvn||5.004000| unwind_handler_stack||| update_debugger_info||| upg_version||5.009005| usage||| utf16_to_utf8_reversed||5.006001| utf16_to_utf8||5.006001| utf8_distance||5.006000| utf8_hop||5.006000| utf8_length||5.007001| utf8_mg_pos_cache_update||| utf8_to_bytes||5.006001| utf8_to_uvchr||5.007001| utf8_to_uvuni||5.007001| utf8n_to_uvchr||| utf8n_to_uvuni||5.007001| utilize||| uvchr_to_utf8_flags||5.007003| uvchr_to_utf8||| uvuni_to_utf8_flags||5.007003| uvuni_to_utf8||5.007001| validate_suid||| varname||| vcmp||5.009000| vcroak||5.006000| vdeb||5.007003| vdie_common||| vdie_croak_common||| vdie||| vform||5.006000| visit||| vivify_defelem||| vivify_ref||| vload_module|5.006000||p vmess||5.006000| vnewSVpvf|5.006000|5.004000|p vnormal||5.009002| vnumify||5.009000| vstringify||5.009000| vverify||5.009003| vwarner||5.006000| vwarn||5.006000| wait4pid||| warn_nocontext|||vn warner_nocontext|||vn warner|5.006000|5.004000|pv warn|||v watch||| whichsig||| write_no_mem||| write_to_stderr||| xmldump_all||| xmldump_attr||| xmldump_eval||| xmldump_form||| xmldump_indent|||v xmldump_packsubs||| xmldump_sub||| xmldump_vindent||| yyerror||| yylex||| yyparse||| yywarn||| ); if (exists $opt{'list-unsupported'}) { my $f; for $f (sort { lc $a cmp lc $b } keys %API) { next unless $API{$f}{todo}; print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n"; } exit 0; } # Scan for possible replacement candidates my(%replace, %need, %hints, %warnings, %depends); my $replace = 0; my($hint, $define, $function); sub find_api { my $code = shift; $code =~ s{ / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*) | "[^"\\]*(?:\\.[^"\\]*)*" | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx; grep { exists $API{$_} } $code =~ /(\w+)/mg; } while () { if ($hint) { my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings; if (m{^\s*\*\s(.*?)\s*$}) { for (@{$hint->[1]}) { $h->{$_} ||= ''; # suppress warning with older perls $h->{$_} .= "$1\n"; } } else { undef $hint } } $hint = [$1, [split /,?\s+/, $2]] if m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$}; if ($define) { if ($define->[1] =~ /\\$/) { $define->[1] .= $_; } else { if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) { my @n = find_api($define->[1]); push @{$depends{$define->[0]}}, @n if @n } undef $define; } } $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)}; if ($function) { if (/^}/) { if (exists $API{$function->[0]}) { my @n = find_api($function->[1]); push @{$depends{$function->[0]}}, @n if @n } undef $function; } else { $function->[1] .= $_; } } $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)}; $replace = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$}; $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)}; $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce}; $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$}; if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) { my @deps = map { s/\s+//g; $_ } split /,/, $3; my $d; for $d (map { s/\s+//g; $_ } split /,/, $1) { push @{$depends{$d}}, @deps; } } $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)}; } for (values %depends) { my %s; $_ = [sort grep !$s{$_}++, @$_]; } if (exists $opt{'api-info'}) { my $f; my $count = 0; my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$"; for $f (sort { lc $a cmp lc $b } keys %API) { next unless $f =~ /$match/; print "\n=== $f ===\n\n"; my $info = 0; if ($API{$f}{base} || $API{$f}{todo}) { my $base = format_version($API{$f}{base} || $API{$f}{todo}); print "Supported at least starting from perl-$base.\n"; $info++; } if ($API{$f}{provided}) { my $todo = $API{$f}{todo} ? format_version($API{$f}{todo}) : "5.003"; print "Support by $ppport provided back to perl-$todo.\n"; print "Support needs to be explicitly requested by NEED_$f.\n" if exists $need{$f}; print "Depends on: ", join(', ', @{$depends{$f}}), ".\n" if exists $depends{$f}; print "\n$hints{$f}" if exists $hints{$f}; print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f}; $info++; } print "No portability information available.\n" unless $info; $count++; } $count or print "Found no API matching '$opt{'api-info'}'."; print "\n"; exit 0; } if (exists $opt{'list-provided'}) { my $f; for $f (sort { lc $a cmp lc $b } keys %API) { next unless $API{$f}{provided}; my @flags; push @flags, 'explicit' if exists $need{$f}; push @flags, 'depend' if exists $depends{$f}; push @flags, 'hint' if exists $hints{$f}; push @flags, 'warning' if exists $warnings{$f}; my $flags = @flags ? ' ['.join(', ', @flags).']' : ''; print "$f$flags\n"; } exit 0; } my @files; my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc ); my $srcext = join '|', map { quotemeta $_ } @srcext; if (@ARGV) { my %seen; for (@ARGV) { if (-e) { if (-f) { push @files, $_ unless $seen{$_}++; } else { warn "'$_' is not a file.\n" } } else { my @new = grep { -f } glob $_ or warn "'$_' does not exist.\n"; push @files, grep { !$seen{$_}++ } @new; } } } else { eval { require File::Find; File::Find::find(sub { $File::Find::name =~ /($srcext)$/i and push @files, $File::Find::name; }, '.'); }; if ($@) { @files = map { glob "*$_" } @srcext; } } if (!@ARGV || $opt{filter}) { my(@in, @out); my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files; for (@files) { my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i; push @{ $out ? \@out : \@in }, $_; } if (@ARGV && @out) { warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out); } @files = @in; } die "No input files given!\n" unless @files; my(%files, %global, %revreplace); %revreplace = reverse %replace; my $filename; my $patch_opened = 0; for $filename (@files) { unless (open IN, "<$filename") { warn "Unable to read from $filename: $!\n"; next; } info("Scanning $filename ..."); my $c = do { local $/; }; close IN; my %file = (orig => $c, changes => 0); # Temporarily remove C/XS comments and strings from the code my @ccom; $c =~ s{ ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]* | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* ) | ( ^$HS*\#[^\r\n]* | "[^"\\]*(?:\\.[^"\\]*)*" | '[^'\\]*(?:\\.[^'\\]*)*' | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) ) }{ defined $2 and push @ccom, $2; defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex; $file{ccom} = \@ccom; $file{code} = $c; $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m; my $func; for $func (keys %API) { my $match = $func; $match .= "|$revreplace{$func}" if exists $revreplace{$func}; if ($c =~ /\b(?:Perl_)?($match)\b/) { $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func}; $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/; if (exists $API{$func}{provided}) { $file{uses_provided}{$func}++; if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) { $file{uses}{$func}++; my @deps = rec_depend($func); if (@deps) { $file{uses_deps}{$func} = \@deps; for (@deps) { $file{uses}{$_} = 0 unless exists $file{uses}{$_}; } } for ($func, @deps) { $file{needs}{$_} = 'static' if exists $need{$_}; } } } if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) { if ($c =~ /\b$func\b/) { $file{uses_todo}{$func}++; } } } } while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) { if (exists $need{$2}) { $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++; } else { warning("Possibly wrong #define $1 in $filename") } } for (qw(uses needs uses_todo needed_global needed_static)) { for $func (keys %{$file{$_}}) { push @{$global{$_}{$func}}, $filename; } } $files{$filename} = \%file; } # Globally resolve NEED_'s my $need; for $need (keys %{$global{needs}}) { if (@{$global{needs}{$need}} > 1) { my @targets = @{$global{needs}{$need}}; my @t = grep $files{$_}{needed_global}{$need}, @targets; @targets = @t if @t; @t = grep /\.xs$/i, @targets; @targets = @t if @t; my $target = shift @targets; $files{$target}{needs}{$need} = 'global'; for (@{$global{needs}{$need}}) { $files{$_}{needs}{$need} = 'extern' if $_ ne $target; } } } for $filename (@files) { exists $files{$filename} or next; info("=== Analyzing $filename ==="); my %file = %{$files{$filename}}; my $func; my $c = $file{code}; my $warnings = 0; for $func (sort keys %{$file{uses_Perl}}) { if ($API{$func}{varargs}) { unless ($API{$func}{nothxarg}) { my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))} { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge); if ($changes) { warning("Doesn't pass interpreter argument aTHX to Perl_$func"); $file{changes} += $changes; } } } else { warning("Uses Perl_$func instead of $func"); $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*} {$func$1(}g); } } for $func (sort keys %{$file{uses_replace}}) { warning("Uses $func instead of $replace{$func}"); $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g); } for $func (sort keys %{$file{uses_provided}}) { if ($file{uses}{$func}) { if (exists $file{uses_deps}{$func}) { diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}})); } else { diag("Uses $func"); } } $warnings += hint($func); } unless ($opt{quiet}) { for $func (sort keys %{$file{uses_todo}}) { print "*** WARNING: Uses $func, which may not be portable below perl ", format_version($API{$func}{todo}), ", even with '$ppport'\n"; $warnings++; } } for $func (sort keys %{$file{needed_static}}) { my $message = ''; if (not exists $file{uses}{$func}) { $message = "No need to define NEED_$func if $func is never used"; } elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') { $message = "No need to define NEED_$func when already needed globally"; } if ($message) { diag($message); $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg); } } for $func (sort keys %{$file{needed_global}}) { my $message = ''; if (not exists $global{uses}{$func}) { $message = "No need to define NEED_${func}_GLOBAL if $func is never used"; } elsif (exists $file{needs}{$func}) { if ($file{needs}{$func} eq 'extern') { $message = "No need to define NEED_${func}_GLOBAL when already needed globally"; } elsif ($file{needs}{$func} eq 'static') { $message = "No need to define NEED_${func}_GLOBAL when only used in this file"; } } if ($message) { diag($message); $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg); } } $file{needs_inc_ppport} = keys %{$file{uses}}; if ($file{needs_inc_ppport}) { my $pp = ''; for $func (sort keys %{$file{needs}}) { my $type = $file{needs}{$func}; next if $type eq 'extern'; my $suffix = $type eq 'global' ? '_GLOBAL' : ''; unless (exists $file{"needed_$type"}{$func}) { if ($type eq 'global') { diag("Files [@{$global{needs}{$func}}] need $func, adding global request"); } else { diag("File needs $func, adding static request"); } $pp .= "#define NEED_$func$suffix\n"; } } if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) { $pp = ''; $file{changes}++; } unless ($file{has_inc_ppport}) { diag("Needs to include '$ppport'"); $pp .= qq(#include "$ppport"\n) } if ($pp) { $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms) || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m) || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m) || ($c =~ s/^/$pp/); } } else { if ($file{has_inc_ppport}) { diag("No need to include '$ppport'"); $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m); } } # put back in our C comments my $ix; my $cppc = 0; my @ccom = @{$file{ccom}}; for $ix (0 .. $#ccom) { if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) { $cppc++; $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/; } else { $c =~ s/$rccs$ix$rcce/$ccom[$ix]/; } } if ($cppc) { my $s = $cppc != 1 ? 's' : ''; warning("Uses $cppc C++ style comment$s, which is not portable"); } my $s = $warnings != 1 ? 's' : ''; my $warn = $warnings ? " ($warnings warning$s)" : ''; info("Analysis completed$warn"); if ($file{changes}) { if (exists $opt{copy}) { my $newfile = "$filename$opt{copy}"; if (-e $newfile) { error("'$newfile' already exists, refusing to write copy of '$filename'"); } else { local *F; if (open F, ">$newfile") { info("Writing copy of '$filename' with changes to '$newfile'"); print F $c; close F; } else { error("Cannot open '$newfile' for writing: $!"); } } } elsif (exists $opt{patch} || $opt{changes}) { if (exists $opt{patch}) { unless ($patch_opened) { if (open PATCH, ">$opt{patch}") { $patch_opened = 1; } else { error("Cannot open '$opt{patch}' for writing: $!"); delete $opt{patch}; $opt{changes} = 1; goto fallback; } } mydiff(\*PATCH, $filename, $c); } else { fallback: info("Suggested changes:"); mydiff(\*STDOUT, $filename, $c); } } else { my $s = $file{changes} == 1 ? '' : 's'; info("$file{changes} potentially required change$s detected"); } } else { info("Looks good"); } } close PATCH if $patch_opened; exit 0; sub try_use { eval "use @_;"; return $@ eq '' } sub mydiff { local *F = shift; my($file, $str) = @_; my $diff; if (exists $opt{diff}) { $diff = run_diff($opt{diff}, $file, $str); } if (!defined $diff and try_use('Text::Diff')) { $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' }); $diff = <
$tmp") { print F $str; close F; if (open F, "$prog $file $tmp |") { while () { s/\Q$tmp\E/$file.patched/; $diff .= $_; } close F; unlink $tmp; return $diff; } unlink $tmp; } else { error("Cannot open '$tmp' for writing: $!"); } return undef; } sub rec_depend { my($func, $seen) = @_; return () unless exists $depends{$func}; $seen = {%{$seen||{}}}; return () if $seen->{$func}++; my %s; grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}}; } sub parse_version { my $ver = shift; if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) { return ($1, $2, $3); } elsif ($ver !~ /^\d+\.[\d_]+$/) { die "cannot parse version '$ver'\n"; } $ver =~ s/_//g; $ver =~ s/$/000000/; my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; $v = int $v; $s = int $s; if ($r < 5 || ($r == 5 && $v < 6)) { if ($s % 10) { die "cannot parse version '$ver'\n"; } } return ($r, $v, $s); } sub format_version { my $ver = shift; $ver =~ s/$/000000/; my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; $v = int $v; $s = int $s; if ($r < 5 || ($r == 5 && $v < 6)) { if ($s % 10) { die "invalid version '$ver'\n"; } $s /= 10; $ver = sprintf "%d.%03d", $r, $v; $s > 0 and $ver .= sprintf "_%02d", $s; return $ver; } return sprintf "%d.%d.%d", $r, $v, $s; } sub info { $opt{quiet} and return; print @_, "\n"; } sub diag { $opt{quiet} and return; $opt{diag} and print @_, "\n"; } sub warning { $opt{quiet} and return; print "*** ", @_, "\n"; } sub error { print "*** ERROR: ", @_, "\n"; } my %given_hints; my %given_warnings; sub hint { $opt{quiet} and return; my $func = shift; my $rv = 0; if (exists $warnings{$func} && !$given_warnings{$func}++) { my $warn = $warnings{$func}; $warn =~ s!^!*** !mg; print "*** WARNING: $func\n", $warn; $rv++; } if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) { my $hint = $hints{$func}; $hint =~ s/^/ /mg; print " --- hint for $func ---\n", $hint; } $rv; } sub usage { my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms; my %M = ( 'I' => '*' ); $usage =~ s/^\s*perl\s+\S+/$^X $0/; $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g; print < }; my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms; $copy =~ s/^(?=\S+)/ /gms; $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms; $self =~ s/^SKIP.*(?=^__DATA__)/SKIP if (\@ARGV && \$ARGV[0] eq '--unstrip') { eval { require Devel::PPPort }; \$@ and die "Cannot require Devel::PPPort, please install.\\n"; if (eval \$Devel::PPPort::VERSION < $VERSION) { die "$0 was originally generated with Devel::PPPort $VERSION.\\n" . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n" . "Please install a newer version, or --unstrip will not work.\\n"; } Devel::PPPort::WriteFile(\$0); exit 0; } print <$0" or die "cannot strip $0: $!\n"; print OUT "$pl$c\n"; exit 0; } __DATA__ */ #ifndef _P_P_PORTABILITY_H_ #define _P_P_PORTABILITY_H_ #ifndef DPPP_NAMESPACE # define DPPP_NAMESPACE DPPP_ #endif #define DPPP_CAT2(x,y) CAT2(x,y) #define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name) #ifndef PERL_REVISION # if !defined(__PATCHLEVEL_H_INCLUDED__) && !(defined(PATCHLEVEL) && defined(SUBVERSION)) # define PERL_PATCHLEVEL_H_IMPLICIT # include # endif # if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL))) # include # endif # ifndef PERL_REVISION # define PERL_REVISION (5) /* Replace: 1 */ # define PERL_VERSION PATCHLEVEL # define PERL_SUBVERSION SUBVERSION /* Replace PERL_PATCHLEVEL with PERL_VERSION */ /* Replace: 0 */ # endif #endif #define _dpppDEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10)) #define PERL_BCDVERSION ((_dpppDEC2BCD(PERL_REVISION)<<24)|(_dpppDEC2BCD(PERL_VERSION)<<12)|_dpppDEC2BCD(PERL_SUBVERSION)) /* It is very unlikely that anyone will try to use this with Perl 6 (or greater), but who knows. */ #if PERL_REVISION != 5 # error ppport.h only works with Perl version 5 #endif /* PERL_REVISION != 5 */ #ifndef dTHR # define dTHR dNOOP #endif #ifndef dTHX # define dTHX dNOOP #endif #ifndef dTHXa # define dTHXa(x) dNOOP #endif #ifndef pTHX # define pTHX void #endif #ifndef pTHX_ # define pTHX_ #endif #ifndef aTHX # define aTHX #endif #ifndef aTHX_ # define aTHX_ #endif #if (PERL_BCDVERSION < 0x5006000) # ifdef USE_THREADS # define aTHXR thr # define aTHXR_ thr, # else # define aTHXR # define aTHXR_ # endif # define dTHXR dTHR #else # define aTHXR aTHX # define aTHXR_ aTHX_ # define dTHXR dTHX #endif #ifndef dTHXoa # define dTHXoa(x) dTHXa(x) #endif #ifdef I_LIMITS # include #endif #ifndef PERL_UCHAR_MIN # define PERL_UCHAR_MIN ((unsigned char)0) #endif #ifndef PERL_UCHAR_MAX # ifdef UCHAR_MAX # define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX) # else # ifdef MAXUCHAR # define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR) # else # define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0) # endif # endif #endif #ifndef PERL_USHORT_MIN # define PERL_USHORT_MIN ((unsigned short)0) #endif #ifndef PERL_USHORT_MAX # ifdef USHORT_MAX # define PERL_USHORT_MAX ((unsigned short)USHORT_MAX) # else # ifdef MAXUSHORT # define PERL_USHORT_MAX ((unsigned short)MAXUSHORT) # else # ifdef USHRT_MAX # define PERL_USHORT_MAX ((unsigned short)USHRT_MAX) # else # define PERL_USHORT_MAX ((unsigned short)~(unsigned)0) # endif # endif # endif #endif #ifndef PERL_SHORT_MAX # ifdef SHORT_MAX # define PERL_SHORT_MAX ((short)SHORT_MAX) # else # ifdef MAXSHORT /* Often used in */ # define PERL_SHORT_MAX ((short)MAXSHORT) # else # ifdef SHRT_MAX # define PERL_SHORT_MAX ((short)SHRT_MAX) # else # define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1)) # endif # endif # endif #endif #ifndef PERL_SHORT_MIN # ifdef SHORT_MIN # define PERL_SHORT_MIN ((short)SHORT_MIN) # else # ifdef MINSHORT # define PERL_SHORT_MIN ((short)MINSHORT) # else # ifdef SHRT_MIN # define PERL_SHORT_MIN ((short)SHRT_MIN) # else # define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3)) # endif # endif # endif #endif #ifndef PERL_UINT_MAX # ifdef UINT_MAX # define PERL_UINT_MAX ((unsigned int)UINT_MAX) # else # ifdef MAXUINT # define PERL_UINT_MAX ((unsigned int)MAXUINT) # else # define PERL_UINT_MAX (~(unsigned int)0) # endif # endif #endif #ifndef PERL_UINT_MIN # define PERL_UINT_MIN ((unsigned int)0) #endif #ifndef PERL_INT_MAX # ifdef INT_MAX # define PERL_INT_MAX ((int)INT_MAX) # else # ifdef MAXINT /* Often used in */ # define PERL_INT_MAX ((int)MAXINT) # else # define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1)) # endif # endif #endif #ifndef PERL_INT_MIN # ifdef INT_MIN # define PERL_INT_MIN ((int)INT_MIN) # else # ifdef MININT # define PERL_INT_MIN ((int)MININT) # else # define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3)) # endif # endif #endif #ifndef PERL_ULONG_MAX # ifdef ULONG_MAX # define PERL_ULONG_MAX ((unsigned long)ULONG_MAX) # else # ifdef MAXULONG # define PERL_ULONG_MAX ((unsigned long)MAXULONG) # else # define PERL_ULONG_MAX (~(unsigned long)0) # endif # endif #endif #ifndef PERL_ULONG_MIN # define PERL_ULONG_MIN ((unsigned long)0L) #endif #ifndef PERL_LONG_MAX # ifdef LONG_MAX # define PERL_LONG_MAX ((long)LONG_MAX) # else # ifdef MAXLONG # define PERL_LONG_MAX ((long)MAXLONG) # else # define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1)) # endif # endif #endif #ifndef PERL_LONG_MIN # ifdef LONG_MIN # define PERL_LONG_MIN ((long)LONG_MIN) # else # ifdef MINLONG # define PERL_LONG_MIN ((long)MINLONG) # else # define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3)) # endif # endif #endif #if defined(HAS_QUAD) && (defined(convex) || defined(uts)) # ifndef PERL_UQUAD_MAX # ifdef ULONGLONG_MAX # define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX) # else # ifdef MAXULONGLONG # define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG) # else # define PERL_UQUAD_MAX (~(unsigned long long)0) # endif # endif # endif # ifndef PERL_UQUAD_MIN # define PERL_UQUAD_MIN ((unsigned long long)0L) # endif # ifndef PERL_QUAD_MAX # ifdef LONGLONG_MAX # define PERL_QUAD_MAX ((long long)LONGLONG_MAX) # else # ifdef MAXLONGLONG # define PERL_QUAD_MAX ((long long)MAXLONGLONG) # else # define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1)) # endif # endif # endif # ifndef PERL_QUAD_MIN # ifdef LONGLONG_MIN # define PERL_QUAD_MIN ((long long)LONGLONG_MIN) # else # ifdef MINLONGLONG # define PERL_QUAD_MIN ((long long)MINLONGLONG) # else # define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3)) # endif # endif # endif #endif /* This is based on code from 5.003 perl.h */ #ifdef HAS_QUAD # ifdef cray #ifndef IVTYPE # define IVTYPE int #endif #ifndef IV_MIN # define IV_MIN PERL_INT_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_INT_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_UINT_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_UINT_MAX #endif # ifdef INTSIZE #ifndef IVSIZE # define IVSIZE INTSIZE #endif # endif # else # if defined(convex) || defined(uts) #ifndef IVTYPE # define IVTYPE long long #endif #ifndef IV_MIN # define IV_MIN PERL_QUAD_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_QUAD_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_UQUAD_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_UQUAD_MAX #endif # ifdef LONGLONGSIZE #ifndef IVSIZE # define IVSIZE LONGLONGSIZE #endif # endif # else #ifndef IVTYPE # define IVTYPE long #endif #ifndef IV_MIN # define IV_MIN PERL_LONG_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_LONG_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_ULONG_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_ULONG_MAX #endif # ifdef LONGSIZE #ifndef IVSIZE # define IVSIZE LONGSIZE #endif # endif # endif # endif #ifndef IVSIZE # define IVSIZE 8 #endif #ifndef PERL_QUAD_MIN # define PERL_QUAD_MIN IV_MIN #endif #ifndef PERL_QUAD_MAX # define PERL_QUAD_MAX IV_MAX #endif #ifndef PERL_UQUAD_MIN # define PERL_UQUAD_MIN UV_MIN #endif #ifndef PERL_UQUAD_MAX # define PERL_UQUAD_MAX UV_MAX #endif #else #ifndef IVTYPE # define IVTYPE long #endif #ifndef IV_MIN # define IV_MIN PERL_LONG_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_LONG_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_ULONG_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_ULONG_MAX #endif #endif #ifndef IVSIZE # ifdef LONGSIZE # define IVSIZE LONGSIZE # else # define IVSIZE 4 /* A bold guess, but the best we can make. */ # endif #endif #ifndef UVTYPE # define UVTYPE unsigned IVTYPE #endif #ifndef UVSIZE # define UVSIZE IVSIZE #endif #ifndef sv_setuv # define sv_setuv(sv, uv) \ STMT_START { \ UV TeMpUv = uv; \ if (TeMpUv <= IV_MAX) \ sv_setiv(sv, TeMpUv); \ else \ sv_setnv(sv, (double)TeMpUv); \ } STMT_END #endif #ifndef newSVuv # define newSVuv(uv) ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv)) #endif #ifndef sv_2uv # define sv_2uv(sv) ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv))) #endif #ifndef SvUVX # define SvUVX(sv) ((UV)SvIVX(sv)) #endif #ifndef SvUVXx # define SvUVXx(sv) SvUVX(sv) #endif #ifndef SvUV # define SvUV(sv) (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv)) #endif #ifndef SvUVx # define SvUVx(sv) ((PL_Sv = (sv)), SvUV(PL_Sv)) #endif /* Hint: sv_uv * Always use the SvUVx() macro instead of sv_uv(). */ #ifndef sv_uv # define sv_uv(sv) SvUVx(sv) #endif #if !defined(SvUOK) && defined(SvIOK_UV) # define SvUOK(sv) SvIOK_UV(sv) #endif #ifndef XST_mUV # define XST_mUV(i,v) (ST(i) = sv_2mortal(newSVuv(v)) ) #endif #ifndef XSRETURN_UV # define XSRETURN_UV(v) STMT_START { XST_mUV(0,v); XSRETURN(1); } STMT_END #endif #ifndef PUSHu # define PUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG; } STMT_END #endif #ifndef XPUSHu # define XPUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END #endif #ifdef HAS_MEMCMP #ifndef memNE # define memNE(s1,s2,l) (memcmp(s1,s2,l)) #endif #ifndef memEQ # define memEQ(s1,s2,l) (!memcmp(s1,s2,l)) #endif #else #ifndef memNE # define memNE(s1,s2,l) (bcmp(s1,s2,l)) #endif #ifndef memEQ # define memEQ(s1,s2,l) (!bcmp(s1,s2,l)) #endif #endif #ifndef MoveD # define MoveD(s,d,n,t) memmove((char*)(d),(char*)(s), (n) * sizeof(t)) #endif #ifndef CopyD # define CopyD(s,d,n,t) memcpy((char*)(d),(char*)(s), (n) * sizeof(t)) #endif #ifdef HAS_MEMSET #ifndef ZeroD # define ZeroD(d,n,t) memzero((char*)(d), (n) * sizeof(t)) #endif #else #ifndef ZeroD # define ZeroD(d,n,t) ((void)memzero((char*)(d), (n) * sizeof(t)), d) #endif #endif #ifndef PoisonWith # define PoisonWith(d,n,t,b) (void)memset((char*)(d), (U8)(b), (n) * sizeof(t)) #endif #ifndef PoisonNew # define PoisonNew(d,n,t) PoisonWith(d,n,t,0xAB) #endif #ifndef PoisonFree # define PoisonFree(d,n,t) PoisonWith(d,n,t,0xEF) #endif #ifndef Poison # define Poison(d,n,t) PoisonFree(d,n,t) #endif #ifndef Newx # define Newx(v,n,t) New(0,v,n,t) #endif #ifndef Newxc # define Newxc(v,n,t,c) Newc(0,v,n,t,c) #endif #ifndef Newxz # define Newxz(v,n,t) Newz(0,v,n,t) #endif #ifndef PERL_UNUSED_DECL # ifdef HASATTRIBUTE # if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER) # define PERL_UNUSED_DECL # else # define PERL_UNUSED_DECL __attribute__((unused)) # endif # else # define PERL_UNUSED_DECL # endif #endif #ifndef PERL_UNUSED_ARG # if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */ # include # define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x)) # else # define PERL_UNUSED_ARG(x) ((void)x) # endif #endif #ifndef PERL_UNUSED_VAR # define PERL_UNUSED_VAR(x) ((void)x) #endif #ifndef PERL_UNUSED_CONTEXT # ifdef USE_ITHREADS # define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl) # else # define PERL_UNUSED_CONTEXT # endif #endif #ifndef NOOP # define NOOP /*EMPTY*/(void)0 #endif #ifndef dNOOP # define dNOOP extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL #endif #ifndef NVTYPE # if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) # define NVTYPE long double # else # define NVTYPE double # endif typedef NVTYPE NV; #endif #ifndef INT2PTR # if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE) # define PTRV UV # define INT2PTR(any,d) (any)(d) # else # if PTRSIZE == LONGSIZE # define PTRV unsigned long # else # define PTRV unsigned # endif # define INT2PTR(any,d) (any)(PTRV)(d) # endif #endif #ifndef PTR2ul # if PTRSIZE == LONGSIZE # define PTR2ul(p) (unsigned long)(p) # else # define PTR2ul(p) INT2PTR(unsigned long,p) # endif #endif #ifndef PTR2nat # define PTR2nat(p) (PTRV)(p) #endif #ifndef NUM2PTR # define NUM2PTR(any,d) (any)PTR2nat(d) #endif #ifndef PTR2IV # define PTR2IV(p) INT2PTR(IV,p) #endif #ifndef PTR2UV # define PTR2UV(p) INT2PTR(UV,p) #endif #ifndef PTR2NV # define PTR2NV(p) NUM2PTR(NV,p) #endif #undef START_EXTERN_C #undef END_EXTERN_C #undef EXTERN_C #ifdef __cplusplus # define START_EXTERN_C extern "C" { # define END_EXTERN_C } # define EXTERN_C extern "C" #else # define START_EXTERN_C # define END_EXTERN_C # define EXTERN_C extern #endif #if defined(PERL_GCC_PEDANTIC) # ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN # define PERL_GCC_BRACE_GROUPS_FORBIDDEN # endif #endif #if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus) # ifndef PERL_USE_GCC_BRACE_GROUPS # define PERL_USE_GCC_BRACE_GROUPS # endif #endif #undef STMT_START #undef STMT_END #ifdef PERL_USE_GCC_BRACE_GROUPS # define STMT_START (void)( /* gcc supports ``({ STATEMENTS; })'' */ # define STMT_END ) #else # if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__) # define STMT_START if (1) # define STMT_END else (void)0 # else # define STMT_START do # define STMT_END while (0) # endif #endif #ifndef boolSV # define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no) #endif /* DEFSV appears first in 5.004_56 */ #ifndef DEFSV # define DEFSV GvSV(PL_defgv) #endif #ifndef SAVE_DEFSV # define SAVE_DEFSV SAVESPTR(GvSV(PL_defgv)) #endif #ifndef DEFSV_set # define DEFSV_set(sv) (DEFSV = (sv)) #endif /* Older perls (<=5.003) lack AvFILLp */ #ifndef AvFILLp # define AvFILLp AvFILL #endif #ifndef ERRSV # define ERRSV get_sv("@",FALSE) #endif /* Hint: gv_stashpvn * This function's backport doesn't support the length parameter, but * rather ignores it. Portability can only be ensured if the length * parameter is used for speed reasons, but the length can always be * correctly computed from the string argument. */ #ifndef gv_stashpvn # define gv_stashpvn(str,len,create) gv_stashpv(str,create) #endif /* Replace: 1 */ #ifndef get_cv # define get_cv perl_get_cv #endif #ifndef get_sv # define get_sv perl_get_sv #endif #ifndef get_av # define get_av perl_get_av #endif #ifndef get_hv # define get_hv perl_get_hv #endif /* Replace: 0 */ #ifndef dUNDERBAR # define dUNDERBAR dNOOP #endif #ifndef UNDERBAR # define UNDERBAR DEFSV #endif #ifndef dAX # define dAX I32 ax = MARK - PL_stack_base + 1 #endif #ifndef dITEMS # define dITEMS I32 items = SP - MARK #endif #ifndef dXSTARG # define dXSTARG SV * targ = sv_newmortal() #endif #ifndef dAXMARK # define dAXMARK I32 ax = POPMARK; \ register SV ** const mark = PL_stack_base + ax++ #endif #ifndef XSprePUSH # define XSprePUSH (sp = PL_stack_base + ax - 1) #endif #if (PERL_BCDVERSION < 0x5005000) # undef XSRETURN # define XSRETURN(off) \ STMT_START { \ PL_stack_sp = PL_stack_base + ax + ((off) - 1); \ return; \ } STMT_END #endif #ifndef XSPROTO # define XSPROTO(name) void name(pTHX_ CV* cv) #endif #ifndef SVfARG # define SVfARG(p) ((void*)(p)) #endif #ifndef PERL_ABS # define PERL_ABS(x) ((x) < 0 ? -(x) : (x)) #endif #ifndef dVAR # define dVAR dNOOP #endif #ifndef SVf # define SVf "_" #endif #ifndef UTF8_MAXBYTES # define UTF8_MAXBYTES UTF8_MAXLEN #endif #ifndef CPERLscope # define CPERLscope(x) x #endif #ifndef PERL_HASH # define PERL_HASH(hash,str,len) \ STMT_START { \ const char *s_PeRlHaSh = str; \ I32 i_PeRlHaSh = len; \ U32 hash_PeRlHaSh = 0; \ while (i_PeRlHaSh--) \ hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \ (hash) = hash_PeRlHaSh; \ } STMT_END #endif #ifndef PERLIO_FUNCS_DECL # ifdef PERLIO_FUNCS_CONST # define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs # define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs) # else # define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs # define PERLIO_FUNCS_CAST(funcs) (funcs) # endif #endif /* provide these typedefs for older perls */ #if (PERL_BCDVERSION < 0x5009003) # ifdef ARGSproto typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto); # else typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX); # endif typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*); #endif #ifndef isPSXSPC # define isPSXSPC(c) (isSPACE(c) || (c) == '\v') #endif #ifndef isBLANK # define isBLANK(c) ((c) == ' ' || (c) == '\t') #endif #ifdef EBCDIC #ifndef isALNUMC # define isALNUMC(c) isalnum(c) #endif #ifndef isASCII # define isASCII(c) isascii(c) #endif #ifndef isCNTRL # define isCNTRL(c) iscntrl(c) #endif #ifndef isGRAPH # define isGRAPH(c) isgraph(c) #endif #ifndef isPRINT # define isPRINT(c) isprint(c) #endif #ifndef isPUNCT # define isPUNCT(c) ispunct(c) #endif #ifndef isXDIGIT # define isXDIGIT(c) isxdigit(c) #endif #else # if (PERL_BCDVERSION < 0x5010000) /* Hint: isPRINT * The implementation in older perl versions includes all of the * isSPACE() characters, which is wrong. The version provided by * Devel::PPPort always overrides a present buggy version. */ # undef isPRINT # endif #ifndef isALNUMC # define isALNUMC(c) (isALPHA(c) || isDIGIT(c)) #endif #ifndef isASCII # define isASCII(c) ((c) <= 127) #endif #ifndef isCNTRL # define isCNTRL(c) ((c) < ' ' || (c) == 127) #endif #ifndef isGRAPH # define isGRAPH(c) (isALNUM(c) || isPUNCT(c)) #endif #ifndef isPRINT # define isPRINT(c) (((c) >= 32 && (c) < 127)) #endif #ifndef isPUNCT # define isPUNCT(c) (((c) >= 33 && (c) <= 47) || ((c) >= 58 && (c) <= 64) || ((c) >= 91 && (c) <= 96) || ((c) >= 123 && (c) <= 126)) #endif #ifndef isXDIGIT # define isXDIGIT(c) (isDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) #endif #endif #ifndef PERL_SIGNALS_UNSAFE_FLAG #define PERL_SIGNALS_UNSAFE_FLAG 0x0001 #if (PERL_BCDVERSION < 0x5008000) # define D_PPP_PERL_SIGNALS_INIT PERL_SIGNALS_UNSAFE_FLAG #else # define D_PPP_PERL_SIGNALS_INIT 0 #endif #if defined(NEED_PL_signals) static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT; #elif defined(NEED_PL_signals_GLOBAL) U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT; #else extern U32 DPPP_(my_PL_signals); #endif #define PL_signals DPPP_(my_PL_signals) #endif /* Hint: PL_ppaddr * Calling an op via PL_ppaddr requires passing a context argument * for threaded builds. Since the context argument is different for * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will * automatically be defined as the correct argument. */ #if (PERL_BCDVERSION <= 0x5005005) /* Replace: 1 */ # define PL_ppaddr ppaddr # define PL_no_modify no_modify /* Replace: 0 */ #endif #if (PERL_BCDVERSION <= 0x5004005) /* Replace: 1 */ # define PL_DBsignal DBsignal # define PL_DBsingle DBsingle # define PL_DBsub DBsub # define PL_DBtrace DBtrace # define PL_Sv Sv # define PL_bufend bufend # define PL_bufptr bufptr # define PL_compiling compiling # define PL_copline copline # define PL_curcop curcop # define PL_curstash curstash # define PL_debstash debstash # define PL_defgv defgv # define PL_diehook diehook # define PL_dirty dirty # define PL_dowarn dowarn # define PL_errgv errgv # define PL_error_count error_count # define PL_expect expect # define PL_hexdigit hexdigit # define PL_hints hints # define PL_in_my in_my # define PL_laststatval laststatval # define PL_lex_state lex_state # define PL_lex_stuff lex_stuff # define PL_linestr linestr # define PL_na na # define PL_perl_destruct_level perl_destruct_level # define PL_perldb perldb # define PL_rsfp_filters rsfp_filters # define PL_rsfp rsfp # define PL_stack_base stack_base # define PL_stack_sp stack_sp # define PL_statcache statcache # define PL_stdingv stdingv # define PL_sv_arenaroot sv_arenaroot # define PL_sv_no sv_no # define PL_sv_undef sv_undef # define PL_sv_yes sv_yes # define PL_tainted tainted # define PL_tainting tainting # define PL_tokenbuf tokenbuf /* Replace: 0 */ #endif /* Warning: PL_parser * For perl versions earlier than 5.9.5, this is an always * non-NULL dummy. Also, it cannot be dereferenced. Don't * use it if you can avoid is and unless you absolutely know * what you're doing. * If you always check that PL_parser is non-NULL, you can * define DPPP_PL_parser_NO_DUMMY to avoid the creation of * a dummy parser structure. */ #if (PERL_BCDVERSION >= 0x5009005) # ifdef DPPP_PL_parser_NO_DUMMY # define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \ (croak("panic: PL_parser == NULL in %s:%d", \ __FILE__, __LINE__), (yy_parser *) NULL))->var) # else # ifdef DPPP_PL_parser_NO_DUMMY_WARNING # define D_PPP_parser_dummy_warning(var) # else # define D_PPP_parser_dummy_warning(var) \ warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__), # endif # define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \ (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var) #if defined(NEED_PL_parser) static yy_parser DPPP_(dummy_PL_parser); #elif defined(NEED_PL_parser_GLOBAL) yy_parser DPPP_(dummy_PL_parser); #else extern yy_parser DPPP_(dummy_PL_parser); #endif # endif /* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */ /* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf * Do not use this variable unless you know exactly what you're * doint. It is internal to the perl parser and may change or even * be removed in the future. As of perl 5.9.5, you have to check * for (PL_parser != NULL) for this variable to have any effect. * An always non-NULL PL_parser dummy is provided for earlier * perl versions. * If PL_parser is NULL when you try to access this variable, a * dummy is being accessed instead and a warning is issued unless * you define DPPP_PL_parser_NO_DUMMY_WARNING. * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access * this variable will croak with a panic message. */ # define PL_expect D_PPP_my_PL_parser_var(expect) # define PL_copline D_PPP_my_PL_parser_var(copline) # define PL_rsfp D_PPP_my_PL_parser_var(rsfp) # define PL_rsfp_filters D_PPP_my_PL_parser_var(rsfp_filters) # define PL_linestr D_PPP_my_PL_parser_var(linestr) # define PL_bufptr D_PPP_my_PL_parser_var(bufptr) # define PL_bufend D_PPP_my_PL_parser_var(bufend) # define PL_lex_state D_PPP_my_PL_parser_var(lex_state) # define PL_lex_stuff D_PPP_my_PL_parser_var(lex_stuff) # define PL_tokenbuf D_PPP_my_PL_parser_var(tokenbuf) # define PL_in_my D_PPP_my_PL_parser_var(in_my) # define PL_in_my_stash D_PPP_my_PL_parser_var(in_my_stash) # define PL_error_count D_PPP_my_PL_parser_var(error_count) #else /* ensure that PL_parser != NULL and cannot be dereferenced */ # define PL_parser ((void *) 1) #endif #ifndef mPUSHs # define mPUSHs(s) PUSHs(sv_2mortal(s)) #endif #ifndef PUSHmortal # define PUSHmortal PUSHs(sv_newmortal()) #endif #ifndef mPUSHp # define mPUSHp(p,l) sv_setpvn(PUSHmortal, (p), (l)) #endif #ifndef mPUSHn # define mPUSHn(n) sv_setnv(PUSHmortal, (NV)(n)) #endif #ifndef mPUSHi # define mPUSHi(i) sv_setiv(PUSHmortal, (IV)(i)) #endif #ifndef mPUSHu # define mPUSHu(u) sv_setuv(PUSHmortal, (UV)(u)) #endif #ifndef mXPUSHs # define mXPUSHs(s) XPUSHs(sv_2mortal(s)) #endif #ifndef XPUSHmortal # define XPUSHmortal XPUSHs(sv_newmortal()) #endif #ifndef mXPUSHp # define mXPUSHp(p,l) STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END #endif #ifndef mXPUSHn # define mXPUSHn(n) STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END #endif #ifndef mXPUSHi # define mXPUSHi(i) STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END #endif #ifndef mXPUSHu # define mXPUSHu(u) STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END #endif /* Replace: 1 */ #ifndef call_sv # define call_sv perl_call_sv #endif #ifndef call_pv # define call_pv perl_call_pv #endif #ifndef call_argv # define call_argv perl_call_argv #endif #ifndef call_method # define call_method perl_call_method #endif #ifndef eval_sv # define eval_sv perl_eval_sv #endif /* Replace: 0 */ #ifndef PERL_LOADMOD_DENY # define PERL_LOADMOD_DENY 0x1 #endif #ifndef PERL_LOADMOD_NOIMPORT # define PERL_LOADMOD_NOIMPORT 0x2 #endif #ifndef PERL_LOADMOD_IMPORT_OPS # define PERL_LOADMOD_IMPORT_OPS 0x4 #endif #ifndef G_METHOD # define G_METHOD 64 # ifdef call_sv # undef call_sv # endif # if (PERL_BCDVERSION < 0x5006000) # define call_sv(sv, flags) ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \ (flags) & ~G_METHOD) : perl_call_sv(sv, flags)) # else # define call_sv(sv, flags) ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \ (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags)) # endif #endif /* Replace perl_eval_pv with eval_pv */ #ifndef eval_pv #if defined(NEED_eval_pv) static SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error); static #else extern SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error); #endif #ifdef eval_pv # undef eval_pv #endif #define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b) #define Perl_eval_pv DPPP_(my_eval_pv) #if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL) SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error) { dSP; SV* sv = newSVpv(p, 0); PUSHMARK(sp); eval_sv(sv, G_SCALAR); SvREFCNT_dec(sv); SPAGAIN; sv = POPs; PUTBACK; if (croak_on_error && SvTRUE(GvSV(errgv))) croak(SvPVx(GvSV(errgv), na)); return sv; } #endif #endif #ifndef vload_module #if defined(NEED_vload_module) static void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args); static #else extern void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args); #endif #ifdef vload_module # undef vload_module #endif #define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d) #define Perl_vload_module DPPP_(my_vload_module) #if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL) void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args) { dTHR; dVAR; OP *veop, *imop; OP * const modname = newSVOP(OP_CONST, 0, name); /* 5.005 has a somewhat hacky force_normal that doesn't croak on SvREADONLY() if PL_compling is true. Current perls take care in ck_require() to correctly turn off SvREADONLY before calling force_normal_flags(). This seems a better fix than fudging PL_compling */ SvREADONLY_off(((SVOP*)modname)->op_sv); modname->op_private |= OPpCONST_BARE; if (ver) { veop = newSVOP(OP_CONST, 0, ver); } else veop = NULL; if (flags & PERL_LOADMOD_NOIMPORT) { imop = sawparens(newNULLLIST()); } else if (flags & PERL_LOADMOD_IMPORT_OPS) { imop = va_arg(*args, OP*); } else { SV *sv; imop = NULL; sv = va_arg(*args, SV*); while (sv) { imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv)); sv = va_arg(*args, SV*); } } { const line_t ocopline = PL_copline; COP * const ocurcop = PL_curcop; const int oexpect = PL_expect; #if (PERL_BCDVERSION >= 0x5004000) utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0), veop, modname, imop); #else utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(), modname, imop); #endif PL_expect = oexpect; PL_copline = ocopline; PL_curcop = ocurcop; } } #endif #endif #ifndef load_module #if defined(NEED_load_module) static void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...); static #else extern void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...); #endif #ifdef load_module # undef load_module #endif #define load_module DPPP_(my_load_module) #define Perl_load_module DPPP_(my_load_module) #if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL) void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...) { va_list args; va_start(args, ver); vload_module(flags, name, ver, &args); va_end(args); } #endif #endif #ifndef newRV_inc # define newRV_inc(sv) newRV(sv) /* Replace */ #endif #ifndef newRV_noinc #if defined(NEED_newRV_noinc) static SV * DPPP_(my_newRV_noinc)(SV *sv); static #else extern SV * DPPP_(my_newRV_noinc)(SV *sv); #endif #ifdef newRV_noinc # undef newRV_noinc #endif #define newRV_noinc(a) DPPP_(my_newRV_noinc)(aTHX_ a) #define Perl_newRV_noinc DPPP_(my_newRV_noinc) #if defined(NEED_newRV_noinc) || defined(NEED_newRV_noinc_GLOBAL) SV * DPPP_(my_newRV_noinc)(SV *sv) { SV *rv = (SV *)newRV(sv); SvREFCNT_dec(sv); return rv; } #endif #endif /* Hint: newCONSTSUB * Returns a CV* as of perl-5.7.1. This return value is not supported * by Devel::PPPort. */ /* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */ #if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005) #if defined(NEED_newCONSTSUB) static void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv); static #else extern void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv); #endif #ifdef newCONSTSUB # undef newCONSTSUB #endif #define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c) #define Perl_newCONSTSUB DPPP_(my_newCONSTSUB) #if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL) /* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */ /* (There's no PL_parser in perl < 5.005, so this is completely safe) */ #define D_PPP_PL_copline PL_copline void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv) { U32 oldhints = PL_hints; HV *old_cop_stash = PL_curcop->cop_stash; HV *old_curstash = PL_curstash; line_t oldline = PL_curcop->cop_line; PL_curcop->cop_line = D_PPP_PL_copline; PL_hints &= ~HINT_BLOCK_SCOPE; if (stash) PL_curstash = PL_curcop->cop_stash = stash; newSUB( #if (PERL_BCDVERSION < 0x5003022) start_subparse(), #elif (PERL_BCDVERSION == 0x5003022) start_subparse(0), #else /* 5.003_23 onwards */ start_subparse(FALSE, 0), #endif newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)), newSVOP(OP_CONST, 0, &PL_sv_no), /* SvPV(&PL_sv_no) == "" -- GMB */ newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv)) ); PL_hints = oldhints; PL_curcop->cop_stash = old_cop_stash; PL_curstash = old_curstash; PL_curcop->cop_line = oldline; } #endif #endif /* * Boilerplate macros for initializing and accessing interpreter-local * data from C. All statics in extensions should be reworked to use * this, if you want to make the extension thread-safe. See ext/re/re.xs * for an example of the use of these macros. * * Code that uses these macros is responsible for the following: * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts" * 2. Declare a typedef named my_cxt_t that is a structure that contains * all the data that needs to be interpreter-local. * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t. * 4. Use the MY_CXT_INIT macro such that it is called exactly once * (typically put in the BOOT: section). * 5. Use the members of the my_cxt_t structure everywhere as * MY_CXT.member. * 6. Use the dMY_CXT macro (a declaration) in all the functions that * access MY_CXT. */ #if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \ defined(PERL_CAPI) || defined(PERL_IMPLICIT_CONTEXT) #ifndef START_MY_CXT /* This must appear in all extensions that define a my_cxt_t structure, * right after the definition (i.e. at file scope). The non-threads * case below uses it to declare the data as static. */ #define START_MY_CXT #if (PERL_BCDVERSION < 0x5004068) /* Fetches the SV that keeps the per-interpreter data. */ #define dMY_CXT_SV \ SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE) #else /* >= perl5.004_68 */ #define dMY_CXT_SV \ SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY, \ sizeof(MY_CXT_KEY)-1, TRUE) #endif /* < perl5.004_68 */ /* This declaration should be used within all functions that use the * interpreter-local data. */ #define dMY_CXT \ dMY_CXT_SV; \ my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv)) /* Creates and zeroes the per-interpreter data. * (We allocate my_cxtp in a Perl SV so that it will be released when * the interpreter goes away.) */ #define MY_CXT_INIT \ dMY_CXT_SV; \ /* newSV() allocates one more than needed */ \ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ Zero(my_cxtp, 1, my_cxt_t); \ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) /* This macro must be used to access members of the my_cxt_t structure. * e.g. MYCXT.some_data */ #define MY_CXT (*my_cxtp) /* Judicious use of these macros can reduce the number of times dMY_CXT * is used. Use is similar to pTHX, aTHX etc. */ #define pMY_CXT my_cxt_t *my_cxtp #define pMY_CXT_ pMY_CXT, #define _pMY_CXT ,pMY_CXT #define aMY_CXT my_cxtp #define aMY_CXT_ aMY_CXT, #define _aMY_CXT ,aMY_CXT #endif /* START_MY_CXT */ #ifndef MY_CXT_CLONE /* Clones the per-interpreter data. */ #define MY_CXT_CLONE \ dMY_CXT_SV; \ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) #endif #else /* single interpreter */ #ifndef START_MY_CXT #define START_MY_CXT static my_cxt_t my_cxt; #define dMY_CXT_SV dNOOP #define dMY_CXT dNOOP #define MY_CXT_INIT NOOP #define MY_CXT my_cxt #define pMY_CXT void #define pMY_CXT_ #define _pMY_CXT #define aMY_CXT #define aMY_CXT_ #define _aMY_CXT #endif /* START_MY_CXT */ #ifndef MY_CXT_CLONE #define MY_CXT_CLONE NOOP #endif #endif #ifndef IVdf # if IVSIZE == LONGSIZE # define IVdf "ld" # define UVuf "lu" # define UVof "lo" # define UVxf "lx" # define UVXf "lX" # else # if IVSIZE == INTSIZE # define IVdf "d" # define UVuf "u" # define UVof "o" # define UVxf "x" # define UVXf "X" # endif # endif #endif #ifndef NVef # if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \ defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000) /* Not very likely, but let's try anyway. */ # define NVef PERL_PRIeldbl # define NVff PERL_PRIfldbl # define NVgf PERL_PRIgldbl # else # define NVef "e" # define NVff "f" # define NVgf "g" # endif #endif #ifndef SvREFCNT_inc # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc(sv) \ ({ \ SV * const _sv = (SV*)(sv); \ if (_sv) \ (SvREFCNT(_sv))++; \ _sv; \ }) # else # define SvREFCNT_inc(sv) \ ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL) # endif #endif #ifndef SvREFCNT_inc_simple # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc_simple(sv) \ ({ \ if (sv) \ (SvREFCNT(sv))++; \ (SV *)(sv); \ }) # else # define SvREFCNT_inc_simple(sv) \ ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL) # endif #endif #ifndef SvREFCNT_inc_NN # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc_NN(sv) \ ({ \ SV * const _sv = (SV*)(sv); \ SvREFCNT(_sv)++; \ _sv; \ }) # else # define SvREFCNT_inc_NN(sv) \ (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv) # endif #endif #ifndef SvREFCNT_inc_void # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc_void(sv) \ ({ \ SV * const _sv = (SV*)(sv); \ if (_sv) \ (void)(SvREFCNT(_sv)++); \ }) # else # define SvREFCNT_inc_void(sv) \ (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0) # endif #endif #ifndef SvREFCNT_inc_simple_void # define SvREFCNT_inc_simple_void(sv) STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END #endif #ifndef SvREFCNT_inc_simple_NN # define SvREFCNT_inc_simple_NN(sv) (++SvREFCNT(sv), (SV*)(sv)) #endif #ifndef SvREFCNT_inc_void_NN # define SvREFCNT_inc_void_NN(sv) (void)(++SvREFCNT((SV*)(sv))) #endif #ifndef SvREFCNT_inc_simple_void_NN # define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv))) #endif #ifndef newSV_type #if defined(NEED_newSV_type) static SV* DPPP_(my_newSV_type)(pTHX_ svtype const t); static #else extern SV* DPPP_(my_newSV_type)(pTHX_ svtype const t); #endif #ifdef newSV_type # undef newSV_type #endif #define newSV_type(a) DPPP_(my_newSV_type)(aTHX_ a) #define Perl_newSV_type DPPP_(my_newSV_type) #if defined(NEED_newSV_type) || defined(NEED_newSV_type_GLOBAL) SV* DPPP_(my_newSV_type)(pTHX_ svtype const t) { SV* const sv = newSV(0); sv_upgrade(sv, t); return sv; } #endif #endif #if (PERL_BCDVERSION < 0x5006000) # define D_PPP_CONSTPV_ARG(x) ((char *) (x)) #else # define D_PPP_CONSTPV_ARG(x) (x) #endif #ifndef newSVpvn # define newSVpvn(data,len) ((data) \ ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \ : newSV(0)) #endif #ifndef newSVpvn_utf8 # define newSVpvn_utf8(s, len, u) newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0) #endif #ifndef SVf_UTF8 # define SVf_UTF8 0 #endif #ifndef newSVpvn_flags #if defined(NEED_newSVpvn_flags) static SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags); static #else extern SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags); #endif #ifdef newSVpvn_flags # undef newSVpvn_flags #endif #define newSVpvn_flags(a,b,c) DPPP_(my_newSVpvn_flags)(aTHX_ a,b,c) #define Perl_newSVpvn_flags DPPP_(my_newSVpvn_flags) #if defined(NEED_newSVpvn_flags) || defined(NEED_newSVpvn_flags_GLOBAL) SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags) { SV *sv = newSVpvn(D_PPP_CONSTPV_ARG(s), len); SvFLAGS(sv) |= (flags & SVf_UTF8); return (flags & SVs_TEMP) ? sv_2mortal(sv) : sv; } #endif #endif /* Backwards compatibility stuff... :-( */ #if !defined(NEED_sv_2pv_flags) && defined(NEED_sv_2pv_nolen) # define NEED_sv_2pv_flags #endif #if !defined(NEED_sv_2pv_flags_GLOBAL) && defined(NEED_sv_2pv_nolen_GLOBAL) # define NEED_sv_2pv_flags_GLOBAL #endif /* Hint: sv_2pv_nolen * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen(). */ #ifndef sv_2pv_nolen # define sv_2pv_nolen(sv) SvPV_nolen(sv) #endif #ifdef SvPVbyte /* Hint: SvPVbyte * Does not work in perl-5.6.1, ppport.h implements a version * borrowed from perl-5.7.3. */ #if (PERL_BCDVERSION < 0x5007000) #if defined(NEED_sv_2pvbyte) static char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp); static #else extern char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp); #endif #ifdef sv_2pvbyte # undef sv_2pvbyte #endif #define sv_2pvbyte(a,b) DPPP_(my_sv_2pvbyte)(aTHX_ a,b) #define Perl_sv_2pvbyte DPPP_(my_sv_2pvbyte) #if defined(NEED_sv_2pvbyte) || defined(NEED_sv_2pvbyte_GLOBAL) char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp) { sv_utf8_downgrade(sv,0); return SvPV(sv,*lp); } #endif /* Hint: sv_2pvbyte * Use the SvPVbyte() macro instead of sv_2pvbyte(). */ #undef SvPVbyte #define SvPVbyte(sv, lp) \ ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK) \ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp)) #endif #else # define SvPVbyte SvPV # define sv_2pvbyte sv_2pv #endif #ifndef sv_2pvbyte_nolen # define sv_2pvbyte_nolen(sv) sv_2pv_nolen(sv) #endif /* Hint: sv_pvn * Always use the SvPV() macro instead of sv_pvn(). */ /* Hint: sv_pvn_force * Always use the SvPV_force() macro instead of sv_pvn_force(). */ /* If these are undefined, they're not handled by the core anyway */ #ifndef SV_IMMEDIATE_UNREF # define SV_IMMEDIATE_UNREF 0 #endif #ifndef SV_GMAGIC # define SV_GMAGIC 0 #endif #ifndef SV_COW_DROP_PV # define SV_COW_DROP_PV 0 #endif #ifndef SV_UTF8_NO_ENCODING # define SV_UTF8_NO_ENCODING 0 #endif #ifndef SV_NOSTEAL # define SV_NOSTEAL 0 #endif #ifndef SV_CONST_RETURN # define SV_CONST_RETURN 0 #endif #ifndef SV_MUTABLE_RETURN # define SV_MUTABLE_RETURN 0 #endif #ifndef SV_SMAGIC # define SV_SMAGIC 0 #endif #ifndef SV_HAS_TRAILING_NUL # define SV_HAS_TRAILING_NUL 0 #endif #ifndef SV_COW_SHARED_HASH_KEYS # define SV_COW_SHARED_HASH_KEYS 0 #endif #if (PERL_BCDVERSION < 0x5007002) #if defined(NEED_sv_2pv_flags) static char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); static #else extern char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); #endif #ifdef sv_2pv_flags # undef sv_2pv_flags #endif #define sv_2pv_flags(a,b,c) DPPP_(my_sv_2pv_flags)(aTHX_ a,b,c) #define Perl_sv_2pv_flags DPPP_(my_sv_2pv_flags) #if defined(NEED_sv_2pv_flags) || defined(NEED_sv_2pv_flags_GLOBAL) char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags) { STRLEN n_a = (STRLEN) flags; return sv_2pv(sv, lp ? lp : &n_a); } #endif #if defined(NEED_sv_pvn_force_flags) static char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); static #else extern char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); #endif #ifdef sv_pvn_force_flags # undef sv_pvn_force_flags #endif #define sv_pvn_force_flags(a,b,c) DPPP_(my_sv_pvn_force_flags)(aTHX_ a,b,c) #define Perl_sv_pvn_force_flags DPPP_(my_sv_pvn_force_flags) #if defined(NEED_sv_pvn_force_flags) || defined(NEED_sv_pvn_force_flags_GLOBAL) char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags) { STRLEN n_a = (STRLEN) flags; return sv_pvn_force(sv, lp ? lp : &n_a); } #endif #endif #if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) ) # define DPPP_SVPV_NOLEN_LP_ARG &PL_na #else # define DPPP_SVPV_NOLEN_LP_ARG 0 #endif #ifndef SvPV_const # define SvPV_const(sv, lp) SvPV_flags_const(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_mutable # define SvPV_mutable(sv, lp) SvPV_flags_mutable(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_flags # define SvPV_flags(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags)) #endif #ifndef SvPV_flags_const # define SvPV_flags_const(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \ (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN)) #endif #ifndef SvPV_flags_const_nolen # define SvPV_flags_const_nolen(sv, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? SvPVX_const(sv) : \ (const char*) sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN)) #endif #ifndef SvPV_flags_mutable # define SvPV_flags_mutable(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \ sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN)) #endif #ifndef SvPV_force # define SvPV_force(sv, lp) SvPV_force_flags(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_force_nolen # define SvPV_force_nolen(sv) SvPV_force_flags_nolen(sv, SV_GMAGIC) #endif #ifndef SvPV_force_mutable # define SvPV_force_mutable(sv, lp) SvPV_force_flags_mutable(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_force_nomg # define SvPV_force_nomg(sv, lp) SvPV_force_flags(sv, lp, 0) #endif #ifndef SvPV_force_nomg_nolen # define SvPV_force_nomg_nolen(sv) SvPV_force_flags_nolen(sv, 0) #endif #ifndef SvPV_force_flags # define SvPV_force_flags(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags)) #endif #ifndef SvPV_force_flags_nolen # define SvPV_force_flags_nolen(sv, flags) \ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ ? SvPVX(sv) : sv_pvn_force_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags)) #endif #ifndef SvPV_force_flags_mutable # define SvPV_force_flags_mutable(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \ : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN)) #endif #ifndef SvPV_nolen # define SvPV_nolen(sv) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? SvPVX(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC)) #endif #ifndef SvPV_nolen_const # define SvPV_nolen_const(sv) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? SvPVX_const(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN)) #endif #ifndef SvPV_nomg # define SvPV_nomg(sv, lp) SvPV_flags(sv, lp, 0) #endif #ifndef SvPV_nomg_const # define SvPV_nomg_const(sv, lp) SvPV_flags_const(sv, lp, 0) #endif #ifndef SvPV_nomg_const_nolen # define SvPV_nomg_const_nolen(sv) SvPV_flags_const_nolen(sv, 0) #endif #ifndef SvPV_renew # define SvPV_renew(sv,n) STMT_START { SvLEN_set(sv, n); \ SvPV_set((sv), (char *) saferealloc( \ (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \ } STMT_END #endif #ifndef SvMAGIC_set # define SvMAGIC_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \ (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END #endif #if (PERL_BCDVERSION < 0x5009003) #ifndef SvPVX_const # define SvPVX_const(sv) ((const char*) (0 + SvPVX(sv))) #endif #ifndef SvPVX_mutable # define SvPVX_mutable(sv) (0 + SvPVX(sv)) #endif #ifndef SvRV_set # define SvRV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \ (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END #endif #else #ifndef SvPVX_const # define SvPVX_const(sv) ((const char*)((sv)->sv_u.svu_pv)) #endif #ifndef SvPVX_mutable # define SvPVX_mutable(sv) ((sv)->sv_u.svu_pv) #endif #ifndef SvRV_set # define SvRV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \ ((sv)->sv_u.svu_rv = (val)); } STMT_END #endif #endif #ifndef SvSTASH_set # define SvSTASH_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \ (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END #endif #if (PERL_BCDVERSION < 0x5004000) #ifndef SvUV_set # define SvUV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \ (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END #endif #else #ifndef SvUV_set # define SvUV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \ (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END #endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf) #if defined(NEED_vnewSVpvf) static SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args); static #else extern SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args); #endif #ifdef vnewSVpvf # undef vnewSVpvf #endif #define vnewSVpvf(a,b) DPPP_(my_vnewSVpvf)(aTHX_ a,b) #define Perl_vnewSVpvf DPPP_(my_vnewSVpvf) #if defined(NEED_vnewSVpvf) || defined(NEED_vnewSVpvf_GLOBAL) SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args) { register SV *sv = newSV(0); sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); return sv; } #endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf) # define sv_vcatpvf(sv, pat, args) sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)) #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf) # define sv_vsetpvf(sv, pat, args) sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)) #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg) #if defined(NEED_sv_catpvf_mg) static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...); #endif #define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg) #if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL) void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...) { va_list args; va_start(args, pat); sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #ifdef PERL_IMPLICIT_CONTEXT #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext) #if defined(NEED_sv_catpvf_mg_nocontext) static void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...); #endif #define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext) #define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext) #if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL) void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...) { dTHX; va_list args; va_start(args, pat); sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #endif /* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */ #ifndef sv_catpvf_mg # ifdef PERL_IMPLICIT_CONTEXT # define sv_catpvf_mg Perl_sv_catpvf_mg_nocontext # else # define sv_catpvf_mg Perl_sv_catpvf_mg # endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg) # define sv_vcatpvf_mg(sv, pat, args) \ STMT_START { \ sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \ SvSETMAGIC(sv); \ } STMT_END #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg) #if defined(NEED_sv_setpvf_mg) static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...); #endif #define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg) #if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL) void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...) { va_list args; va_start(args, pat); sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #ifdef PERL_IMPLICIT_CONTEXT #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext) #if defined(NEED_sv_setpvf_mg_nocontext) static void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...); #endif #define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext) #define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext) #if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL) void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...) { dTHX; va_list args; va_start(args, pat); sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #endif /* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */ #ifndef sv_setpvf_mg # ifdef PERL_IMPLICIT_CONTEXT # define sv_setpvf_mg Perl_sv_setpvf_mg_nocontext # else # define sv_setpvf_mg Perl_sv_setpvf_mg # endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg) # define sv_vsetpvf_mg(sv, pat, args) \ STMT_START { \ sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \ SvSETMAGIC(sv); \ } STMT_END #endif #ifndef newSVpvn_share #if defined(NEED_newSVpvn_share) static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash); static #else extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash); #endif #ifdef newSVpvn_share # undef newSVpvn_share #endif #define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c) #define Perl_newSVpvn_share DPPP_(my_newSVpvn_share) #if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL) SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash) { SV *sv; if (len < 0) len = -len; if (!hash) PERL_HASH(hash, (char*) src, len); sv = newSVpvn((char *) src, len); sv_upgrade(sv, SVt_PVIV); SvIVX(sv) = hash; SvREADONLY_on(sv); SvPOK_on(sv); return sv; } #endif #endif #ifndef SvSHARED_HASH # define SvSHARED_HASH(sv) (0 + SvUVX(sv)) #endif #ifndef HvNAME_get # define HvNAME_get(hv) HvNAME(hv) #endif #ifndef HvNAMELEN_get # define HvNAMELEN_get(hv) (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0) #endif #ifndef GvSVn # define GvSVn(gv) GvSV(gv) #endif #ifndef isGV_with_GP # define isGV_with_GP(gv) isGV(gv) #endif #ifndef WARN_ALL # define WARN_ALL 0 #endif #ifndef WARN_CLOSURE # define WARN_CLOSURE 1 #endif #ifndef WARN_DEPRECATED # define WARN_DEPRECATED 2 #endif #ifndef WARN_EXITING # define WARN_EXITING 3 #endif #ifndef WARN_GLOB # define WARN_GLOB 4 #endif #ifndef WARN_IO # define WARN_IO 5 #endif #ifndef WARN_CLOSED # define WARN_CLOSED 6 #endif #ifndef WARN_EXEC # define WARN_EXEC 7 #endif #ifndef WARN_LAYER # define WARN_LAYER 8 #endif #ifndef WARN_NEWLINE # define WARN_NEWLINE 9 #endif #ifndef WARN_PIPE # define WARN_PIPE 10 #endif #ifndef WARN_UNOPENED # define WARN_UNOPENED 11 #endif #ifndef WARN_MISC # define WARN_MISC 12 #endif #ifndef WARN_NUMERIC # define WARN_NUMERIC 13 #endif #ifndef WARN_ONCE # define WARN_ONCE 14 #endif #ifndef WARN_OVERFLOW # define WARN_OVERFLOW 15 #endif #ifndef WARN_PACK # define WARN_PACK 16 #endif #ifndef WARN_PORTABLE # define WARN_PORTABLE 17 #endif #ifndef WARN_RECURSION # define WARN_RECURSION 18 #endif #ifndef WARN_REDEFINE # define WARN_REDEFINE 19 #endif #ifndef WARN_REGEXP # define WARN_REGEXP 20 #endif #ifndef WARN_SEVERE # define WARN_SEVERE 21 #endif #ifndef WARN_DEBUGGING # define WARN_DEBUGGING 22 #endif #ifndef WARN_INPLACE # define WARN_INPLACE 23 #endif #ifndef WARN_INTERNAL # define WARN_INTERNAL 24 #endif #ifndef WARN_MALLOC # define WARN_MALLOC 25 #endif #ifndef WARN_SIGNAL # define WARN_SIGNAL 26 #endif #ifndef WARN_SUBSTR # define WARN_SUBSTR 27 #endif #ifndef WARN_SYNTAX # define WARN_SYNTAX 28 #endif #ifndef WARN_AMBIGUOUS # define WARN_AMBIGUOUS 29 #endif #ifndef WARN_BAREWORD # define WARN_BAREWORD 30 #endif #ifndef WARN_DIGIT # define WARN_DIGIT 31 #endif #ifndef WARN_PARENTHESIS # define WARN_PARENTHESIS 32 #endif #ifndef WARN_PRECEDENCE # define WARN_PRECEDENCE 33 #endif #ifndef WARN_PRINTF # define WARN_PRINTF 34 #endif #ifndef WARN_PROTOTYPE # define WARN_PROTOTYPE 35 #endif #ifndef WARN_QW # define WARN_QW 36 #endif #ifndef WARN_RESERVED # define WARN_RESERVED 37 #endif #ifndef WARN_SEMICOLON # define WARN_SEMICOLON 38 #endif #ifndef WARN_TAINT # define WARN_TAINT 39 #endif #ifndef WARN_THREADS # define WARN_THREADS 40 #endif #ifndef WARN_UNINITIALIZED # define WARN_UNINITIALIZED 41 #endif #ifndef WARN_UNPACK # define WARN_UNPACK 42 #endif #ifndef WARN_UNTIE # define WARN_UNTIE 43 #endif #ifndef WARN_UTF8 # define WARN_UTF8 44 #endif #ifndef WARN_VOID # define WARN_VOID 45 #endif #ifndef WARN_ASSERTIONS # define WARN_ASSERTIONS 46 #endif #ifndef packWARN # define packWARN(a) (a) #endif #ifndef ckWARN # ifdef G_WARN_ON # define ckWARN(a) (PL_dowarn & G_WARN_ON) # else # define ckWARN(a) PL_dowarn # endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(warner) #if defined(NEED_warner) static void DPPP_(my_warner)(U32 err, const char *pat, ...); static #else extern void DPPP_(my_warner)(U32 err, const char *pat, ...); #endif #define Perl_warner DPPP_(my_warner) #if defined(NEED_warner) || defined(NEED_warner_GLOBAL) void DPPP_(my_warner)(U32 err, const char *pat, ...) { SV *sv; va_list args; PERL_UNUSED_ARG(err); va_start(args, pat); sv = vnewSVpvf(pat, &args); va_end(args); sv_2mortal(sv); warn("%s", SvPV_nolen(sv)); } #define warner Perl_warner #define Perl_warner_nocontext Perl_warner #endif #endif /* concatenating with "" ensures that only literal strings are accepted as argument * note that STR_WITH_LEN() can't be used as argument to macros or functions that * under some configurations might be macros */ #ifndef STR_WITH_LEN # define STR_WITH_LEN(s) (s ""), (sizeof(s)-1) #endif #ifndef newSVpvs # define newSVpvs(str) newSVpvn(str "", sizeof(str) - 1) #endif #ifndef newSVpvs_flags # define newSVpvs_flags(str, flags) newSVpvn_flags(str "", sizeof(str) - 1, flags) #endif #ifndef sv_catpvs # define sv_catpvs(sv, str) sv_catpvn(sv, str "", sizeof(str) - 1) #endif #ifndef sv_setpvs # define sv_setpvs(sv, str) sv_setpvn(sv, str "", sizeof(str) - 1) #endif #ifndef hv_fetchs # define hv_fetchs(hv, key, lval) hv_fetch(hv, key "", sizeof(key) - 1, lval) #endif #ifndef hv_stores # define hv_stores(hv, key, val) hv_store(hv, key "", sizeof(key) - 1, val, 0) #endif #ifndef gv_fetchpvn_flags # define gv_fetchpvn_flags(name, len, flags, svt) gv_fetchpv(name, flags, svt) #endif #ifndef gv_fetchpvs # define gv_fetchpvs(name, flags, svt) gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt) #endif #ifndef gv_stashpvs # define gv_stashpvs(name, flags) gv_stashpvn(name "", sizeof(name) - 1, flags) #endif #ifndef SvGETMAGIC # define SvGETMAGIC(x) STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END #endif #ifndef PERL_MAGIC_sv # define PERL_MAGIC_sv '\0' #endif #ifndef PERL_MAGIC_overload # define PERL_MAGIC_overload 'A' #endif #ifndef PERL_MAGIC_overload_elem # define PERL_MAGIC_overload_elem 'a' #endif #ifndef PERL_MAGIC_overload_table # define PERL_MAGIC_overload_table 'c' #endif #ifndef PERL_MAGIC_bm # define PERL_MAGIC_bm 'B' #endif #ifndef PERL_MAGIC_regdata # define PERL_MAGIC_regdata 'D' #endif #ifndef PERL_MAGIC_regdatum # define PERL_MAGIC_regdatum 'd' #endif #ifndef PERL_MAGIC_env # define PERL_MAGIC_env 'E' #endif #ifndef PERL_MAGIC_envelem # define PERL_MAGIC_envelem 'e' #endif #ifndef PERL_MAGIC_fm # define PERL_MAGIC_fm 'f' #endif #ifndef PERL_MAGIC_regex_global # define PERL_MAGIC_regex_global 'g' #endif #ifndef PERL_MAGIC_isa # define PERL_MAGIC_isa 'I' #endif #ifndef PERL_MAGIC_isaelem # define PERL_MAGIC_isaelem 'i' #endif #ifndef PERL_MAGIC_nkeys # define PERL_MAGIC_nkeys 'k' #endif #ifndef PERL_MAGIC_dbfile # define PERL_MAGIC_dbfile 'L' #endif #ifndef PERL_MAGIC_dbline # define PERL_MAGIC_dbline 'l' #endif #ifndef PERL_MAGIC_mutex # define PERL_MAGIC_mutex 'm' #endif #ifndef PERL_MAGIC_shared # define PERL_MAGIC_shared 'N' #endif #ifndef PERL_MAGIC_shared_scalar # define PERL_MAGIC_shared_scalar 'n' #endif #ifndef PERL_MAGIC_collxfrm # define PERL_MAGIC_collxfrm 'o' #endif #ifndef PERL_MAGIC_tied # define PERL_MAGIC_tied 'P' #endif #ifndef PERL_MAGIC_tiedelem # define PERL_MAGIC_tiedelem 'p' #endif #ifndef PERL_MAGIC_tiedscalar # define PERL_MAGIC_tiedscalar 'q' #endif #ifndef PERL_MAGIC_qr # define PERL_MAGIC_qr 'r' #endif #ifndef PERL_MAGIC_sig # define PERL_MAGIC_sig 'S' #endif #ifndef PERL_MAGIC_sigelem # define PERL_MAGIC_sigelem 's' #endif #ifndef PERL_MAGIC_taint # define PERL_MAGIC_taint 't' #endif #ifndef PERL_MAGIC_uvar # define PERL_MAGIC_uvar 'U' #endif #ifndef PERL_MAGIC_uvar_elem # define PERL_MAGIC_uvar_elem 'u' #endif #ifndef PERL_MAGIC_vstring # define PERL_MAGIC_vstring 'V' #endif #ifndef PERL_MAGIC_vec # define PERL_MAGIC_vec 'v' #endif #ifndef PERL_MAGIC_utf8 # define PERL_MAGIC_utf8 'w' #endif #ifndef PERL_MAGIC_substr # define PERL_MAGIC_substr 'x' #endif #ifndef PERL_MAGIC_defelem # define PERL_MAGIC_defelem 'y' #endif #ifndef PERL_MAGIC_glob # define PERL_MAGIC_glob '*' #endif #ifndef PERL_MAGIC_arylen # define PERL_MAGIC_arylen '#' #endif #ifndef PERL_MAGIC_pos # define PERL_MAGIC_pos '.' #endif #ifndef PERL_MAGIC_backref # define PERL_MAGIC_backref '<' #endif #ifndef PERL_MAGIC_ext # define PERL_MAGIC_ext '~' #endif /* That's the best we can do... */ #ifndef sv_catpvn_nomg # define sv_catpvn_nomg sv_catpvn #endif #ifndef sv_catsv_nomg # define sv_catsv_nomg sv_catsv #endif #ifndef sv_setsv_nomg # define sv_setsv_nomg sv_setsv #endif #ifndef sv_pvn_nomg # define sv_pvn_nomg sv_pvn #endif #ifndef SvIV_nomg # define SvIV_nomg SvIV #endif #ifndef SvUV_nomg # define SvUV_nomg SvUV #endif #ifndef sv_catpv_mg # define sv_catpv_mg(sv, ptr) \ STMT_START { \ SV *TeMpSv = sv; \ sv_catpv(TeMpSv,ptr); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_catpvn_mg # define sv_catpvn_mg(sv, ptr, len) \ STMT_START { \ SV *TeMpSv = sv; \ sv_catpvn(TeMpSv,ptr,len); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_catsv_mg # define sv_catsv_mg(dsv, ssv) \ STMT_START { \ SV *TeMpSv = dsv; \ sv_catsv(TeMpSv,ssv); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setiv_mg # define sv_setiv_mg(sv, i) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setiv(TeMpSv,i); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setnv_mg # define sv_setnv_mg(sv, num) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setnv(TeMpSv,num); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setpv_mg # define sv_setpv_mg(sv, ptr) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setpv(TeMpSv,ptr); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setpvn_mg # define sv_setpvn_mg(sv, ptr, len) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setpvn(TeMpSv,ptr,len); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setsv_mg # define sv_setsv_mg(dsv, ssv) \ STMT_START { \ SV *TeMpSv = dsv; \ sv_setsv(TeMpSv,ssv); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setuv_mg # define sv_setuv_mg(sv, i) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setuv(TeMpSv,i); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_usepvn_mg # define sv_usepvn_mg(sv, ptr, len) \ STMT_START { \ SV *TeMpSv = sv; \ sv_usepvn(TeMpSv,ptr,len); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef SvVSTRING_mg # define SvVSTRING_mg(sv) (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL) #endif /* Hint: sv_magic_portable * This is a compatibility function that is only available with * Devel::PPPort. It is NOT in the perl core. * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when * it is being passed a name pointer with namlen == 0. In that * case, perl 5.8.0 and later store the pointer, not a copy of it. * The compatibility can be provided back to perl 5.004. With * earlier versions, the code will not compile. */ #if (PERL_BCDVERSION < 0x5004000) /* code that uses sv_magic_portable will not compile */ #elif (PERL_BCDVERSION < 0x5008000) # define sv_magic_portable(sv, obj, how, name, namlen) \ STMT_START { \ SV *SvMp_sv = (sv); \ char *SvMp_name = (char *) (name); \ I32 SvMp_namlen = (namlen); \ if (SvMp_name && SvMp_namlen == 0) \ { \ MAGIC *mg; \ sv_magic(SvMp_sv, obj, how, 0, 0); \ mg = SvMAGIC(SvMp_sv); \ mg->mg_len = -42; /* XXX: this is the tricky part */ \ mg->mg_ptr = SvMp_name; \ } \ else \ { \ sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \ } \ } STMT_END #else # define sv_magic_portable(a, b, c, d, e) sv_magic(a, b, c, d, e) #endif #ifdef USE_ITHREADS #ifndef CopFILE # define CopFILE(c) ((c)->cop_file) #endif #ifndef CopFILEGV # define CopFILEGV(c) (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv) #endif #ifndef CopFILE_set # define CopFILE_set(c,pv) ((c)->cop_file = savepv(pv)) #endif #ifndef CopFILESV # define CopFILESV(c) (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv) #endif #ifndef CopFILEAV # define CopFILEAV(c) (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav) #endif #ifndef CopSTASHPV # define CopSTASHPV(c) ((c)->cop_stashpv) #endif #ifndef CopSTASHPV_set # define CopSTASHPV_set(c,pv) ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch)) #endif #ifndef CopSTASH # define CopSTASH(c) (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv) #endif #ifndef CopSTASH_set # define CopSTASH_set(c,hv) CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch) #endif #ifndef CopSTASH_eq # define CopSTASH_eq(c,hv) ((hv) && (CopSTASHPV(c) == HvNAME(hv) \ || (CopSTASHPV(c) && HvNAME(hv) \ && strEQ(CopSTASHPV(c), HvNAME(hv))))) #endif #else #ifndef CopFILEGV # define CopFILEGV(c) ((c)->cop_filegv) #endif #ifndef CopFILEGV_set # define CopFILEGV_set(c,gv) ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv)) #endif #ifndef CopFILE_set # define CopFILE_set(c,pv) CopFILEGV_set((c), gv_fetchfile(pv)) #endif #ifndef CopFILESV # define CopFILESV(c) (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv) #endif #ifndef CopFILEAV # define CopFILEAV(c) (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav) #endif #ifndef CopFILE # define CopFILE(c) (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch) #endif #ifndef CopSTASH # define CopSTASH(c) ((c)->cop_stash) #endif #ifndef CopSTASH_set # define CopSTASH_set(c,hv) ((c)->cop_stash = (hv)) #endif #ifndef CopSTASHPV # define CopSTASHPV(c) (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch) #endif #ifndef CopSTASHPV_set # define CopSTASHPV_set(c,pv) CopSTASH_set((c), gv_stashpv(pv,GV_ADD)) #endif #ifndef CopSTASH_eq # define CopSTASH_eq(c,hv) (CopSTASH(c) == (hv)) #endif #endif /* USE_ITHREADS */ #ifndef IN_PERL_COMPILETIME # define IN_PERL_COMPILETIME (PL_curcop == &PL_compiling) #endif #ifndef IN_LOCALE_RUNTIME # define IN_LOCALE_RUNTIME (PL_curcop->op_private & HINT_LOCALE) #endif #ifndef IN_LOCALE_COMPILETIME # define IN_LOCALE_COMPILETIME (PL_hints & HINT_LOCALE) #endif #ifndef IN_LOCALE # define IN_LOCALE (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME) #endif #ifndef IS_NUMBER_IN_UV # define IS_NUMBER_IN_UV 0x01 #endif #ifndef IS_NUMBER_GREATER_THAN_UV_MAX # define IS_NUMBER_GREATER_THAN_UV_MAX 0x02 #endif #ifndef IS_NUMBER_NOT_INT # define IS_NUMBER_NOT_INT 0x04 #endif #ifndef IS_NUMBER_NEG # define IS_NUMBER_NEG 0x08 #endif #ifndef IS_NUMBER_INFINITY # define IS_NUMBER_INFINITY 0x10 #endif #ifndef IS_NUMBER_NAN # define IS_NUMBER_NAN 0x20 #endif #ifndef GROK_NUMERIC_RADIX # define GROK_NUMERIC_RADIX(sp, send) grok_numeric_radix(sp, send) #endif #ifndef PERL_SCAN_GREATER_THAN_UV_MAX # define PERL_SCAN_GREATER_THAN_UV_MAX 0x02 #endif #ifndef PERL_SCAN_SILENT_ILLDIGIT # define PERL_SCAN_SILENT_ILLDIGIT 0x04 #endif #ifndef PERL_SCAN_ALLOW_UNDERSCORES # define PERL_SCAN_ALLOW_UNDERSCORES 0x01 #endif #ifndef PERL_SCAN_DISALLOW_PREFIX # define PERL_SCAN_DISALLOW_PREFIX 0x02 #endif #ifndef grok_numeric_radix #if defined(NEED_grok_numeric_radix) static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send); static #else extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send); #endif #ifdef grok_numeric_radix # undef grok_numeric_radix #endif #define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b) #define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix) #if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL) bool DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send) { #ifdef USE_LOCALE_NUMERIC #ifdef PL_numeric_radix_sv if (PL_numeric_radix_sv && IN_LOCALE) { STRLEN len; char* radix = SvPV(PL_numeric_radix_sv, len); if (*sp + len <= send && memEQ(*sp, radix, len)) { *sp += len; return TRUE; } } #else /* older perls don't have PL_numeric_radix_sv so the radix * must manually be requested from locale.h */ #include dTHR; /* needed for older threaded perls */ struct lconv *lc = localeconv(); char *radix = lc->decimal_point; if (radix && IN_LOCALE) { STRLEN len = strlen(radix); if (*sp + len <= send && memEQ(*sp, radix, len)) { *sp += len; return TRUE; } } #endif #endif /* USE_LOCALE_NUMERIC */ /* always try "." if numeric radix didn't match because * we may have data from different locales mixed */ if (*sp < send && **sp == '.') { ++*sp; return TRUE; } return FALSE; } #endif #endif #ifndef grok_number #if defined(NEED_grok_number) static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep); static #else extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep); #endif #ifdef grok_number # undef grok_number #endif #define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c) #define Perl_grok_number DPPP_(my_grok_number) #if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL) int DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep) { const char *s = pv; const char *send = pv + len; const UV max_div_10 = UV_MAX / 10; const char max_mod_10 = UV_MAX % 10; int numtype = 0; int sawinf = 0; int sawnan = 0; while (s < send && isSPACE(*s)) s++; if (s == send) { return 0; } else if (*s == '-') { s++; numtype = IS_NUMBER_NEG; } else if (*s == '+') s++; if (s == send) return 0; /* next must be digit or the radix separator or beginning of infinity */ if (isDIGIT(*s)) { /* UVs are at least 32 bits, so the first 9 decimal digits cannot overflow. */ UV value = *s - '0'; /* This construction seems to be more optimiser friendly. (without it gcc does the isDIGIT test and the *s - '0' separately) With it gcc on arm is managing 6 instructions (6 cycles) per digit. In theory the optimiser could deduce how far to unroll the loop before checking for overflow. */ if (++s < send) { int digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { /* Now got 9 digits, so need to check each time for overflow. */ digit = *s - '0'; while (digit >= 0 && digit <= 9 && (value < max_div_10 || (value == max_div_10 && digit <= max_mod_10))) { value = value * 10 + digit; if (++s < send) digit = *s - '0'; else break; } if (digit >= 0 && digit <= 9 && (s < send)) { /* value overflowed. skip the remaining digits, don't worry about setting *valuep. */ do { s++; } while (s < send && isDIGIT(*s)); numtype |= IS_NUMBER_GREATER_THAN_UV_MAX; goto skip_value; } } } } } } } } } } } } } } } } } } numtype |= IS_NUMBER_IN_UV; if (valuep) *valuep = value; skip_value: if (GROK_NUMERIC_RADIX(&s, send)) { numtype |= IS_NUMBER_NOT_INT; while (s < send && isDIGIT(*s)) /* optional digits after the radix */ s++; } } else if (GROK_NUMERIC_RADIX(&s, send)) { numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */ /* no digits before the radix means we need digits after it */ if (s < send && isDIGIT(*s)) { do { s++; } while (s < send && isDIGIT(*s)); if (valuep) { /* integer approximation is valid - it's 0. */ *valuep = 0; } } else return 0; } else if (*s == 'I' || *s == 'i') { s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; s++; if (s == send || (*s != 'F' && *s != 'f')) return 0; s++; if (s < send && (*s == 'I' || *s == 'i')) { s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; s++; if (s == send || (*s != 'I' && *s != 'i')) return 0; s++; if (s == send || (*s != 'T' && *s != 't')) return 0; s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0; s++; } sawinf = 1; } else if (*s == 'N' || *s == 'n') { /* XXX TODO: There are signaling NaNs and quiet NaNs. */ s++; if (s == send || (*s != 'A' && *s != 'a')) return 0; s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; s++; sawnan = 1; } else return 0; if (sawinf) { numtype &= IS_NUMBER_NEG; /* Keep track of sign */ numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT; } else if (sawnan) { numtype &= IS_NUMBER_NEG; /* Keep track of sign */ numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT; } else if (s < send) { /* we can have an optional exponent part */ if (*s == 'e' || *s == 'E') { /* The only flag we keep is sign. Blow away any "it's UV" */ numtype &= IS_NUMBER_NEG; numtype |= IS_NUMBER_NOT_INT; s++; if (s < send && (*s == '-' || *s == '+')) s++; if (s < send && isDIGIT(*s)) { do { s++; } while (s < send && isDIGIT(*s)); } else return 0; } } while (s < send && isSPACE(*s)) s++; if (s >= send) return numtype; if (len == 10 && memEQ(pv, "0 but true", 10)) { if (valuep) *valuep = 0; return IS_NUMBER_IN_UV; } return 0; } #endif #endif /* * The grok_* routines have been modified to use warn() instead of * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit, * which is why the stack variable has been renamed to 'xdigit'. */ #ifndef grok_bin #if defined(NEED_grok_bin) static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); static #else extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); #endif #ifdef grok_bin # undef grok_bin #endif #define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d) #define Perl_grok_bin DPPP_(my_grok_bin) #if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL) UV DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) { const char *s = start; STRLEN len = *len_p; UV value = 0; NV value_nv = 0; const UV max_div_2 = UV_MAX / 2; bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; bool overflowed = FALSE; if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) { /* strip off leading b or 0b. for compatibility silently suffer "b" and "0b" as valid binary numbers. */ if (len >= 1) { if (s[0] == 'b') { s++; len--; } else if (len >= 2 && s[0] == '0' && s[1] == 'b') { s+=2; len-=2; } } } for (; len-- && *s; s++) { char bit = *s; if (bit == '0' || bit == '1') { /* Write it in this wonky order with a goto to attempt to get the compiler to make the common case integer-only loop pretty tight. With gcc seems to be much straighter code than old scan_bin. */ redo: if (!overflowed) { if (value <= max_div_2) { value = (value << 1) | (bit - '0'); continue; } /* Bah. We're just overflowed. */ warn("Integer overflow in binary number"); overflowed = TRUE; value_nv = (NV) value; } value_nv *= 2.0; /* If an NV has not enough bits in its mantissa to * represent a UV this summing of small low-order numbers * is a waste of time (because the NV cannot preserve * the low-order bits anyway): we could just remember when * did we overflow and in the end just multiply value_nv by the * right amount. */ value_nv += (NV)(bit - '0'); continue; } if (bit == '_' && len && allow_underscores && (bit = s[1]) && (bit == '0' || bit == '1')) { --len; ++s; goto redo; } if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) warn("Illegal binary digit '%c' ignored", *s); break; } if ( ( overflowed && value_nv > 4294967295.0) #if UVSIZE > 4 || (!overflowed && value > 0xffffffff ) #endif ) { warn("Binary number > 0b11111111111111111111111111111111 non-portable"); } *len_p = s - start; if (!overflowed) { *flags = 0; return value; } *flags = PERL_SCAN_GREATER_THAN_UV_MAX; if (result) *result = value_nv; return UV_MAX; } #endif #endif #ifndef grok_hex #if defined(NEED_grok_hex) static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); static #else extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); #endif #ifdef grok_hex # undef grok_hex #endif #define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d) #define Perl_grok_hex DPPP_(my_grok_hex) #if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL) UV DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) { const char *s = start; STRLEN len = *len_p; UV value = 0; NV value_nv = 0; const UV max_div_16 = UV_MAX / 16; bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; bool overflowed = FALSE; const char *xdigit; if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) { /* strip off leading x or 0x. for compatibility silently suffer "x" and "0x" as valid hex numbers. */ if (len >= 1) { if (s[0] == 'x') { s++; len--; } else if (len >= 2 && s[0] == '0' && s[1] == 'x') { s+=2; len-=2; } } } for (; len-- && *s; s++) { xdigit = strchr((char *) PL_hexdigit, *s); if (xdigit) { /* Write it in this wonky order with a goto to attempt to get the compiler to make the common case integer-only loop pretty tight. With gcc seems to be much straighter code than old scan_hex. */ redo: if (!overflowed) { if (value <= max_div_16) { value = (value << 4) | ((xdigit - PL_hexdigit) & 15); continue; } warn("Integer overflow in hexadecimal number"); overflowed = TRUE; value_nv = (NV) value; } value_nv *= 16.0; /* If an NV has not enough bits in its mantissa to * represent a UV this summing of small low-order numbers * is a waste of time (because the NV cannot preserve * the low-order bits anyway): we could just remember when * did we overflow and in the end just multiply value_nv by the * right amount of 16-tuples. */ value_nv += (NV)((xdigit - PL_hexdigit) & 15); continue; } if (*s == '_' && len && allow_underscores && s[1] && (xdigit = strchr((char *) PL_hexdigit, s[1]))) { --len; ++s; goto redo; } if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) warn("Illegal hexadecimal digit '%c' ignored", *s); break; } if ( ( overflowed && value_nv > 4294967295.0) #if UVSIZE > 4 || (!overflowed && value > 0xffffffff ) #endif ) { warn("Hexadecimal number > 0xffffffff non-portable"); } *len_p = s - start; if (!overflowed) { *flags = 0; return value; } *flags = PERL_SCAN_GREATER_THAN_UV_MAX; if (result) *result = value_nv; return UV_MAX; } #endif #endif #ifndef grok_oct #if defined(NEED_grok_oct) static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); static #else extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); #endif #ifdef grok_oct # undef grok_oct #endif #define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d) #define Perl_grok_oct DPPP_(my_grok_oct) #if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL) UV DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) { const char *s = start; STRLEN len = *len_p; UV value = 0; NV value_nv = 0; const UV max_div_8 = UV_MAX / 8; bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; bool overflowed = FALSE; for (; len-- && *s; s++) { /* gcc 2.95 optimiser not smart enough to figure that this subtraction out front allows slicker code. */ int digit = *s - '0'; if (digit >= 0 && digit <= 7) { /* Write it in this wonky order with a goto to attempt to get the compiler to make the common case integer-only loop pretty tight. */ redo: if (!overflowed) { if (value <= max_div_8) { value = (value << 3) | digit; continue; } /* Bah. We're just overflowed. */ warn("Integer overflow in octal number"); overflowed = TRUE; value_nv = (NV) value; } value_nv *= 8.0; /* If an NV has not enough bits in its mantissa to * represent a UV this summing of small low-order numbers * is a waste of time (because the NV cannot preserve * the low-order bits anyway): we could just remember when * did we overflow and in the end just multiply value_nv by the * right amount of 8-tuples. */ value_nv += (NV)digit; continue; } if (digit == ('_' - '0') && len && allow_underscores && (digit = s[1] - '0') && (digit >= 0 && digit <= 7)) { --len; ++s; goto redo; } /* Allow \octal to work the DWIM way (that is, stop scanning * as soon as non-octal characters are seen, complain only iff * someone seems to want to use the digits eight and nine). */ if (digit == 8 || digit == 9) { if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) warn("Illegal octal digit '%c' ignored", *s); } break; } if ( ( overflowed && value_nv > 4294967295.0) #if UVSIZE > 4 || (!overflowed && value > 0xffffffff ) #endif ) { warn("Octal number > 037777777777 non-portable"); } *len_p = s - start; if (!overflowed) { *flags = 0; return value; } *flags = PERL_SCAN_GREATER_THAN_UV_MAX; if (result) *result = value_nv; return UV_MAX; } #endif #endif #if !defined(my_snprintf) #if defined(NEED_my_snprintf) static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...); static #else extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...); #endif #define my_snprintf DPPP_(my_my_snprintf) #define Perl_my_snprintf DPPP_(my_my_snprintf) #if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL) int DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...) { dTHX; int retval; va_list ap; va_start(ap, format); #ifdef HAS_VSNPRINTF retval = vsnprintf(buffer, len, format, ap); #else retval = vsprintf(buffer, format, ap); #endif va_end(ap); if (retval < 0 || (len > 0 && (Size_t)retval >= len)) Perl_croak(aTHX_ "panic: my_snprintf buffer overflow"); return retval; } #endif #endif #if !defined(my_sprintf) #if defined(NEED_my_sprintf) static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...); static #else extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...); #endif #define my_sprintf DPPP_(my_my_sprintf) #define Perl_my_sprintf DPPP_(my_my_sprintf) #if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL) int DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...) { va_list args; va_start(args, pat); vsprintf(buffer, pat, args); va_end(args); return strlen(buffer); } #endif #endif #ifdef NO_XSLOCKS # ifdef dJMPENV # define dXCPT dJMPENV; int rEtV = 0 # define XCPT_TRY_START JMPENV_PUSH(rEtV); if (rEtV == 0) # define XCPT_TRY_END JMPENV_POP; # define XCPT_CATCH if (rEtV != 0) # define XCPT_RETHROW JMPENV_JUMP(rEtV) # else # define dXCPT Sigjmp_buf oldTOP; int rEtV = 0 # define XCPT_TRY_START Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0) # define XCPT_TRY_END Copy(oldTOP, top_env, 1, Sigjmp_buf); # define XCPT_CATCH if (rEtV != 0) # define XCPT_RETHROW Siglongjmp(top_env, rEtV) # endif #endif #if !defined(my_strlcat) #if defined(NEED_my_strlcat) static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size); static #else extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size); #endif #define my_strlcat DPPP_(my_my_strlcat) #define Perl_my_strlcat DPPP_(my_my_strlcat) #if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL) Size_t DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size) { Size_t used, length, copy; used = strlen(dst); length = strlen(src); if (size > 0 && used < size - 1) { copy = (length >= size - used) ? size - used - 1 : length; memcpy(dst + used, src, copy); dst[used + copy] = '\0'; } return used + length; } #endif #endif #if !defined(my_strlcpy) #if defined(NEED_my_strlcpy) static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size); static #else extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size); #endif #define my_strlcpy DPPP_(my_my_strlcpy) #define Perl_my_strlcpy DPPP_(my_my_strlcpy) #if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL) Size_t DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size) { Size_t length, copy; length = strlen(src); if (size > 0) { copy = (length >= size) ? size - 1 : length; memcpy(dst, src, copy); dst[copy] = '\0'; } return length; } #endif #endif #ifndef PERL_PV_ESCAPE_QUOTE # define PERL_PV_ESCAPE_QUOTE 0x0001 #endif #ifndef PERL_PV_PRETTY_QUOTE # define PERL_PV_PRETTY_QUOTE PERL_PV_ESCAPE_QUOTE #endif #ifndef PERL_PV_PRETTY_ELLIPSES # define PERL_PV_PRETTY_ELLIPSES 0x0002 #endif #ifndef PERL_PV_PRETTY_LTGT # define PERL_PV_PRETTY_LTGT 0x0004 #endif #ifndef PERL_PV_ESCAPE_FIRSTCHAR # define PERL_PV_ESCAPE_FIRSTCHAR 0x0008 #endif #ifndef PERL_PV_ESCAPE_UNI # define PERL_PV_ESCAPE_UNI 0x0100 #endif #ifndef PERL_PV_ESCAPE_UNI_DETECT # define PERL_PV_ESCAPE_UNI_DETECT 0x0200 #endif #ifndef PERL_PV_ESCAPE_ALL # define PERL_PV_ESCAPE_ALL 0x1000 #endif #ifndef PERL_PV_ESCAPE_NOBACKSLASH # define PERL_PV_ESCAPE_NOBACKSLASH 0x2000 #endif #ifndef PERL_PV_ESCAPE_NOCLEAR # define PERL_PV_ESCAPE_NOCLEAR 0x4000 #endif #ifndef PERL_PV_ESCAPE_RE # define PERL_PV_ESCAPE_RE 0x8000 #endif #ifndef PERL_PV_PRETTY_NOCLEAR # define PERL_PV_PRETTY_NOCLEAR PERL_PV_ESCAPE_NOCLEAR #endif #ifndef PERL_PV_PRETTY_DUMP # define PERL_PV_PRETTY_DUMP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE #endif #ifndef PERL_PV_PRETTY_REGPROP # define PERL_PV_PRETTY_REGPROP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE #endif /* Hint: pv_escape * Note that unicode functionality is only backported to * those perl versions that support it. For older perl * versions, the implementation will fall back to bytes. */ #ifndef pv_escape #if defined(NEED_pv_escape) static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags); static #else extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags); #endif #ifdef pv_escape # undef pv_escape #endif #define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f) #define Perl_pv_escape DPPP_(my_pv_escape) #if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL) char * DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags) { const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\'; const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc; char octbuf[32] = "%123456789ABCDF"; STRLEN wrote = 0; STRLEN chsize = 0; STRLEN readsize = 1; #if defined(is_utf8_string) && defined(utf8_to_uvchr) bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0; #endif const char *pv = str; const char * const end = pv + count; octbuf[0] = esc; if (!(flags & PERL_PV_ESCAPE_NOCLEAR)) sv_setpvs(dsv, ""); #if defined(is_utf8_string) && defined(utf8_to_uvchr) if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count)) isuni = 1; #endif for (; pv < end && (!max || wrote < max) ; pv += readsize) { const UV u = #if defined(is_utf8_string) && defined(utf8_to_uvchr) isuni ? utf8_to_uvchr((U8*)pv, &readsize) : #endif (U8)*pv; const U8 c = (U8)u & 0xFF; if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) { if (flags & PERL_PV_ESCAPE_FIRSTCHAR) chsize = my_snprintf(octbuf, sizeof octbuf, "%"UVxf, u); else chsize = my_snprintf(octbuf, sizeof octbuf, "%cx{%"UVxf"}", esc, u); } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) { chsize = 1; } else { if (c == dq || c == esc || !isPRINT(c)) { chsize = 2; switch (c) { case '\\' : /* fallthrough */ case '%' : if (c == esc) octbuf[1] = esc; else chsize = 1; break; case '\v' : octbuf[1] = 'v'; break; case '\t' : octbuf[1] = 't'; break; case '\r' : octbuf[1] = 'r'; break; case '\n' : octbuf[1] = 'n'; break; case '\f' : octbuf[1] = 'f'; break; case '"' : if (dq == '"') octbuf[1] = '"'; else chsize = 1; break; default: chsize = my_snprintf(octbuf, sizeof octbuf, pv < end && isDIGIT((U8)*(pv+readsize)) ? "%c%03o" : "%c%o", esc, c); } } else { chsize = 1; } } if (max && wrote + chsize > max) { break; } else if (chsize > 1) { sv_catpvn(dsv, octbuf, chsize); wrote += chsize; } else { char tmp[2]; my_snprintf(tmp, sizeof tmp, "%c", c); sv_catpvn(dsv, tmp, 1); wrote++; } if (flags & PERL_PV_ESCAPE_FIRSTCHAR) break; } if (escaped != NULL) *escaped= pv - str; return SvPVX(dsv); } #endif #endif #ifndef pv_pretty #if defined(NEED_pv_pretty) static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags); static #else extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags); #endif #ifdef pv_pretty # undef pv_pretty #endif #define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g) #define Perl_pv_pretty DPPP_(my_pv_pretty) #if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL) char * DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags) { const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%'; STRLEN escaped; if (!(flags & PERL_PV_PRETTY_NOCLEAR)) sv_setpvs(dsv, ""); if (dq == '"') sv_catpvs(dsv, "\""); else if (flags & PERL_PV_PRETTY_LTGT) sv_catpvs(dsv, "<"); if (start_color != NULL) sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color)); pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR); if (end_color != NULL) sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color)); if (dq == '"') sv_catpvs(dsv, "\""); else if (flags & PERL_PV_PRETTY_LTGT) sv_catpvs(dsv, ">"); if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count) sv_catpvs(dsv, "..."); return SvPVX(dsv); } #endif #endif #ifndef pv_display #if defined(NEED_pv_display) static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim); static #else extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim); #endif #ifdef pv_display # undef pv_display #endif #define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e) #define Perl_pv_display DPPP_(my_pv_display) #if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL) char * DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim) { pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP); if (len > cur && pv[cur] == '\0') sv_catpvs(dsv, "\\0"); return SvPVX(dsv); } #endif #endif #endif /* _P_P_PORTABILITY_H_ */ /* End of File ppport.h */ Slic3r-1.2.9/xs/t/000077500000000000000000000000001254023100400135465ustar00rootroot00000000000000Slic3r-1.2.9/xs/t/01_trianglemesh.t000066400000000000000000000117441254023100400167240ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 49; is Slic3r::TriangleMesh::hello_world(), 'Hello world!', 'hello world'; my $cube = { vertices => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ], facets => [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5] ], }; { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; my ($vertices, $facets) = ($m->vertices, $m->facets); is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip'; is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip'; is scalar(@{$m->normals}), scalar(@$facets), 'normals returns the right number of items'; { my $m2 = $m->clone; is_deeply $m2->vertices, $cube->{vertices}, 'cloned vertices arrayref roundtrip'; is_deeply $m2->facets, $cube->{facets}, 'cloned facets arrayref roundtrip'; $m2->scale(3); # check that it does not affect $m } { my $stats = $m->stats; is $stats->{number_of_facets}, scalar(@{ $cube->{facets} }), 'stats.number_of_facets'; ok abs($stats->{volume} - 20*20*20) < 1E-2, 'stats.volume'; } $m->scale(2); ok abs($m->stats->{volume} - 40*40*40) < 1E-2, 'scale'; $m->scale_xyz(Slic3r::Pointf3->new(2,1,1)); ok abs($m->stats->{volume} - 2*40*40*40) < 1E-2, 'scale_xyz'; $m->translate(5,10,0); is_deeply $m->vertices->[0], [85,50,0], 'translate'; $m->align_to_origin; is_deeply $m->vertices->[2], [0,0,0], 'align_to_origin'; is_deeply $m->size, [80,40,40], 'size'; $m->scale_xyz(Slic3r::Pointf3->new(0.5,1,1)); $m->rotate(45, Slic3r::Point->new(20,20)); ok abs($m->size->[0] - sqrt(2)*40) < 1E-4, 'rotate'; { my $meshes = $m->split; is scalar(@$meshes), 1, 'split'; isa_ok $meshes->[0], 'Slic3r::TriangleMesh', 'split'; is_deeply $m->bb3, $meshes->[0]->bb3, 'split populates stats'; } my $m2 = Slic3r::TriangleMesh->new; $m2->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m2->repair; $m->merge($m2); $m->repair; is $m->stats->{number_of_facets}, 2 * $m2->stats->{number_of_facets}, 'merge'; { my $meshes = $m->split; is scalar(@$meshes), 2, 'split'; } } { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; my @z = (0,2,4,8,6,8,10,12,14,16,18,20); my $result = $m->slice(\@z); my $SCALING_FACTOR = 0.000001; for my $i (0..$#z) { is scalar(@{$result->[$i]}), 1, "number of returned polygons per layer (z = " . $z[$i] . ")"; is $result->[$i][0]->area, 20*20/($SCALING_FACTOR**2), 'size of returned polygon'; } } { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl( [ [0,0,0],[0,0,20],[0,5,0],[0,5,20],[50,0,0],[50,0,20],[15,5,0],[35,5,0],[15,20,0],[50,5,0],[35,20,0],[15,5,10],[50,5,20],[35,5,10],[35,20,10],[15,20,10] ], [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[0,2,4],[4,2,6],[7,6,8],[4,6,7],[9,4,7],[7,8,10],[2,3,6],[11,3,12],[7,12,9],[13,12,7],[6,3,11],[11,12,13],[3,1,5],[12,3,5],[5,4,9],[12,5,9],[13,7,10],[14,13,10],[8,15,10],[10,15,14],[6,11,8],[8,11,15],[15,11,13],[14,15,13] ], ); $m->repair; { # at Z = 10 we have a top horizontal surface my $slices = $m->slice([ 5, 10 ]); is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a top tangent plane includes its area'; } $m->flip_z; { # this second test also checks that performing a second slice on a mesh after # a transformation works properly (shared_vertices is correctly invalidated); # at Z = -10 we have a bottom horizontal surface my $slices = $m->slice([ -5, -10 ]); is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area'; } } { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; { my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; $m->cut(0, $upper, $lower); $upper->repair; $lower->repair; is $upper->facets_count, 12, 'upper mesh has all facets except those belonging to the slicing plane'; is $lower->facets_count, 0, 'lower mesh has no facets'; } { my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; $m->cut(10, $upper, $lower); #$upper->repair; $lower->repair; # we expect: # 2 facets on external horizontal surfaces # 3 facets on each side = 12 facets # 6 facets on the triangulated side (8 vertices) is $upper->facets_count, 2+12+6, 'upper mesh has the expected number of facets'; is $lower->facets_count, 2+12+6, 'lower mesh has the expected number of facets'; } } __END__ Slic3r-1.2.9/xs/t/03_point.t000066400000000000000000000054651254023100400154000ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 24; my $point = Slic3r::Point->new(10, 15); is_deeply [ @$point ], [10, 15], 'point roundtrip'; my $point2 = $point->clone; $point2->scale(2); is_deeply [ @$point2 ], [20, 30], 'scale'; $point2->translate(10, -15); is_deeply [ @$point2 ], [30, 15], 'translate'; ok $point->coincides_with($point->clone), 'coincides_with'; ok !$point->coincides_with($point2), 'coincides_with'; { my $point3 = Slic3r::Point->new(4300000, -9880845); is $point->[0], $point->x, 'x accessor'; is $point->[1], $point->y, 'y accessor'; #,, } { my $nearest = $point->nearest_point([ $point2, Slic3r::Point->new(100, 200) ]); ok $nearest->coincides_with($point2), 'nearest_point'; } { my $line = Slic3r::Line->new([0,0], [100,0]); is +Slic3r::Point->new(0,0) ->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(50,0) ->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(150,0)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(0,50) ->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(50,50)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(50,50) ->perp_distance_to_line($line), 50, 'perp_distance_to_line()'; is +Slic3r::Point->new(150,50)->perp_distance_to_line($line), 50, 'perp_distance_to_line()'; } { my $line = Slic3r::Line->new([50,50], [125,-25]); is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; } { my $line = Slic3r::Line->new( [18335846,18335845], [18335846,1664160], ); $point = Slic3r::Point->new(1664161,18335848); is $point->perp_distance_to_line($line), 16671685, 'perp_distance_to_line() does not overflow'; } { my $p0 = Slic3r::Point->new(76975850,89989996); my $p1 = Slic3r::Point->new(76989990,109989991); my $p2 = Slic3r::Point->new(76989987,89989994); ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; } { my $point = Slic3r::Point->new(15,15); my $line = Slic3r::Line->new([10,10], [20,10]); is_deeply $point->projection_onto_line($line)->pp, [15,10], 'project_onto_line'; $point = Slic3r::Point->new(0, 15); is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; $point = Slic3r::Point->new(25, 15); is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; $point = Slic3r::Point->new(10,10); is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; $point = Slic3r::Point->new(12, 10); is_deeply $point->projection_onto_line($line)->pp, [12,10], 'project_onto_line'; } __END__ Slic3r-1.2.9/xs/t/04_expolygon.t000066400000000000000000000111041254023100400162570ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(first sum); use Slic3r::XS; use Test::More tests => 33; use constant PI => 4 * atan2(1, 1); my $square = [ # ccw [100, 100], [200, 100], [200, 200], [100, 200], ]; my $hole_in_square = [ # cw [140, 140], [140, 160], [160, 160], [160, 140], ]; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); ok $expolygon->is_valid, 'is_valid'; is ref($expolygon->pp), 'ARRAY', 'expolygon pp is unblessed'; is_deeply $expolygon->pp, [$square, $hole_in_square], 'expolygon roundtrip'; is ref($expolygon->arrayref), 'ARRAY', 'expolygon arrayref is unblessed'; isa_ok $expolygon->[0], 'Slic3r::Polygon::Ref', 'expolygon polygon is blessed'; isa_ok $expolygon->contour, 'Slic3r::Polygon::Ref', 'expolygon contour is blessed'; isa_ok $expolygon->holes->[0], 'Slic3r::Polygon::Ref', 'expolygon hole is blessed'; isa_ok $expolygon->[0][0], 'Slic3r::Point::Ref', 'expolygon point is blessed'; { my $expolygon2 = $expolygon->clone; my $polygon = $expolygon2->[0]; $polygon->scale(2); is $expolygon2->[0][0][0], $polygon->[0][0], 'polygons are returned by reference'; } is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; is $expolygon->area, 100*100-20*20, 'area'; { my $expolygon2 = $expolygon->clone; $expolygon2->scale(2.5); is_deeply $expolygon2->pp, [ [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$square], [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$hole_in_square] ], 'scale'; } { my $expolygon2 = $expolygon->clone; $expolygon2->translate(10, -5); is_deeply $expolygon2->pp, [ [map [ $_->[0]+10, $_->[1]-5 ], @$square], [map [ $_->[0]+10, $_->[1]-5 ], @$hole_in_square] ], 'translate'; } { my $expolygon2 = $expolygon->clone; $expolygon2->rotate(PI/2, Slic3r::Point->new(150,150)); is_deeply $expolygon2->pp, [ [ @$square[1,2,3,0] ], [ @$hole_in_square[3,0,1,2] ] ], 'rotate around Point'; } { my $expolygon2 = $expolygon->clone; $expolygon2->rotate(PI/2, [150,150]); is_deeply $expolygon2->pp, [ [ @$square[1,2,3,0] ], [ @$hole_in_square[3,0,1,2] ] ], 'rotate around pure-Perl Point'; } { my $expolygon2 = $expolygon->clone; $expolygon2->scale(2); my $collection = Slic3r::ExPolygon::Collection->new($expolygon->pp, $expolygon2->pp); is_deeply $collection->pp, [ $expolygon->pp, $expolygon2->pp ], 'expolygon collection (pure Perl) roundtrip'; my $collection2 = Slic3r::ExPolygon::Collection->new($expolygon, $expolygon2); is_deeply $collection->pp, $collection2->pp, 'expolygon collection (XS) roundtrip'; $collection->clear; is scalar(@$collection), 0, 'clear collection'; $collection->append($expolygon); is scalar(@$collection), 1, 'append to collection'; my $exp = $collection->[0]; $exp->scale(3); is $collection->[0][0][0][0], $exp->[0][0][0], 'collection items are returned by reference'; is_deeply $collection->[0]->clone->pp, $collection->[0]->pp, 'clone collection item'; } { my $expolygon = Slic3r::ExPolygon->new($square); my $polygons = $expolygon->get_trapezoids2(PI/2); is scalar(@$polygons), 1, 'correct number of trapezoids returned'; is scalar(@{$polygons->[0]}), 4, 'trapezoid has 4 points'; is $polygons->[0]->area, $expolygon->area, 'trapezoid has correct area'; } { my $polygons = $expolygon->get_trapezoids2(PI/2); is scalar(@$polygons), 4, 'correct number of trapezoids returned'; # trapezoid polygons might have more than 4 points in case of collinear segments $polygons = [ map @{$_->simplify(1)}, @$polygons ]; ok !defined(first { @$_ != 4 } @$polygons), 'all trapezoids have 4 points'; is scalar(grep { $_->area == 40*100 } @$polygons), 2, 'trapezoids have expected area'; is scalar(grep { $_->area == 20*40 } @$polygons), 2, 'trapezoids have expected area'; } { my $expolygon = Slic3r::ExPolygon->new([ [0,100],[100,0],[200,0],[300,100],[200,200],[100,200] ]); my $polygons = $expolygon->get_trapezoids2(PI/2); is scalar(@$polygons), 3, 'correct number of trapezoids returned'; is scalar(grep { $_->area == 100*200/2 } @$polygons), 2, 'trapezoids have expected area'; is scalar(grep { $_->area == 100*200 } @$polygons), 1, 'trapezoids have expected area'; } { my $triangles = $expolygon->triangulate_pp; is scalar(@$triangles), 8, 'expected number of triangles'; is sum(map $_->area, @$triangles), $expolygon->area, 'sum of triangles area equals original expolygon area'; } __END__ Slic3r-1.2.9/xs/t/05_surface.t000066400000000000000000000046671254023100400157040ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 15; my $square = [ # ccw [100, 100], [200, 100], [200, 200], [100, 200], ]; my $hole_in_square = [ # cw [140, 140], [140, 160], [160, 160], [160, 140], ]; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); my $surface = Slic3r::Surface->new( expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_INTERNAL, ); $surface = $surface->clone; isa_ok $surface->expolygon, 'Slic3r::ExPolygon::Ref', 'expolygon'; is_deeply [ @{$surface->expolygon->pp} ], [$square, $hole_in_square], 'expolygon roundtrip'; is scalar(@{$surface->polygons}), 2, 'polygons roundtrip'; is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; $surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; $surface->bridge_angle(30); is $surface->bridge_angle, 30, 'bridge_angle'; $surface->extra_perimeters(2); is $surface->extra_perimeters, 2, 'extra_perimeters'; { my $surface2 = $surface->clone; $surface2->expolygon->scale(2); isnt $surface2->expolygon->area, $expolygon->area, 'expolygon is returned by reference'; } { my $collection = Slic3r::Surface::Collection->new; $collection->append($_) for $surface, $surface->clone; is scalar(@$collection), 2, 'collection has the right number of items'; is_deeply $collection->[0]->expolygon->pp, [$square, $hole_in_square], 'collection returns a correct surface expolygon'; $collection->clear; is scalar(@$collection), 0, 'clear collection'; $collection->append($surface); is scalar(@$collection), 1, 'append to collection'; my $item = $collection->[0]; isa_ok $item, 'Slic3r::Surface::Ref'; $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; } { my $collection = Slic3r::Surface::Collection->new; $collection->append($_) for Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_TOP); is scalar(@{$collection->group}), 2, 'group() returns correct number of groups'; } __END__ Slic3r-1.2.9/xs/t/06_polygon.t000066400000000000000000000050031254023100400157250ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(first); use Slic3r::XS; use Test::More tests => 20; use constant PI => 4 * atan2(1, 1); my $square = [ # ccw [100, 100], [200, 100], [200, 200], [100, 200], ]; my $polygon = Slic3r::Polygon->new(@$square); my $cw_polygon = $polygon->clone; $cw_polygon->reverse; ok $polygon->is_valid, 'is_valid'; is_deeply $polygon->pp, $square, 'polygon roundtrip'; is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed'; isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed'; my $lines = $polygon->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], [ [200, 100], [200, 200] ], [ [200, 200], [100, 200] ], [ [100, 200], [100, 100] ], ], 'polygon lines'; is_deeply $polygon->split_at_first_point->pp, [ @$square[0,1,2,3,0] ], 'split_at_first_point'; is_deeply $polygon->split_at_index(2)->pp, [ @$square[2,3,0,1,2] ], 'split_at_index'; is_deeply $polygon->split_at_vertex(Slic3r::Point->new(@{$square->[2]}))->pp, [ @$square[2,3,0,1,2] ], 'split_at'; is $polygon->area, 100*100, 'area'; ok $polygon->is_counter_clockwise, 'is_counter_clockwise'; ok !$cw_polygon->is_counter_clockwise, 'is_counter_clockwise'; { my $clone = $polygon->clone; $clone->reverse; ok !$clone->is_counter_clockwise, 'is_counter_clockwise'; $clone->make_counter_clockwise; ok $clone->is_counter_clockwise, 'make_counter_clockwise'; $clone->make_counter_clockwise; ok $clone->is_counter_clockwise, 'make_counter_clockwise'; } ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point'; ok $polygon->contains_point(Slic3r::Point->new(150,150)), 'ccw contains_point'; ok $cw_polygon->contains_point(Slic3r::Point->new(150,150)), 'cw contains_point'; { my @points = (Slic3r::Point->new(100,0)); foreach my $i (1..5) { my $point = $points[0]->clone; $point->rotate(PI/3*$i, [0,0]); push @points, $point; } my $hexagon = Slic3r::Polygon->new(@points); my $triangles = $hexagon->triangulate_convex; is scalar(@$triangles), 4, 'right number of triangles'; ok !(defined first { $_->is_clockwise } @$triangles), 'all triangles are ccw'; } { is_deeply $polygon->centroid->pp, [150,150], 'centroid'; } # this is not a test: this just demonstrates bad usage, where $polygon->clone gets # DESTROY'ed before the derived object ($point), causing bad memory access if (0) { my $point; { $point = $polygon->clone->[0]; } $point->scale(2); } __END__ Slic3r-1.2.9/xs/t/07_extrusionpath.t000066400000000000000000000017421254023100400171620ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 8; my $points = [ [100, 100], [200, 100], [200, 200], ]; my $path = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline'; is_deeply $path->polyline->pp, $points, 'path points roundtrip'; $path->reverse; is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; $path->append([ 150, 150 ]); is scalar(@$path), 4, 'append to path'; $path->pop_back; is scalar(@$path), 3, 'pop_back from path'; ok $path->first_point->coincides_with($path->polyline->[0]), 'first_point'; $path = $path->clone; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; $path->role(Slic3r::ExtrusionPath::EXTR_ROLE_FILL); is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_FILL, 'modify role'; __END__ Slic3r-1.2.9/xs/t/08_extrusionloop.t000066400000000000000000000175621254023100400172070ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(sum); use Slic3r::XS; use Test::More tests => 48; { my $square = [ [100, 100], [200, 100], [200, 200], [100, 200], ]; my $square_p = Slic3r::Polygon->new(@$square); my $loop = Slic3r::ExtrusionLoop->new; $loop->append(Slic3r::ExtrusionPath->new( polyline => $square_p->split_at_first_point, role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, )); isa_ok $loop, 'Slic3r::ExtrusionLoop'; isa_ok $loop->polygon, 'Slic3r::Polygon', 'loop polygon'; is $loop->polygon->area, $square_p->area, 'polygon area'; is $loop->length, $square_p->length(), 'loop length'; $loop = $loop->clone; is scalar(@$loop), 1, 'loop contains one path'; { my $path = $loop->[0]; isa_ok $path, 'Slic3r::ExtrusionPath::Ref'; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; $path->role(Slic3r::ExtrusionPath::EXTR_ROLE_FILL); is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_FILL, 'modify role'; } $loop->split_at_vertex($square_p->[2]); is scalar(@$loop), 1, 'splitting a single-path loop results in a single path'; is scalar(@{$loop->[0]->polyline}), 5, 'path has correct number of points'; ok $loop->[0]->polyline->[0]->coincides_with($square_p->[2]), 'expected point order'; ok $loop->[0]->polyline->[1]->coincides_with($square_p->[3]), 'expected point order'; ok $loop->[0]->polyline->[2]->coincides_with($square_p->[0]), 'expected point order'; ok $loop->[0]->polyline->[3]->coincides_with($square_p->[1]), 'expected point order'; ok $loop->[0]->polyline->[4]->coincides_with($square_p->[2]), 'expected point order'; } { my $polyline1 = Slic3r::Polyline->new([100,100], [200,100], [200,200]); my $polyline2 = Slic3r::Polyline->new([200,200], [100,200], [100,100]); my $loop = Slic3r::ExtrusionLoop->new_from_paths( Slic3r::ExtrusionPath->new( polyline => $polyline1, role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ), Slic3r::ExtrusionPath->new( polyline => $polyline2, role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1, ), ); my $tot_len = sum($polyline1->length, $polyline2->length); is $loop->length, $tot_len, 'length'; is scalar(@$loop), 2, 'loop contains two paths'; { # check splitting at intermediate point my $loop2 = $loop->clone; isa_ok $loop2, 'Slic3r::ExtrusionLoop'; $loop2->split_at_vertex($polyline1->[1]); is $loop2->length, $tot_len, 'length after splitting is unchanged'; is scalar(@$loop2), 3, 'loop contains three paths after splitting'; ok $loop2->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[2]->polyline->[0]), 'paths have common point'; is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; is $loop2->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; is scalar(@{$loop2->[0]->polyline}), 2, 'path has correct number of points'; is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; is scalar(@{$loop2->[2]->polyline}), 2, 'path has correct number of points'; my @paths = @{$loop2->clip_end(3)}; is sum(map $_->length, @paths), $loop2->length - 3, 'returned paths have expected length'; } { # check splitting at endpoint my $loop2 = $loop->clone; $loop2->split_at_vertex($polyline2->[0]); is $loop2->length, $tot_len, 'length after splitting is unchanged'; is scalar(@$loop2), 2, 'loop contains two paths after splitting'; ok $loop2->[0]->polyline->[0]->coincides_with($polyline2->[0]), 'expected starting point'; ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline2->[0]), 'expected ending point'; ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[0]->polyline->[0]), 'paths have common point'; is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; is scalar(@{$loop2->[0]->polyline}), 3, 'path has correct number of points'; is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; } { my $loop2 = $loop->clone; my $point = Slic3r::Point->new(250,150); $loop2->split_at($point); is $loop2->length, $tot_len, 'length after splitting is unchanged'; is scalar(@$loop2), 3, 'loop contains three paths after splitting'; my $expected_start_point = Slic3r::Point->new(200,150); ok $loop2->[0]->polyline->[0]->coincides_with($expected_start_point), 'expected starting point'; ok $loop2->[-1]->polyline->[-1]->coincides_with($expected_start_point), 'expected ending point'; } } { my @polylines = ( Slic3r::Polyline->new([59312736,4821067],[64321068,4821067],[64321068,4821067],[64321068,9321068],[59312736,9321068]), Slic3r::Polyline->new([59312736,9321068],[9829401,9321068]), Slic3r::Polyline->new([9829401,9321068],[4821067,9321068],[4821067,4821067],[9829401,4821067]), Slic3r::Polyline->new([9829401,4821067],[59312736,4821067]), ); my $loop = Slic3r::ExtrusionLoop->new; $loop->append($_) for ( Slic3r::ExtrusionPath->new(polyline => $polylines[0], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[1], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), ); my $len = $loop->length; my $point = Slic3r::Point->new(4821067,9321068); $loop->split_at_vertex($point) or $loop->split_at($point); is $loop->length, $len, 'total length is preserved after splitting'; is_deeply [ map $_->role, @$loop ], [ Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, ], 'order is correctly preserved after splitting'; } { my $loop = Slic3r::ExtrusionLoop->new; $loop->append(Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1 )); my $len = $loop->length; $loop->split_at(Slic3r::Point->new(15896783,15868739)); is $loop->length, $len, 'split_at() preserves total length'; } __END__ Slic3r-1.2.9/xs/t/09_polyline.t000066400000000000000000000070421254023100400161010ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 18; my $points = [ [100, 100], [200, 100], [200, 200], ]; my $polyline = Slic3r::Polyline->new(@$points); is_deeply $polyline->pp, $points, 'polyline roundtrip'; is ref($polyline->arrayref), 'ARRAY', 'polyline arrayref is unblessed'; isa_ok $polyline->[0], 'Slic3r::Point::Ref', 'polyline point is blessed'; my $lines = $polyline->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], [ [200, 100], [200, 200] ], ], 'polyline lines'; $polyline->append_polyline($polyline->clone); is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; { my $len = $polyline->length; $polyline->clip_end($len/3); ok abs($polyline->length - ($len-($len/3))) < 1, 'clip_end'; } { my $polyline = Slic3r::Polyline->new( [0,0], [20,0], [50,0], [80,0], [100,0], ); $polyline->simplify(2); is_deeply $polyline->pp, [ [0,0], [100,0] ], 'Douglas-Peucker'; } { my $polyline = Slic3r::Polyline->new( [0,0], [50,50], [100,0], [125,-25], [150,50], ); $polyline->simplify(25); is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; } { my $polyline = Slic3r::Polyline->new( [0,0], [100,0], [50,10], ); $polyline->simplify(25); is_deeply $polyline->pp, [ [0,0], [100,0], [50,10] ], 'Douglas-Peucker uses shortest distance instead of perpendicular distance'; } { my $polyline = Slic3r::Polyline->new(@$points); is $polyline->length, 100*2, 'length'; $polyline->extend_end(50); is $polyline->length, 100*2 + 50, 'extend_end'; $polyline->extend_start(50); is $polyline->length, 100*2 + 50 + 50, 'extend_start'; } { my $polyline = Slic3r::Polyline->new(@$points); my $p1 = Slic3r::Polyline->new; my $p2 = Slic3r::Polyline->new; my $point = Slic3r::Point->new(150, 100); $polyline->split_at($point, $p1, $p2); is scalar(@$p1), 2, 'split_at'; is scalar(@$p2), 3, 'split_at'; ok $p1->last_point->coincides_with($point), 'split_at'; ok $p2->first_point->coincides_with($point), 'split_at'; } { my $polyline = Slic3r::Polyline->new(@$points[0,1,2,0]); my $p1 = Slic3r::Polyline->new; my $p2 = Slic3r::Polyline->new; $polyline->split_at($polyline->first_point, $p1, $p2); is scalar(@$p1), 1, 'split_at'; is scalar(@$p2), 4, 'split_at'; } # disabled because we now use a more efficient but incomplete algorithm if (0) { my $polyline = Slic3r::Polyline->new( map [$_,10], (0,10,20,30,40,50,60) ); { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [25,0], [55,0], [55,30], [25,30], )); my $p = $polyline->clone; $p->simplify_by_visibility($expolygon); is_deeply $p->pp, [ map [$_,10], (0,10,20,30,50,60) ], 'simplify_by_visibility()'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [-15,0], [75,0], [75,30], [-15,30], )); my $p = $polyline->clone; $p->simplify_by_visibility($expolygon); is_deeply $p->pp, [ map [$_,10], (0,60) ], 'simplify_by_visibility()'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [-15,0], [25,0], [25,30], [-15,30], )); my $p = $polyline->clone; $p->simplify_by_visibility($expolygon); is_deeply $p->pp, [ map [$_,10], (0,20,30,40,50,60) ], 'simplify_by_visibility()'; } } __END__ Slic3r-1.2.9/xs/t/10_line.t000066400000000000000000000043421254023100400151650ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 40; use constant PI => 4 * atan2(1, 1); use constant EPSILON => 1E-4; my $points = [ [100, 100], [200, 100], ]; my $line = Slic3r::Line->new(@$points); is_deeply $line->pp, $points, 'line roundtrip'; is ref($line->arrayref), 'ARRAY', 'line arrayref is unblessed'; isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed'; { my $clone = $line->clone; $clone->reverse; is_deeply $clone->pp, [ reverse @$points ], 'reverse'; } { my $line2 = Slic3r::Line->new($line->a->clone, $line->b->clone); is_deeply $line2->pp, $points, 'line roundtrip with cloned points'; } { my $clone = $line->clone; $clone->translate(10, -5); is_deeply $clone->pp, [ [110, 95], [210, 95], ], 'translate'; } { ok +Slic3r::Line->new([0,0],[200,0])->parallel_to_line(Slic3r::Line->new([200,200],[0,200])), 'parallel_to'; } foreach my $base_angle (0, PI/4, PI/2, PI) { my $line = Slic3r::Line->new([0,0], [100,0]); $line->rotate($base_angle, [0,0]); my $clone = $line->clone; ok $line->parallel_to_line($clone), 'line is parallel to self'; $clone->reverse; ok $line->parallel_to_line($clone), 'line is parallel to self + PI'; ok $line->parallel_to($line->direction), 'line is parallel to its direction'; ok $line->parallel_to($line->direction + PI), 'line is parallel to its direction + PI'; ok $line->parallel_to($line->direction - PI), 'line is parallel to its direction - PI'; { my $line2 = $line->clone; $line2->reverse; ok $line->parallel_to_line($line2), 'line is parallel to its opposite'; } { my $line2 = $line->clone; $line2->rotate(+(EPSILON)/2, [0,0]); ok $line->parallel_to_line($line2), 'line is parallel within epsilon'; } { my $line2 = $line->clone; $line2->rotate(-(EPSILON)/2, [0,0]); ok $line->parallel_to_line($line2), 'line is parallel within epsilon'; } } { my $a = Slic3r::Line->new([100, 0], [200, 0]); my $b = Slic3r::Line->new([300, 300], [300, 100]); my $r = $a->intersection_infinite($b); is_deeply $r->pp, [300, 0], 'intersection_infinite'; } __END__ Slic3r-1.2.9/xs/t/11_clipper.t000066400000000000000000000245621254023100400157030ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(sum); use Slic3r::XS; use Test::More tests => 23; my $square = Slic3r::Polygon->new( # ccw [200, 100], [200, 200], [100, 200], [100, 100], ); my $hole_in_square = Slic3r::Polygon->new( # cw [160, 140], [140, 140], [140, 160], [160, 160], ); my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); { my $result = Slic3r::Geometry::Clipper::offset([ $square, $hole_in_square ], 5); is_deeply [ map $_->pp, @$result ], [ [ [205, 205], [95, 205], [95, 95], [205, 95], ], [ [145, 145], [145, 155], [155, 155], [155, 145], ] ], 'offset'; } { my $result = Slic3r::Geometry::Clipper::offset_ex([ @$expolygon ], 5); is_deeply $result->[0]->pp, [ [ [205, 205], [95, 205], [95, 95], [205, 95], ], [ [145, 145], [145, 155], [155, 155], [155, 145], ] ], 'offset_ex'; } { my $result = Slic3r::Geometry::Clipper::offset2_ex([ @$expolygon ], 5, -2); is_deeply $result->[0]->pp, [ [ [203, 203], [97, 203], [97, 97], [203, 97], ], [ [143, 143], [143, 157], [157, 157], [157, 143], ] ], 'offset2_ex'; } { my $expolygon2 = Slic3r::ExPolygon->new([ [20000000, 20000000], [0, 20000000], [0, 0], [20000000, 0], ], [ [5000000, 15000000], [15000000, 15000000], [15000000, 5000000], [5000000, 5000000], ]); my $result = Slic3r::Geometry::Clipper::offset2_ex([ @$expolygon2 ], -1, +1); is $result->[0]->area, $expolygon2->area, 'offset2_ex'; } { my $polygon1 = Slic3r::Polygon->new(@$square); my $polygon2 = Slic3r::Polygon->new(reverse @$hole_in_square); my $result = Slic3r::Geometry::Clipper::diff_ex([$polygon1], [$polygon2]); is $result->[0]->area, $expolygon->area, 'diff_ex'; } { my $polyline = Slic3r::Polyline->new([50,150], [300,150]); { my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square, $hole_in_square]); is scalar(@$result), 2, 'intersection_pl - correct number of result lines'; # results are in no particular order is scalar(grep $_->length == 40, @$result), 2, 'intersection_pl - result lines have correct length'; } { my $result = Slic3r::Geometry::Clipper::diff_pl([$polyline], [$square, $hole_in_square]); is scalar(@$result), 3, 'diff_pl - correct number of result lines'; # results are in no particular order is scalar(grep $_->length == 50, @$result), 1, 'diff_pl - the left result line has correct length'; is scalar(grep $_->length == 100, @$result), 1, 'diff_pl - two right result line has correct length'; is scalar(grep $_->length == 20, @$result), 1, 'diff_pl - the central result line has correct length'; } } if (0) { # Clipper does not preserve polyline orientation my $polyline = Slic3r::Polyline->new([50,150], [300,150]); my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]); is scalar(@$result), 1, 'intersection_pl - correct number of result lines'; is_deeply $result->[0]->pp, [[100,150], [200,150]], 'clipped line orientation is preserved'; } if (0) { # Clipper does not preserve polyline orientation my $polyline = Slic3r::Polyline->new([300,150], [50,150]); my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]); is scalar(@$result), 1, 'intersection_pl - correct number of result lines'; is_deeply $result->[0]->pp, [[200,150], [100,150]], 'clipped line orientation is preserved'; } if (0) { # Clipper does not preserve polyline orientation my $result = Slic3r::Geometry::Clipper::intersection_ppl([$hole_in_square], [$square]); is_deeply $result->[0]->pp, $hole_in_square->split_at_first_point->pp, 'intersection_ppl - clipping cw polygon as polyline preserves winding order'; } { my $square2 = $square->clone; $square2->translate(50,50); { my $result = Slic3r::Geometry::Clipper::intersection_ppl([$square2], [$square]); is scalar(@$result), 1, 'intersection_ppl - result contains a single line'; is scalar(@{$result->[0]}), 3, 'intersection_ppl - result contains expected number of points'; # Clipper does not preserve polyline orientation so we only check the middle point ###ok $result->[0][0]->coincides_with(Slic3r::Point->new(150,200)), 'intersection_ppl - expected point order'; ok $result->[0][1]->coincides_with(Slic3r::Point->new(150,150)), 'intersection_ppl - expected point order'; ###ok $result->[0][2]->coincides_with(Slic3r::Point->new(200,150)), 'intersection_ppl - expected point order'; } } { my $square2 = $square->clone; $square2->reverse; $square2->translate(50,50); { my $result = Slic3r::Geometry::Clipper::intersection_ppl([$square2], [$square]); is scalar(@$result), 1, 'intersection_ppl - result contains a single line'; is scalar(@{$result->[0]}), 3, 'intersection_ppl - result contains expected number of points'; # Clipper does not preserve polyline orientation so we only check the middle point ###ok $result->[0][0]->coincides_with(Slic3r::Point->new(200,150)), 'intersection_ppl - expected point order'; ok $result->[0][1]->coincides_with(Slic3r::Point->new(150,150)), 'intersection_ppl - expected point order'; ###ok $result->[0][2]->coincides_with(Slic3r::Point->new(150,200)), 'intersection_ppl - expected point order'; } } { # Clipper bug #96 (our issue #2028) my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] ); my $clip = [ Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); is scalar(@$result), 1, 'intersection_pl - result is not empty'; } { my $subject = Slic3r::Polygon->new( [44730000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000] ); my $clip = [ Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_ppl([$subject], $clip); is scalar(@$result), 1, 'intersection_ppl - result is not empty'; } { # Clipper bug #122 my $subject = [ Slic3r::Polyline->new([1975,1975],[25,1975],[25,25],[1975,25],[1975,1975]), ]; my $clip = [ Slic3r::Polygon->new([2025,2025],[-25,2025],[-25,-25],[2025,-25]), Slic3r::Polygon->new([525,525],[525,1475],[1475,1475],[1475,525]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, $clip); is scalar(@$result), 1, 'intersection_pl - result is not empty'; is scalar(@{$result->[0]}), 5, 'intersection_pl - result is not empty'; } { # Clipper bug #126 my $subject = Slic3r::Polyline->new( [200000,19799999],[200000,200000],[24304692,200000],[15102879,17506106],[13883200,19799999],[200000,19799999], ); my $clip = [ Slic3r::Polygon->new([15257205,18493894],[14350057,20200000],[-200000,20200000],[-200000,-200000],[25196917,-200000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); is scalar(@$result), 1, 'intersection_pl - result is not empty'; is $result->[0]->length, $subject->length, 'intersection_pl - result has same length as subject polyline'; } if (0) { # Disabled until Clipper bug #127 is fixed my $subject = [ Slic3r::Polyline->new([-90000000,-100000000],[-90000000,100000000]), # vertical Slic3r::Polyline->new([-100000000,-10000000],[100000000,-10000000]), # horizontal Slic3r::Polyline->new([-100000000,0],[100000000,0]), # horizontal Slic3r::Polyline->new([-100000000,10000000],[100000000,10000000]), # horizontal ]; my $clip = Slic3r::Polygon->new( # a circular, convex, polygon [99452190,10452846],[97814760,20791169],[95105652,30901699],[91354546,40673664],[86602540,50000000], [80901699,58778525],[74314483,66913061],[66913061,74314483],[58778525,80901699],[50000000,86602540], [40673664,91354546],[30901699,95105652],[20791169,97814760],[10452846,99452190],[0,100000000], [-10452846,99452190],[-20791169,97814760],[-30901699,95105652],[-40673664,91354546], [-50000000,86602540],[-58778525,80901699],[-66913061,74314483],[-74314483,66913061], [-80901699,58778525],[-86602540,50000000],[-91354546,40673664],[-95105652,30901699], [-97814760,20791169],[-99452190,10452846],[-100000000,0],[-99452190,-10452846], [-97814760,-20791169],[-95105652,-30901699],[-91354546,-40673664],[-86602540,-50000000], [-80901699,-58778525],[-74314483,-66913061],[-66913061,-74314483],[-58778525,-80901699], [-50000000,-86602540],[-40673664,-91354546],[-30901699,-95105652],[-20791169,-97814760], [-10452846,-99452190],[0,-100000000],[10452846,-99452190],[20791169,-97814760], [30901699,-95105652],[40673664,-91354546],[50000000,-86602540],[58778525,-80901699], [66913061,-74314483],[74314483,-66913061],[80901699,-58778525],[86602540,-50000000], [91354546,-40673664],[95105652,-30901699],[97814760,-20791169],[99452190,-10452846],[100000000,0] ); my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, [$clip]); is scalar(@$result), scalar(@$subject), 'intersection_pl - expected number of polylines'; is sum(map scalar(@$_), @$result), scalar(@$subject)*2, 'intersection_pl - expected number of points in polylines'; } __END__ Slic3r-1.2.9/xs/t/12_extrusionpathcollection.t000066400000000000000000000062321254023100400212310ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 18; my $points = [ [100, 100], [200, 100], [200, 200], ]; my $path = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); my $loop = Slic3r::ExtrusionLoop->new_from_paths( Slic3r::ExtrusionPath->new( polyline => Slic3r::Polygon->new(@$points)->split_at_first_point, role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, mm3_per_mm => 1, ), ); my $collection = Slic3r::ExtrusionPath::Collection->new( $path, ); isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor'; ok !$collection->no_sort, 'no_sort is false by default'; $collection->append($collection); is scalar(@$collection), 2, 'append ExtrusionPath::Collection'; $collection->append($path); is scalar(@$collection), 3, 'append ExtrusionPath'; $collection->append($loop); is scalar(@$collection), 4, 'append ExtrusionLoop'; isa_ok $collection->[1], 'Slic3r::ExtrusionPath::Collection::Ref', 'correct object returned for collection'; isa_ok $collection->[2], 'Slic3r::ExtrusionPath::Ref', 'correct object returned for path'; isa_ok $collection->[3], 'Slic3r::ExtrusionLoop::Ref', 'correct object returned for loop'; is ref($collection->[2]->clone), 'Slic3r::ExtrusionPath', 'correct object returned for cloned path'; is ref($collection->[3]->clone), 'Slic3r::ExtrusionLoop', 'correct object returned for cloned loop'; is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection_loop = $collection->[3]; $collection_loop->polygon->scale(2); is_deeply $collection->[3]->polygon->pp, $collection_loop->polygon->pp, 'items are returned by reference'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->y, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained_path_from'; is_deeply [ map $_->y, map @{$_->polyline}, @{$collection->chained_path(0)} ], [15, 18, 20, 10, 8, 5], 'chained_path'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->x, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained_path_from'; $collection->no_sort(1); my @foo = @{$collection->chained_path(0)}; pass 'chained_path with no_sort'; } { my $coll2 = $collection->clone; ok !$coll2->no_sort, 'expected no_sort value'; $coll2->no_sort(1); ok $coll2->clone->no_sort, 'no_sort is kept after clone'; } __END__ Slic3r-1.2.9/xs/t/13_polylinecollection.t000066400000000000000000000016471254023100400201550ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 3; { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->y, map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained_path_from'; is_deeply [ map $_->y, map @$_, @{$collection->chained_path(0)} ], [15, 18, 20, 10, 8, 5], 'chained_path'; } { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->x, map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained_path_from'; } __END__ Slic3r-1.2.9/xs/t/14_geometry.t000066400000000000000000000027051254023100400160760ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 9; use constant PI => 4 * atan2(1, 1); { my @points = ( Slic3r::Point->new(100,100), Slic3r::Point->new(100,200), Slic3r::Point->new(200,200), Slic3r::Point->new(200,100), Slic3r::Point->new(150,150), ); my $hull = Slic3r::Geometry::convex_hull(\@points); isa_ok $hull, 'Slic3r::Polygon', 'convex_hull returns a Polygon'; is scalar(@$hull), 4, 'convex_hull returns the correct number of points'; } # directions_parallel() and directions_parallel_within() are tested # also with Slic3r::Line::parallel_to() tests in 10_line.t { ok Slic3r::Geometry::directions_parallel_within(0, 0, 0), 'directions_parallel_within'; ok Slic3r::Geometry::directions_parallel_within(0, PI, 0), 'directions_parallel_within'; ok Slic3r::Geometry::directions_parallel_within(0, 0, PI/180), 'directions_parallel_within'; ok Slic3r::Geometry::directions_parallel_within(0, PI, PI/180), 'directions_parallel_within'; ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, 0), 'directions_parallel_within'; ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, PI/180), 'directions_parallel_within'; } { my $positions = Slic3r::Geometry::arrange(4, Slic3r::Pointf->new(20, 20), 5, Slic3r::Geometry::BoundingBoxf->new); is scalar(@$positions), 4, 'arrange() returns expected number of positions'; } __END__ Slic3r-1.2.9/xs/t/15_config.t000066400000000000000000000224711254023100400155130ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 110; foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $config->set('layer_height', 0.3); ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; is $config->serialize('layer_height'), '0.3', 'serialize float'; $config->set('perimeters', 2); is $config->get('perimeters'), 2, 'set/get int'; is $config->serialize('perimeters'), '2', 'serialize int'; $config->set('extrusion_axis', 'A'); is $config->get('extrusion_axis'), 'A', 'set/get string'; is $config->serialize('extrusion_axis'), 'A', 'serialize string'; $config->set('notes', "foo\nbar"); is $config->get('notes'), "foo\nbar", 'set/get string with newline'; is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; $config->set('first_layer_height', '50%'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment #ok $config->set('print_center', [50,80]), 'valid point coordinates'; #is_deeply $config->get('print_center'), [50,80], 'set/get point'; #is $config->serialize('print_center'), '50,80', 'serialize point'; #$config->set_deserialize('print_center', '20,10'); #is_deeply $config->get('print_center'), [20,10], 'deserialize point'; #ok !$config->set('print_center', ['t',80]), 'invalid point X'; #ok !$config->set('print_center', [50,'t']), 'invalid point Y'; $config->set('use_relative_e_distances', 1); is $config->get('use_relative_e_distances'), 1, 'set/get bool'; is $config->serialize('use_relative_e_distances'), '1', 'serialize bool'; $config->set('gcode_flavor', 'teacup'); is $config->get('gcode_flavor'), 'teacup', 'set/get enum'; is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum'; $config->set_deserialize('gcode_flavor', 'mach3'); is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('gcode_flavor', 'machinekit'); is $config->get('gcode_flavor'), 'machinekit', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('fill_pattern', 'line'); is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)'; $config->set_deserialize('support_material_pattern', 'pillars'); is $config->get('support_material_pattern'), 'pillars', 'deserialize enum (support_material_pattern)'; $config->set('extruder_offset', [[10,20],[30,45]]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points'; $config->set_deserialize('extruder_offset', '20x10'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points'; $config->set_deserialize('extruder_offset', '0x0'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[0,0]], 'deserialize points'; { my @values = ([10,20]); $values[2] = [10,20]; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('extruder_offset', \@values), 'reject undef points'; } # truncate ->get() to first decimal digit $config->set('nozzle_diameter', [0.2,3]); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats'; is $config->serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; $config->set_deserialize('nozzle_diameter', '0.1,0.4'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats'; $config->set_deserialize('nozzle_diameter', '3'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [3], 'deserialize a single float'; { my @values = (0.4); $values[2] = 2; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('nozzle_diameter', \@values), 'reject undef floats'; } $config->set('temperature', [180,210]); is_deeply $config->get('temperature'), [180,210], 'set/get ints'; is $config->serialize('temperature'), '180,210', 'serialize ints'; $config->set_deserialize('temperature', '195,220'); is_deeply $config->get('temperature'), [195,220], 'deserialize ints'; { my @values = (180); $values[2] = 200; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('temperature', \@values), 'reject undef ints'; } $config->set('wipe', [1,0]); is_deeply $config->get('wipe'), [1,0], 'set/get bools'; is $config->get_at('wipe', 0), 1, 'get_at bools'; is $config->get_at('wipe', 1), 0, 'get_at bools'; is $config->get_at('wipe', 9), 1, 'get_at bools'; is $config->serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; $config->set_deserialize('wipe', ''); is_deeply $config->get('wipe'), [], 'deserialize bools from empty string'; $config->set_deserialize('retract_layer_change', 0); is_deeply $config->get('retract_layer_change'), [0], 'deserialize bools from non-string value'; { my @values = (1); $values[2] = 1; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('wipe', \@values), 'reject undef bools'; } $config->set('post_process', ['foo','bar']); is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings'; is $config->serialize('post_process'), 'foo;bar', 'serialize strings'; $config->set_deserialize('post_process', 'bar;baz'); is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings'; { my @values = ('foo'); $values[2] = 'bar'; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('post_process', \@values), 'reject undef strings'; } is_deeply [ sort @{$config->get_keys} ], [ sort keys %{$config->as_hash} ], 'get_keys and as_hash'; } { my $config = Slic3r::Config->new; $config->set('perimeters', 2); # test that no crash happens when using set_deserialize() with a key that hasn't been set() yet $config->set_deserialize('filament_diameter', '3'); my $config2 = Slic3r::Config::Full->new; $config2->apply_dynamic($config); is $config2->get('perimeters'), 2, 'apply_dynamic'; } { my $config = Slic3r::Config::Full->new; my $config2 = Slic3r::Config->new; $config2->apply_static($config); is $config2->get('perimeters'), Slic3r::Config::print_config_def()->{perimeters}{default}, 'apply_static and print_config_def'; $config->set('top_solid_infill_speed', 70); is $config->get_abs_value('top_solid_infill_speed'), 70, 'get_abs_value() works when ratio_over references a floatOrPercent option'; } { my $config = Slic3r::Config->new; $config->set('fill_pattern', 'line'); my $config2 = Slic3r::Config->new; $config2->set('fill_pattern', 'hilbertcurve'); is $config->get('fill_pattern'), 'line', 'no interferences between DynamicConfig objects'; } { my $config = Slic3r::Config->new; # the pair [0,0] is part of the test, since it checks whether the 0x0 serialized value is correctly parsed $config->set('extruder_offset', [ [0,0], [20,0], [0,20] ]); my $config2 = Slic3r::Config->new; $config2->apply($config); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [ map $_->pp, @{$config2->get('extruder_offset')} ], 'apply dynamic over dynamic'; } { my $config = Slic3r::Config->new; $config->set('extruder', 2); $config->set('perimeter_extruder', 3); $config->normalize; ok !$config->has('extruder'), 'extruder option is removed after normalize()'; is $config->get('infill_extruder'), 2, 'undefined extruder is populated with default extruder'; is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder'; } { my $config = Slic3r::Config->new; $config->set('infill_extruder', 2); $config->normalize; is $config->get('solid_infill_extruder'), 2, 'undefined solid infill extruder is populated with infill extruder'; } { my $config = Slic3r::Config->new; $config->set('spiral_vase', 1); $config->set('retract_layer_change', [1,0]); $config->normalize; is_deeply $config->get('retract_layer_change'), [0,0], 'retract_layer_change is disabled with spiral_vase'; } __END__ Slic3r-1.2.9/xs/t/16_flow.t000066400000000000000000000011201254023100400152020ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 2; { my $flow = Slic3r::Flow->new_from_width( role => Slic3r::Flow::FLOW_ROLE_PERIMETER, width => '1', nozzle_diameter => 0.5, layer_height => 0.3, bridge_flow_ratio => 1, ); isa_ok $flow, 'Slic3r::Flow', 'new_from_width'; } { my $flow = Slic3r::Flow->new( width => 1, height => 0.4, nozzle_diameter => 0.5, ); isa_ok $flow, 'Slic3r::Flow', 'new'; } __END__ Slic3r-1.2.9/xs/t/17_boundingbox.t000066400000000000000000000013461254023100400165640ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 5; { my @points = ( Slic3r::Point->new(100, 200), Slic3r::Point->new(500, -600), ); my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@points); isa_ok $bb, 'Slic3r::Geometry::BoundingBox', 'new_from_points'; is_deeply $bb->min_point->pp, [100,-600], 'min_point'; is_deeply $bb->max_point->pp, [500,200], 'max_point'; } { my $bb = Slic3r::Geometry::BoundingBox->new; $bb->merge_point(Slic3r::Point->new(10, 10)); is_deeply $bb->min_point->pp, [10,10], 'min_point equals to the only defined point'; is_deeply $bb->max_point->pp, [10,10], 'max_point equals to the only defined point'; } __END__ Slic3r-1.2.9/xs/t/18_motionplanner.t000066400000000000000000000075471254023100400171450ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../../lib"; } use Slic3r::XS; use Test::More tests => 22; my $square = Slic3r::Polygon->new( # ccw [100, 100], [200, 100], [200, 200], [100, 200], ); my $hole_in_square = Slic3r::Polygon->new( # cw [140, 140], [140, 160], [160, 160], [160, 140], ); $_->scale(1/0.000001) for $square, $hole_in_square; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); { my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(120, 120); my $to = Slic3r::Point->new(180,180); $_->scale(1/0.000001) for $from, $to; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon'; } { my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(80, 100); my $to = Slic3r::Point->new(220,200); $_->scale(1/0.000001) for $from, $to; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon'; } { my $expolygon2 = $expolygon->clone; $expolygon2->translate(300/0.000001, 0); my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(120, 120); my $to = Slic3r::Point->new(120 + 300, 120); $_->scale(1/0.000001) for $from, $to; ok $expolygon->contains_point($from), 'start point is contained in first expolygon'; ok $expolygon2->contains_point($to), 'end point is contained in second expolygon'; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; } { my $expolygons = [ Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]), Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]), ]; my $mp = Slic3r::MotionPlanner->new($expolygons); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(79120520, 107839491); my $to = Slic3r::Point->new(104664164, 108335852); ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon'; ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon'; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; } __END__ Slic3r-1.2.9/xs/t/19_model.t000066400000000000000000000011351254023100400153440ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 4; { my $model = Slic3r::Model->new; my $object = $model->_add_object; isa_ok $object, 'Slic3r::Model::Object::Ref'; isa_ok $object->origin_translation, 'Slic3r::Pointf3::Ref'; $object->origin_translation->translate(10,0,0); is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref'; my $lhr = [ [ 5, 10, 0.1 ] ]; $object->set_layer_height_ranges($lhr); is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; } __END__ Slic3r-1.2.9/xs/t/20_print.t000066400000000000000000000007231254023100400153720ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 5; { my $print = Slic3r::Print->_new; isa_ok $print, 'Slic3r::Print'; isa_ok $print->config, 'Slic3r::Config::Print::Ref'; isa_ok $print->default_object_config, 'Slic3r::Config::PrintObject::Ref'; isa_ok $print->default_region_config, 'Slic3r::Config::PrintRegion::Ref'; isa_ok $print->placeholder_parser, 'Slic3r::GCode::PlaceholderParser::Ref'; } __END__ Slic3r-1.2.9/xs/xsp/000077500000000000000000000000001254023100400141155ustar00rootroot00000000000000Slic3r-1.2.9/xs/xsp/BoundingBox.xsp000066400000000000000000000061451254023100400170750ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Point.hpp" %} %name{Slic3r::Geometry::BoundingBox} class BoundingBox { BoundingBox(); ~BoundingBox(); Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBox* bb) %code{% THIS->merge(*bb); %}; void merge_point(Point* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y); void offset(double delta); Clone polygon(); Clone size(); Clone center(); Clone min_point() %code{% RETVAL = THIS->min; %}; Clone max_point() %code{% RETVAL = THIS->max; %}; long x_min() %code{% RETVAL = THIS->min.x; %}; long x_max() %code{% RETVAL = THIS->max.x; %}; long y_min() %code{% RETVAL = THIS->min.y; %}; long y_max() %code{% RETVAL = THIS->max.y; %}; %{ BoundingBox* new_from_points(CLASS, points) char* CLASS Points points CODE: RETVAL = new BoundingBox(points); OUTPUT: RETVAL %} }; %name{Slic3r::Geometry::BoundingBoxf} class BoundingBoxf { BoundingBoxf(); ~BoundingBoxf(); Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBoxf* bb) %code{% THIS->merge(*bb); %}; void merge_point(Pointf* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y); Clone size(); Clone center(); Clone min_point() %code{% RETVAL = THIS->min; %}; Clone max_point() %code{% RETVAL = THIS->max; %}; double x_min() %code{% RETVAL = THIS->min.x; %}; double x_max() %code{% RETVAL = THIS->max.x; %}; double y_min() %code{% RETVAL = THIS->min.y; %}; double y_max() %code{% RETVAL = THIS->max.y; %}; void set_x_min(double val) %code{% THIS->min.x = val; %}; void set_x_max(double val) %code{% THIS->max.x = val; %}; void set_y_min(double val) %code{% THIS->min.y = val; %}; void set_y_max(double val) %code{% THIS->max.y = val; %}; %{ BoundingBoxf* new_from_points(CLASS, points) char* CLASS Pointfs points CODE: RETVAL = new BoundingBoxf(points); OUTPUT: RETVAL %} }; %name{Slic3r::Geometry::BoundingBoxf3} class BoundingBoxf3 { BoundingBoxf3(); ~BoundingBoxf3(); Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %}; void merge_point(Pointf3* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y, double z); Clone size(); Clone center(); Clone min_point() %code{% RETVAL = THIS->min; %}; Clone max_point() %code{% RETVAL = THIS->max; %}; double x_min() %code{% RETVAL = THIS->min.x; %}; double x_max() %code{% RETVAL = THIS->max.x; %}; double y_min() %code{% RETVAL = THIS->min.y; %}; double y_max() %code{% RETVAL = THIS->max.y; %}; double z_min() %code{% RETVAL = THIS->min.z; %}; double z_max() %code{% RETVAL = THIS->max.z; %}; }; Slic3r-1.2.9/xs/xsp/BridgeDetector.xsp000066400000000000000000000020061254023100400175350ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BridgeDetector.hpp" %} %name{Slic3r::BridgeDetector} class BridgeDetector { ~BridgeDetector(); bool detect_angle(); Polygons coverage() %code{% THIS->coverage(&RETVAL); %}; Polygons coverage_by_angle(double angle) %code{% THIS->coverage(angle, &RETVAL); %}; Polylines unsupported_edges() %code{% THIS->unsupported_edges(&RETVAL); %}; Polylines unsupported_edges_by_angle(double angle) %code{% THIS->unsupported_edges(angle, &RETVAL); %}; double angle() %code{% RETVAL = THIS->angle; %}; double resolution() %code{% RETVAL = THIS->resolution; %}; %{ BridgeDetector* BridgeDetector::new(expolygon, lower_slices, extrusion_width) ExPolygon* expolygon; ExPolygonCollection* lower_slices; long extrusion_width; CODE: RETVAL = new BridgeDetector(*expolygon, *lower_slices, extrusion_width); OUTPUT: RETVAL %} }; Slic3r-1.2.9/xs/xsp/Clipper.xsp000066400000000000000000000121761254023100400162560ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "clipper.hpp" #include "libslic3r/ClipperUtils.hpp" %} %package{Slic3r::Geometry::Clipper}; %{ IV _constant() ALIAS: JT_MITER = jtMiter JT_ROUND = jtRound JT_SQUARE = jtSquare CLIPPER_OFFSET_SCALE = CLIPPER_OFFSET_SCALE CODE: RETVAL = ix; OUTPUT: RETVAL Polygons offset(polygons, delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta double scale ClipperLib::JoinType joinType double miterLimit CODE: offset(polygons, &RETVAL, delta, scale, joinType, miterLimit); OUTPUT: RETVAL ExPolygons offset_ex(polygons, delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta double scale ClipperLib::JoinType joinType double miterLimit CODE: offset(polygons, &RETVAL, delta, scale, joinType, miterLimit); OUTPUT: RETVAL Polygons offset2(polygons, delta1, delta2, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 double scale ClipperLib::JoinType joinType double miterLimit CODE: offset2(polygons, &RETVAL, delta1, delta2, scale, joinType, miterLimit); OUTPUT: RETVAL ExPolygons offset2_ex(polygons, delta1, delta2, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 double scale ClipperLib::JoinType joinType double miterLimit CODE: offset2(polygons, &RETVAL, delta1, delta2, scale, joinType, miterLimit); OUTPUT: RETVAL Polygons diff(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: diff(subject, clip, &RETVAL, safety_offset); OUTPUT: RETVAL ExPolygons diff_ex(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: diff(subject, clip, &RETVAL, safety_offset); OUTPUT: RETVAL Polylines diff_pl(subject, clip) Polylines subject Polygons clip CODE: diff(subject, clip, &RETVAL); OUTPUT: RETVAL Polylines diff_ppl(subject, clip) Polygons subject Polygons clip CODE: diff(subject, clip, &RETVAL); OUTPUT: RETVAL Polygons intersection(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: intersection(subject, clip, &RETVAL, safety_offset); OUTPUT: RETVAL ExPolygons intersection_ex(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: intersection(subject, clip, &RETVAL, safety_offset); OUTPUT: RETVAL Polylines intersection_pl(subject, clip) Polylines subject Polygons clip CODE: intersection(subject, clip, &RETVAL); OUTPUT: RETVAL Polylines intersection_ppl(subject, clip) Polygons subject Polygons clip CODE: intersection(subject, clip, &RETVAL); OUTPUT: RETVAL ExPolygons xor_ex(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: xor_(subject, clip, &RETVAL, safety_offset); OUTPUT: RETVAL Polygons union(subject, safety_offset = false) Polygons subject bool safety_offset CODE: union_(subject, &RETVAL, safety_offset); OUTPUT: RETVAL ExPolygons union_ex(subject, safety_offset = false) Polygons subject bool safety_offset CODE: union_(subject, &RETVAL, safety_offset); OUTPUT: RETVAL SV* union_pt(subject, safety_offset = false) Polygons subject bool safety_offset CODE: // perform operation ClipperLib::PolyTree polytree; union_pt(subject, &polytree, safety_offset); RETVAL = polynode_children_2_perl(polytree); OUTPUT: RETVAL Polygons union_pt_chained(subject, safety_offset = false) Polygons subject bool safety_offset CODE: union_pt_chained(subject, &RETVAL, safety_offset); OUTPUT: RETVAL Polygons simplify_polygons(subject) Polygons subject CODE: simplify_polygons(subject, &RETVAL); OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/Config.xsp000066400000000000000000000266311254023100400160660ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/PrintConfig.hpp" %} %name{Slic3r::Config} class DynamicPrintConfig { DynamicPrintConfig(); ~DynamicPrintConfig(); bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); SV* get_at(t_config_option_key opt_key, int i); bool set(t_config_option_key opt_key, SV* value); bool set_deserialize(t_config_option_key opt_key, SV* str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector diff(DynamicPrintConfig* other) %code{% RETVAL = THIS->diff(*other); %}; bool equals(DynamicPrintConfig* other) %code{% RETVAL = THIS->equals(*other); %}; void apply_static(FullPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; void erase(t_config_option_key opt_key); void normalize(); }; %name{Slic3r::Config::GCode} class GCodeConfig { GCodeConfig(); ~GCodeConfig(); bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); SV* get_at(t_config_option_key opt_key, int i); bool set(t_config_option_key opt_key, SV* value); bool set_deserialize(t_config_option_key opt_key, SV* str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply_print_config(PrintConfig* other) %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; std::string get_extrusion_axis(); }; %name{Slic3r::Config::Print} class PrintConfig { PrintConfig(); ~PrintConfig(); bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); SV* get_at(t_config_option_key opt_key, int i); bool set(t_config_option_key opt_key, SV* value); bool set_deserialize(t_config_option_key opt_key, SV* str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; std::string get_extrusion_axis(); }; %name{Slic3r::Config::PrintRegion} class PrintRegionConfig { PrintRegionConfig(); ~PrintRegionConfig(); bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); SV* get_at(t_config_option_key opt_key, int i); bool set(t_config_option_key opt_key, SV* value); bool set_deserialize(t_config_option_key opt_key, SV* str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(PrintRegionConfig* other) %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; }; %name{Slic3r::Config::PrintObject} class PrintObjectConfig { PrintObjectConfig(); ~PrintObjectConfig(); bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); SV* get_at(t_config_option_key opt_key, int i); bool set(t_config_option_key opt_key, SV* value); bool set_deserialize(t_config_option_key opt_key, SV* str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(PrintObjectConfig* other) %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; }; %name{Slic3r::Config::Full} class FullPrintConfig { FullPrintConfig(); ~FullPrintConfig(); bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); SV* get_at(t_config_option_key opt_key, int i); bool set(t_config_option_key opt_key, SV* value); bool set_deserialize(t_config_option_key opt_key, SV* str); void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply_print_config(PrintConfig* other) %code{% THIS->apply(*other, true); %}; void apply_object_config(PrintObjectConfig* other) %code{% THIS->apply(*other, true); %}; void apply_region_config(PrintRegionConfig* other) %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; std::string get_extrusion_axis(); }; %package{Slic3r::Config}; %{ PROTOTYPES: DISABLE SV* print_config_def() CODE: FullPrintConfig config; t_optiondef_map* def = config.def; HV* options_hv = newHV(); for (t_optiondef_map::iterator oit = def->begin(); oit != def->end(); ++oit) { HV* hv = newHV(); t_config_option_key opt_key = oit->first; ConfigOptionDef* optdef = &oit->second; const char* opt_type; if (optdef->type == coFloat || optdef->type == coFloats || optdef->type == coFloatOrPercent) { opt_type = "f"; } else if (optdef->type == coPercent) { opt_type = "percent"; } else if (optdef->type == coInt || optdef->type == coInts) { opt_type = "i"; } else if (optdef->type == coString) { opt_type = "s"; } else if (optdef->type == coStrings) { opt_type = "s@"; } else if (optdef->type == coPoint || optdef->type == coPoints) { opt_type = "point"; } else if (optdef->type == coBool || optdef->type == coBools) { opt_type = "bool"; } else if (optdef->type == coEnum) { opt_type = "select"; } else { throw "Unknown option type"; } (void)hv_stores( hv, "type", newSVpv(opt_type, 0) ); (void)hv_stores( hv, "gui_type", newSVpvn(optdef->gui_type.c_str(), optdef->gui_type.length()) ); (void)hv_stores( hv, "gui_flags", newSVpvn(optdef->gui_flags.c_str(), optdef->gui_flags.length()) ); (void)hv_stores( hv, "label", newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) ); if (!optdef->full_label.empty()) (void)hv_stores( hv, "full_label", newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) ); (void)hv_stores( hv, "category", newSVpvn(optdef->category.c_str(), optdef->category.length()) ); (void)hv_stores( hv, "tooltip", newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) ); (void)hv_stores( hv, "sidetext", newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) ); (void)hv_stores( hv, "cli", newSVpvn(optdef->cli.c_str(), optdef->cli.length()) ); (void)hv_stores( hv, "ratio_over", newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) ); (void)hv_stores( hv, "multiline", newSViv(optdef->multiline ? 1 : 0) ); (void)hv_stores( hv, "full_width", newSViv(optdef->full_width ? 1 : 0) ); (void)hv_stores( hv, "readonly", newSViv(optdef->readonly ? 1 : 0) ); (void)hv_stores( hv, "height", newSViv(optdef->height) ); (void)hv_stores( hv, "width", newSViv(optdef->width) ); (void)hv_stores( hv, "min", newSViv(optdef->min) ); (void)hv_stores( hv, "max", newSViv(optdef->max) ); // aliases if (!optdef->aliases.empty()) { AV* av = newAV(); av_fill(av, optdef->aliases.size()-1); for (std::vector::iterator it = optdef->aliases.begin(); it != optdef->aliases.end(); ++it) av_store(av, it - optdef->aliases.begin(), newSVpvn(it->c_str(), it->length())); (void)hv_stores( hv, "aliases", newRV_noinc((SV*)av) ); } // shortcut if (!optdef->shortcut.empty()) { AV* av = newAV(); av_fill(av, optdef->shortcut.size()-1); for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) av_store(av, it - optdef->shortcut.begin(), newSVpvn(it->c_str(), it->length())); (void)hv_stores( hv, "shortcut", newRV_noinc((SV*)av) ); } // enum_values if (!optdef->enum_values.empty()) { AV* av = newAV(); av_fill(av, optdef->enum_values.size()-1); for (std::vector::iterator it = optdef->enum_values.begin(); it != optdef->enum_values.end(); ++it) av_store(av, it - optdef->enum_values.begin(), newSVpvn(it->c_str(), it->length())); (void)hv_stores( hv, "values", newRV_noinc((SV*)av) ); } // enum_labels if (!optdef->enum_labels.empty()) { AV* av = newAV(); av_fill(av, optdef->enum_labels.size()-1); for (std::vector::iterator it = optdef->enum_labels.begin(); it != optdef->enum_labels.end(); ++it) av_store(av, it - optdef->enum_labels.begin(), newSVpvn_utf8(it->c_str(), it->length(), true)); (void)hv_stores( hv, "labels", newRV_noinc((SV*)av) ); } (void)hv_stores( hv, "default", config.get(opt_key) ); (void)hv_store( options_hv, opt_key.c_str(), opt_key.length(), newRV_noinc((SV*)hv), 0 ); } RETVAL = newRV_noinc((SV*)options_hv); OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/ExPolygon.xsp000066400000000000000000000037501254023100400166020ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExPolygon.hpp" %} %name{Slic3r::ExPolygon} class ExPolygon { ~ExPolygon(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = THIS->to_AV(); %}; SV* pp() %code{% RETVAL = THIS->to_SV_pureperl(); %}; Ref contour() %code{% RETVAL = &(THIS->contour); %}; Polygons* holes() %code{% RETVAL = &(THIS->holes); %}; void scale(double factor); void translate(double x, double y); double area(); bool is_valid(); bool contains_line(Line* line) %code{% RETVAL = THIS->contains(*line); %}; bool contains_polyline(Polyline* polyline) %code{% RETVAL = THIS->contains(*polyline); %}; bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; ExPolygons simplify(double tolerance); Polygons simplify_p(double tolerance); Polylines medial_axis(double max_width, double min_width) %code{% THIS->medial_axis(max_width, min_width, &RETVAL); %}; Polygons get_trapezoids(double angle) %code{% THIS->get_trapezoids(&RETVAL, angle); %}; Polygons get_trapezoids2(double angle) %code{% THIS->get_trapezoids2(&RETVAL, angle); %}; Polygons triangulate() %code{% THIS->triangulate(&RETVAL); %}; Polygons triangulate_pp() %code{% THIS->triangulate_pp(&RETVAL); %}; %{ ExPolygon* ExPolygon::new(...) CODE: RETVAL = new ExPolygon (); // ST(0) is class name, ST(1) is contour and others are holes RETVAL->contour.from_SV_check(ST(1)); RETVAL->holes.resize(items-2); for (unsigned int i = 2; i < items; i++) { RETVAL->holes[i-2].from_SV_check(ST(i)); } OUTPUT: RETVAL void ExPolygon::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; center.from_SV_check(center_sv); THIS->rotate(angle, center); %} }; Slic3r-1.2.9/xs/xsp/ExPolygonCollection.xsp000066400000000000000000000044741254023100400206220ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExPolygonCollection.hpp" %} %name{Slic3r::ExPolygon::Collection} class ExPolygonCollection { ~ExPolygonCollection(); Clone clone() %code{% RETVAL = THIS; %}; void clear() %code{% THIS->expolygons.clear(); %}; void scale(double factor); void translate(double x, double y); void rotate(double angle, Point* center) %code{% THIS->rotate(angle, *center); %}; int count() %code{% RETVAL = THIS->expolygons.size(); %}; bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; bool contains_line(Line* line) %code{% RETVAL = THIS->contains(*line); %}; bool contains_polyline(Polyline* polyline) %code{% RETVAL = THIS->contains(*polyline); %}; void simplify(double tolerance); Polygons polygons() %code{% RETVAL = *THIS; %}; Clone convex_hull(); %{ ExPolygonCollection* ExPolygonCollection::new(...) CODE: RETVAL = new ExPolygonCollection (); // ST(0) is class name, others are expolygons RETVAL->expolygons.resize(items-1); for (unsigned int i = 1; i < items; i++) { // Note: a COPY of the input is stored RETVAL->expolygons[i-1].from_SV_check(ST(i)); } OUTPUT: RETVAL SV* ExPolygonCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->expolygons.size()-1); int i = 0; for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL SV* ExPolygonCollection::pp() CODE: AV* av = newAV(); av_fill(av, THIS->expolygons.size()-1); int i = 0; for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { av_store(av, i++, (*it).to_SV_pureperl()); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void ExPolygonCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { ExPolygon expolygon; expolygon.from_SV_check( ST(i) ); THIS->expolygons.push_back(expolygon); } %} }; Slic3r-1.2.9/xs/xsp/Extruder.xsp000066400000000000000000000027751254023100400164660ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Extruder.hpp" %} %name{Slic3r::Extruder} class Extruder { Extruder(int id, GCodeConfig *config); ~Extruder(); void reset(); double extrude(double dE); double retract(double length, double restart_extra); double unretract(); double e_per_mm(double mm3_per_mm); double extruded_volume(); double used_filament(); int id() %code%{ RETVAL = THIS->id; %}; double E() %code%{ RETVAL = THIS->E; %}; double set_E(double val) %code%{ RETVAL = THIS->E = val; %}; double absolute_E() %code%{ RETVAL = THIS->absolute_E; %}; double set_absolute_E(double val) %code%{ RETVAL = THIS->absolute_E = val; %}; double retracted() %code%{ RETVAL = THIS->retracted; %}; double set_retracted(double val) %code%{ RETVAL = THIS->retracted = val; %}; double restart_extra() %code%{ RETVAL = THIS->restart_extra; %}; double set_restart_extra(double val) %code%{ RETVAL = THIS->restart_extra = val; %}; double e_per_mm3() %code%{ RETVAL = THIS->e_per_mm3; %}; double retract_speed_mm_min() %code%{ RETVAL = THIS->retract_speed_mm_min; %}; double filament_diameter(); double extrusion_multiplier(); double retract_length(); double retract_lift(); int retract_speed(); double retract_restart_extra(); double retract_length_toolchange(); double retract_restart_extra_toolchange(); }; Slic3r-1.2.9/xs/xsp/ExtrusionEntityCollection.xsp000066400000000000000000000077201254023100400220700ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntityCollection.hpp" %} %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { %name{_new} ExtrusionEntityCollection(); Clone clone() %code{% RETVAL = THIS->clone(); %}; void reverse(); void clear() %code{% THIS->entities.clear(); %}; ExtrusionEntityCollection* chained_path(bool no_reverse) %code{% RETVAL = new ExtrusionEntityCollection(); THIS->chained_path(RETVAL, no_reverse); %}; ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse) %code{% RETVAL = new ExtrusionEntityCollection(); THIS->chained_path_from(*start_near, RETVAL, no_reverse); %}; Clone first_point(); Clone last_point(); int count() %code{% RETVAL = THIS->entities.size(); %}; int items_count() %code{% RETVAL = THIS->items_count(); %}; ExtrusionEntityCollection* flatten() %code{% RETVAL = new ExtrusionEntityCollection(); THIS->flatten(RETVAL); %}; double min_mm3_per_mm(); bool empty() %code{% RETVAL = THIS->entities.empty(); %}; std::vector orig_indices() %code{% RETVAL = THIS->orig_indices; %}; Polygons grow(); %{ void ExtrusionEntityCollection::DESTROY() CODE: for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { delete *it; } delete THIS; SV* ExtrusionEntityCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->entities.size()-1); int i = 0; for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { SV* sv = newSV(0); // return our item by reference if (ExtrusionPath* path = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(path), path ); } else if (ExtrusionLoop* loop = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(loop), loop ); } else if (ExtrusionEntityCollection* collection = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(collection), collection ); } else { croak("Unexpected type in ExtrusionEntityCollection"); } av_store(av, i++, sv); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void ExtrusionEntityCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { if(!sv_isobject( ST(i) ) || (SvTYPE(SvRV( ST(i) )) != SVt_PVMG)) { croak("Argument %d is not object", i); } ExtrusionEntity* entity = (ExtrusionEntity *)SvIV((SV*)SvRV( ST(i) )); // append COPIES if (ExtrusionPath* path = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionPath(*path) ); } else if (ExtrusionLoop* loop = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionLoop(*loop) ); } else if(ExtrusionEntityCollection* collection = dynamic_cast(entity)) { THIS->entities.push_back( collection->clone() ); } else { croak("Argument %d is of unknown type", i); } } bool ExtrusionEntityCollection::no_sort(...) CODE: if (items > 1) { THIS->no_sort = SvTRUE(ST(1)); } RETVAL = THIS->no_sort; OUTPUT: RETVAL ExtrusionEntityCollection* ExtrusionEntityCollection::chained_path_indices(bool no_reverse) CODE: RETVAL = new ExtrusionEntityCollection(); THIS->chained_path(RETVAL, no_reverse, &RETVAL->orig_indices); OUTPUT: RETVAL %} }; Slic3r-1.2.9/xs/xsp/ExtrusionLoop.xsp000066400000000000000000000034451254023100400175110ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntity.hpp" %} %name{Slic3r::ExtrusionLoop} class ExtrusionLoop { ExtrusionLoop(); ~ExtrusionLoop(); Clone clone() %code{% RETVAL = THIS; %}; void reverse(); bool make_clockwise(); bool make_counter_clockwise(); Clone first_point(); Clone last_point(); Clone polygon(); void append(ExtrusionPath* path) %code{% THIS->paths.push_back(*path); %}; double length(); bool split_at_vertex(Point* point) %code{% RETVAL = THIS->split_at_vertex(*point); %}; void split_at(Point* point) %code{% THIS->split_at(*point); %}; ExtrusionPaths clip_end(double distance) %code{% THIS->clip_end(distance, &RETVAL); %}; bool has_overhang_point(Point* point) %code{% RETVAL = THIS->has_overhang_point(*point); %}; bool is_perimeter(); bool is_infill(); bool is_solid_infill(); Polygons grow(); %{ SV* ExtrusionLoop::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->paths.size()-1); int i = 0; for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL ExtrusionLoopRole ExtrusionLoop::role(...) CODE: if (items > 1) { THIS->role = (ExtrusionLoopRole)SvUV(ST(1)); } RETVAL = THIS->role; OUTPUT: RETVAL %} }; %package{Slic3r::ExtrusionLoop}; %{ IV _constant() ALIAS: EXTRL_ROLE_DEFAULT = elrDefault EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = elrContourInternalPerimeter PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/ExtrusionPath.xsp000066400000000000000000000075321254023100400174750ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" %} %name{Slic3r::ExtrusionPath} class ExtrusionPath { ~ExtrusionPath(); SV* arrayref() %code{% RETVAL = THIS->polyline.to_AV(); %}; SV* pp() %code{% RETVAL = THIS->polyline.to_SV_pureperl(); %}; void pop_back() %code{% THIS->polyline.points.pop_back(); %}; void reverse(); Lines lines() %code{% RETVAL = THIS->polyline.lines(); %}; Clone first_point(); Clone last_point(); void clip_end(double distance); void simplify(double tolerance); double length(); bool is_perimeter(); bool is_infill(); bool is_solid_infill(); bool is_bridge(); std::string gcode(Extruder* extruder, double e, double F, double xofs, double yofs, std::string extrusion_axis, std::string gcode_line_suffix); Polygons grow(); %{ ExtrusionPath* _new(CLASS, polyline_sv, role, mm3_per_mm, width, height) char* CLASS; SV* polyline_sv; ExtrusionRole role; double mm3_per_mm; float width; float height; CODE: RETVAL = new ExtrusionPath (role); RETVAL->polyline.from_SV_check(polyline_sv); RETVAL->mm3_per_mm = mm3_per_mm; RETVAL->width = width; RETVAL->height = height; OUTPUT: RETVAL Ref ExtrusionPath::polyline(...) CODE: if (items > 1) { THIS->polyline.from_SV_check( ST(1) ); } RETVAL = &(THIS->polyline); OUTPUT: RETVAL ExtrusionRole ExtrusionPath::role(...) CODE: if (items > 1) { THIS->role = (ExtrusionRole)SvUV(ST(1)); } RETVAL = THIS->role; OUTPUT: RETVAL double ExtrusionPath::mm3_per_mm(...) CODE: if (items > 1) { THIS->mm3_per_mm = (double)SvNV(ST(1)); } RETVAL = THIS->mm3_per_mm; OUTPUT: RETVAL float ExtrusionPath::width(...) CODE: if (items > 1) { THIS->width = (float)SvNV(ST(1)); } RETVAL = THIS->width; OUTPUT: RETVAL float ExtrusionPath::height(...) CODE: if (items > 1) { THIS->height = (float)SvNV(ST(1)); } RETVAL = THIS->height; OUTPUT: RETVAL void ExtrusionPath::append(...) CODE: for (unsigned int i = 1; i < items; i++) { Point p; p.from_SV_check(ST(i)); THIS->polyline.points.push_back(p); } ExtrusionEntityCollection* ExtrusionPath::intersect_expolygons(ExPolygonCollection* collection) CODE: RETVAL = new ExtrusionEntityCollection (); THIS->intersect_expolygons(*collection, RETVAL); OUTPUT: RETVAL ExtrusionEntityCollection* ExtrusionPath::subtract_expolygons(ExPolygonCollection* collection) CODE: RETVAL = new ExtrusionEntityCollection (); THIS->subtract_expolygons(*collection, RETVAL); OUTPUT: RETVAL %} }; %package{Slic3r::ExtrusionPath}; %{ IV _constant() ALIAS: EXTR_ROLE_PERIMETER = erPerimeter EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter EXTR_ROLE_FILL = erInternalInfill EXTR_ROLE_SOLIDFILL = erSolidInfill EXTR_ROLE_TOPSOLIDFILL = erTopSolidInfill EXTR_ROLE_BRIDGE = erBridgeInfill EXTR_ROLE_GAPFILL = erGapFill EXTR_ROLE_SKIRT = erSkirt EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial EXTR_ROLE_SUPPORTMATERIAL_INTERFACE = erSupportMaterialInterface PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/Flow.xsp000066400000000000000000000043521254023100400155640ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Flow.hpp" %} %name{Slic3r::Flow} class Flow { ~Flow(); %name{_new} Flow(float width, float height, float nozzle_diameter); void set_height(float height) %code{% THIS->height = height; %}; void set_bridge(bool bridge) %code{% THIS->bridge = bridge; %}; Clone clone() %code{% RETVAL = THIS; %}; float width() %code{% RETVAL = THIS->width; %}; float height() %code{% RETVAL = THIS->height; %}; float nozzle_diameter() %code{% RETVAL = THIS->nozzle_diameter; %}; bool bridge() %code{% RETVAL = THIS->bridge; %}; float spacing(); float spacing_to(Flow* other) %code{% RETVAL = THIS->spacing(*other); %}; long scaled_width(); long scaled_spacing(); double mm3_per_mm(); %{ Flow* _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) char* CLASS; FlowRole role; std::string width; float nozzle_diameter; float height; float bridge_flow_ratio; CODE: ConfigOptionFloatOrPercent optwidth; optwidth.deserialize(width); RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); OUTPUT: RETVAL Flow* _new_from_spacing(CLASS, spacing, nozzle_diameter, height, bridge) char* CLASS; float spacing; float nozzle_diameter; float height; bool bridge; CODE: RETVAL = new Flow(Flow::new_from_spacing(spacing, nozzle_diameter, height, bridge)); OUTPUT: RETVAL %} }; %package{Slic3r::Flow}; %{ IV _constant() ALIAS: FLOW_ROLE_EXTERNAL_PERIMETER = frExternalPerimeter FLOW_ROLE_PERIMETER = frPerimeter FLOW_ROLE_INFILL = frInfill FLOW_ROLE_SOLID_INFILL = frSolidInfill FLOW_ROLE_TOP_SOLID_INFILL = frTopSolidInfill FLOW_ROLE_SUPPORT_MATERIAL = frSupportMaterial FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE = frSupportMaterialInterface PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/GCodeWriter.xsp000066400000000000000000000051221254023100400170270ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/GCodeWriter.hpp" %} %name{Slic3r::GCode::Writer} class GCodeWriter { GCodeWriter(); ~GCodeWriter(); Ref config() %code%{ RETVAL = &THIS->config; %}; bool multiple_extruders() %code{% RETVAL = THIS->multiple_extruders; %}; Ref extruder(); std::string extrusion_axis(); void apply_print_config(PrintConfig* print_config) %code{% THIS->apply_print_config(*print_config); %}; void set_extruders(std::vector extruder_ids); std::string preamble(); std::string postamble(); std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1); std::string set_bed_temperature(unsigned int temperature, bool wait = false); std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_acceleration(unsigned int acceleration); std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false); bool need_toolchange(unsigned int extruder_id); std::string set_extruder(unsigned int extruder_id); std::string toolchange(unsigned int extruder_id); std::string set_speed(double F, std::string comment = std::string()); std::string travel_to_xy(Pointf* point, std::string comment = std::string()) %code{% RETVAL = THIS->travel_to_xy(*point, comment); %}; std::string travel_to_xyz(Pointf3* point, std::string comment = std::string()) %code{% RETVAL = THIS->travel_to_xyz(*point, comment); %}; std::string travel_to_z(double z, std::string comment = std::string()); bool will_move_z(double z); std::string extrude_to_xy(Pointf* point, double dE, std::string comment = std::string()) %code{% RETVAL = THIS->extrude_to_xy(*point, dE, comment); %}; std::string extrude_to_xyz(Pointf3* point, double dE, std::string comment = std::string()) %code{% RETVAL = THIS->extrude_to_xyz(*point, dE, comment); %}; std::string retract(); std::string retract_for_toolchange(); std::string unretract(); std::string lift(); std::string unlift(); Clone get_position() const; %{ SV* GCodeWriter::extruders() CODE: AV* av = newAV(); av_fill(av, THIS->extruders.size()-1); int i = 0; for (std::map::iterator it = THIS->extruders.begin(); it != THIS->extruders.end(); ++it) { av_store(av, i++, perl_to_SV_ref(it->second)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; Slic3r-1.2.9/xs/xsp/GUI_3DScene.xsp000066400000000000000000000016371254023100400166100ustar00rootroot00000000000000%module{Slic3r::XS}; #include #include "libslic3r/GUI/3DScene.hpp" %name{Slic3r::GUI::_3DScene::GLVertexArray} class GLVertexArray { GLVertexArray(); ~GLVertexArray(); void load_mesh(TriangleMesh* mesh) const %code%{ THIS->load_mesh(*mesh); %}; size_t size() const %code%{ RETVAL = THIS->verts.size(); %}; void* verts_ptr() const %code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->verts.front(); %}; void* norms_ptr() const %code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->norms.front(); %}; }; %package{Slic3r::GUI::_3DScene}; %{ void _extrusionentity_to_verts_do(Lines lines, std::vector widths, std::vector heights, bool closed, double top_z, Point* copy, GLVertexArray* qverts, GLVertexArray* tverts) CODE: _3DScene::_extrusionentity_to_verts_do(lines, widths, heights, closed, top_z, *copy, qverts, tverts); %}Slic3r-1.2.9/xs/xsp/Geometry.xsp000066400000000000000000000037711254023100400164540ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Geometry.hpp" %} %package{Slic3r::Geometry}; Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb) %code{% RETVAL = Slic3r::Geometry::arrange(total_parts, *part, dist, *bb); %}; %{ bool directions_parallel(angle1, angle2) double angle1 double angle2 CODE: RETVAL = Slic3r::Geometry::directions_parallel(angle1, angle2); OUTPUT: RETVAL bool directions_parallel_within(angle1, angle2, max_diff) double angle1 double angle2 double max_diff CODE: RETVAL = Slic3r::Geometry::directions_parallel(angle1, angle2, max_diff); OUTPUT: RETVAL Clone convex_hull(points) Points points CODE: RETVAL = Slic3r::Geometry::convex_hull(points); OUTPUT: RETVAL std::vector chained_path(points) Points points CODE: Slic3r::Geometry::chained_path(points, RETVAL); OUTPUT: RETVAL std::vector chained_path_from(points, start_from) Points points Point* start_from CODE: Slic3r::Geometry::chained_path(points, RETVAL, *start_from); OUTPUT: RETVAL double rad2deg(angle) double angle CODE: RETVAL = Slic3r::Geometry::rad2deg(angle); OUTPUT: RETVAL double rad2deg_dir(angle) double angle CODE: RETVAL = Slic3r::Geometry::rad2deg_dir(angle); OUTPUT: RETVAL double deg2rad(angle) double angle CODE: RETVAL = Slic3r::Geometry::deg2rad(angle); OUTPUT: RETVAL Polygons simplify_polygons(polygons, tolerance) Polygons polygons double tolerance CODE: Slic3r::Geometry::simplify_polygons(polygons, tolerance, &RETVAL); OUTPUT: RETVAL IV _constant() ALIAS: X = X Y = Y Z = Z PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/Layer.xsp000066400000000000000000000111121254023100400157210ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Layer.hpp" %} %name{Slic3r::Layer::Region} class LayerRegion { // owned by Layer, no constructor/destructor Ref layer(); Ref region(); Ref slices() %code%{ RETVAL = &THIS->slices; %}; Ref thin_fills() %code%{ RETVAL = &THIS->thin_fills; %}; Ref fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; Ref bridged() %code%{ RETVAL = &THIS->bridged; %}; Ref unsupported_bridge_edges() %code%{ RETVAL = &THIS->unsupported_bridge_edges; %}; Ref perimeters() %code%{ RETVAL = &THIS->perimeters; %}; Ref fills() %code%{ RETVAL = &THIS->fills; %}; Clone flow(FlowRole role, bool bridge = false, double width = -1) %code%{ RETVAL = THIS->flow(role, bridge, width); %}; void merge_slices(); void prepare_fill_surfaces(); }; %name{Slic3r::Layer} class Layer { // owned by PrintObject, no constructor/destructor int id(); void set_id(int id); Ref object(); Ref upper_layer() %code%{ RETVAL = THIS->upper_layer; %}; Ref lower_layer() %code%{ RETVAL = THIS->lower_layer; %}; bool slicing_errors() %code%{ RETVAL = THIS->slicing_errors; %}; coordf_t slice_z() %code%{ RETVAL = THIS->slice_z; %}; coordf_t print_z() %code%{ RETVAL = THIS->print_z; %}; coordf_t height() %code%{ RETVAL = THIS->height; %}; void set_upper_layer(Layer *layer) %code%{ THIS->upper_layer = layer; %}; void set_lower_layer(Layer *layer) %code%{ THIS->lower_layer = layer; %}; bool has_upper_layer() %code%{ RETVAL = (THIS->upper_layer != NULL); %}; bool has_lower_layer() %code%{ RETVAL = (THIS->lower_layer != NULL); %}; size_t region_count(); Ref get_region(int idx); Ref add_region(PrintRegion* print_region); Ref slices() %code%{ RETVAL = &THIS->slices; %}; int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; void make_slices(); void merge_slices(); bool any_internal_region_slice_contains_polyline(Polyline* polyline) %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; bool any_bottom_region_slice_contains_polyline(Polyline* polyline) %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %}; }; %name{Slic3r::Layer::Support} class SupportLayer { // owned by PrintObject, no constructor/destructor Ref support_islands() %code%{ RETVAL = &THIS->support_islands; %}; Ref support_fills() %code%{ RETVAL = &THIS->support_fills; %}; Ref support_interface_fills() %code%{ RETVAL = &THIS->support_interface_fills; %}; // copies of some Layer methods, because the parameter wrapper code // gets confused about getting a Layer::Support instead of a Layer int id(); void set_id(int id); Ref object(); Ref upper_layer() %code%{ RETVAL = (SupportLayer*)THIS->upper_layer; %}; Ref lower_layer() %code%{ RETVAL = (SupportLayer*)THIS->lower_layer; %}; bool slicing_errors() %code%{ RETVAL = THIS->slicing_errors; %}; coordf_t slice_z() %code%{ RETVAL = THIS->slice_z; %}; coordf_t print_z() %code%{ RETVAL = THIS->print_z; %}; coordf_t height() %code%{ RETVAL = THIS->height; %}; void set_upper_layer(SupportLayer *layer) %code%{ THIS->upper_layer = layer; %}; void set_lower_layer(SupportLayer *layer) %code%{ THIS->lower_layer = layer; %}; bool has_upper_layer() %code%{ RETVAL = (THIS->upper_layer != NULL); %}; bool has_lower_layer() %code%{ RETVAL = (THIS->lower_layer != NULL); %}; size_t region_count(); Ref get_region(int idx); Ref add_region(PrintRegion* print_region); Ref slices() %code%{ RETVAL = &THIS->slices; %}; bool any_internal_region_slice_contains_polyline(Polyline* polyline) %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %}; bool any_bottom_region_slice_contains_polyline(Polyline* polyline) %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %}; }; Slic3r-1.2.9/xs/xsp/Line.xsp000066400000000000000000000043111254023100400155370ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Line.hpp" #include "libslic3r/Polyline.hpp" %} %name{Slic3r::Line} class Line { ~Line(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = THIS->to_AV(); %}; SV* pp() %code{% RETVAL = THIS->to_SV_pureperl(); %}; Ref a() %code{% RETVAL=&THIS->a; %}; Ref b() %code{% RETVAL=&THIS->b; %}; void reverse(); void scale(double factor); void translate(double x, double y); double length(); double atan2_(); double orientation(); double direction(); bool parallel_to(double angle); bool parallel_to_line(Line* line) %code{% RETVAL = THIS->parallel_to(*line); %}; Clone midpoint(); Clone point_at(double distance); Clone intersection_infinite(Line* other) %code{% Point p; bool res = THIS->intersection_infinite(*other, &p); if (!res) CONFESS("Intersection failed"); RETVAL = p; %}; Clone as_polyline() %code{% RETVAL = Polyline(*THIS); %}; Clone normal(); Clone vector(); %{ Line* Line::new(...) CODE: RETVAL = new Line (); // ST(0) is class name, ST(1) and ST(2) are endpoints RETVAL->a.from_SV_check( ST(1) ); RETVAL->b.from_SV_check( ST(2) ); OUTPUT: RETVAL void Line::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; center.from_SV_check(center_sv); THIS->rotate(angle, center); bool Line::coincides_with(line_sv) SV* line_sv; CODE: Line line; line.from_SV_check(line_sv); RETVAL = THIS->coincides_with(line); OUTPUT: RETVAL %} }; %name{Slic3r::Linef3} class Linef3 { Linef3(Pointf3* a, Pointf3* b) %code{% RETVAL = new Linef3(*a, *b); %}; ~Linef3(); Clone clone() %code{% RETVAL = THIS; %}; Ref a() %code{% RETVAL = &THIS->a; %}; Ref b() %code{% RETVAL = &THIS->b; %}; Clone intersect_plane(double z); void scale(double factor); }; Slic3r-1.2.9/xs/xsp/Model.xsp000066400000000000000000000200451254023100400157120ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Model.hpp" #include "libslic3r/PrintConfig.hpp" %} %name{Slic3r::Model} class Model { Model(); ~Model(); Clone clone() %code%{ RETVAL = THIS; %}; %name{_add_object} Ref add_object(); Ref _add_object_clone(ModelObject* other, bool copy_volumes = true) %code%{ RETVAL = THIS->add_object(*other, copy_volumes); %}; void delete_object(size_t idx); void clear_objects(); size_t objects_count() %code%{ RETVAL = THIS->objects.size(); %}; Ref get_object(int idx) %code%{ RETVAL = THIS->objects.at(idx); %}; Ref get_material(t_model_material_id material_id) %code%{ RETVAL = THIS->get_material(material_id); if (RETVAL == NULL) { XSRETURN_UNDEF; } %}; %name{add_material} Ref add_material(t_model_material_id material_id); Ref add_material_clone(t_model_material_id material_id, ModelMaterial* other) %code%{ RETVAL = THIS->add_material(material_id, *other); %}; bool has_material(t_model_material_id material_id) const %code%{ RETVAL = (THIS->get_material(material_id) != NULL); %}; void delete_material(t_model_material_id material_id); void clear_materials(); std::vector material_names() const %code%{ for (ModelMaterialMap::iterator i = THIS->materials.begin(); i != THIS->materials.end(); ++i) { RETVAL.push_back(i->first); } %}; size_t material_count() const %code%{ RETVAL = THIS->materials.size(); %}; // void duplicate_objects_grid(coordf_t x, coordf_t y, coordf_t distance); // void duplicate_objects(size_t copies_num, coordf_t distance, const BoundingBox &bb); // void arrange_objects(coordf_t distance, const BoundingBox &bb); // void duplicate(size_t copies_num, coordf_t distance, const BoundingBox &bb); bool has_objects_with_no_instances(); bool add_default_instances(); Clone bounding_box(); void center_instances_around_point(Pointf* point) %code%{ THIS->center_instances_around_point(*point); %}; void align_instances_to_origin(); void translate(double x, double y, double z); Clone mesh(); Clone raw_mesh(); // void split_meshes(); // std::string get_material_name(t_model_material_id material_id); ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; }; %name{Slic3r::Model::Material} class ModelMaterial { Ref model() %code%{ RETVAL = THIS->get_model(); %}; Ref config() %code%{ RETVAL = &THIS->config; %}; std::string get_attribute(std::string name) %code%{ if (THIS->attributes.find(name) != THIS->attributes.end()) RETVAL = THIS->attributes[name]; %}; void set_attribute(std::string name, std::string value) %code%{ THIS->attributes[name] = value; %}; %{ SV* ModelMaterial::attributes() CODE: HV* hv = newHV(); for (t_model_material_attributes::const_iterator attr = THIS->attributes.begin(); attr != THIS->attributes.end(); ++attr) { (void)hv_store( hv, attr->first.c_str(), attr->first.length(), newSVpv(attr->second.c_str(), attr->second.length()), 0 ); } RETVAL = (SV*)newRV_noinc((SV*)hv); OUTPUT: RETVAL %} }; %name{Slic3r::Model::Object} class ModelObject { ModelVolumePtrs* volumes() %code%{ RETVAL = &THIS->volumes; %}; ModelInstancePtrs* instances() %code%{ RETVAL = &THIS->instances; %}; void invalidate_bounding_box(); void update_bounding_box(); Clone mesh(); Clone raw_mesh(); Clone raw_bounding_box(); Clone instance_bounding_box(int idx); Ref _bounding_box(BoundingBoxf3* new_bbox = NULL) %code{% if (NULL != new_bbox) { THIS->_bounding_box = *new_bbox; THIS->_bounding_box_valid = true; } if (!THIS->_bounding_box_valid) { XSRETURN_UNDEF; } RETVAL = &THIS->_bounding_box; %}; Clone bounding_box(); %name{_add_volume} Ref add_volume(TriangleMesh* mesh) %code%{ RETVAL = THIS->add_volume(*mesh); %}; Ref _add_volume_clone(ModelVolume* other) %code%{ RETVAL = THIS->add_volume(*other); %}; void delete_volume(size_t idx); void clear_volumes(); int volumes_count() %code%{ RETVAL = THIS->volumes.size(); %}; %name{_add_instance} Ref add_instance(); Ref _add_instance_clone(ModelInstance* other) %code%{ RETVAL = THIS->add_instance(*other); %}; void delete_last_instance(); void clear_instances(); int instances_count() %code%{ RETVAL = THIS->instances.size(); %}; std::string name() %code%{ RETVAL = THIS->name; %}; void set_name(std::string value) %code%{ THIS->name = value; %}; std::string input_file() %code%{ RETVAL = THIS->input_file; %}; void set_input_file(std::string value) %code%{ THIS->input_file = value; %}; Ref config() %code%{ RETVAL = &THIS->config; %}; Ref model() %code%{ RETVAL = THIS->get_model(); %}; t_layer_height_ranges layer_height_ranges() %code%{ RETVAL = THIS->layer_height_ranges; %}; void set_layer_height_ranges(t_layer_height_ranges ranges) %code%{ THIS->layer_height_ranges = ranges; %}; Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Pointf3* point) %code%{ THIS->origin_translation = *point; %}; bool needed_repair() const; int materials_count() const; int facets_count(); void center_around_origin(); void translate(double x, double y, double z); void scale_xyz(Pointf3* versor) %code{% THIS->scale(*versor); %}; void rotate(float angle, Axis axis); void flip(Axis axis); Model* cut(double z) %code%{ RETVAL = new Model(); THIS->cut(z, RETVAL); %}; ModelObjectPtrs* split_object() %code%{ RETVAL = new ModelObjectPtrs(); // leak? THIS->split(RETVAL); %}; }; %name{Slic3r::Model::Volume} class ModelVolume { Ref object() %code%{ RETVAL = THIS->get_object(); %}; std::string name() %code%{ RETVAL = THIS->name; %}; void set_name(std::string value) %code%{ THIS->name = value; %}; t_model_material_id material_id(); void set_material_id(t_model_material_id material_id) %code%{ THIS->material_id(material_id); %}; Ref material(); Ref config() %code%{ RETVAL = &THIS->config; %}; Ref mesh() %code%{ RETVAL = &THIS->mesh; %}; bool modifier() %code%{ RETVAL = THIS->modifier; %}; void set_modifier(bool modifier) %code%{ THIS->modifier = modifier; %}; ModelMaterial* assign_unique_material(); }; %name{Slic3r::Model::Instance} class ModelInstance { Ref object() %code%{ RETVAL = THIS->get_object(); %}; double rotation() %code%{ RETVAL = THIS->rotation; %}; double scaling_factor() %code%{ RETVAL = THIS->scaling_factor; %}; Ref offset() %code%{ RETVAL = &THIS->offset; %}; void set_rotation(double val) %code%{ THIS->rotation = val; %}; void set_scaling_factor(double val) %code%{ THIS->scaling_factor = val; %}; void set_offset(Pointf *offset) %code%{ THIS->offset = *offset; %}; void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; void transform_polygon(Polygon* polygon) const; }; Slic3r-1.2.9/xs/xsp/MotionPlanner.xsp000066400000000000000000000005411254023100400174360ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/MotionPlanner.hpp" %} %name{Slic3r::MotionPlanner} class MotionPlanner { MotionPlanner(ExPolygons islands); ~MotionPlanner(); int islands_count(); Clone shortest_path(Point* from, Point* to) %code%{ RETVAL = THIS->shortest_path(*from, *to); %}; }; Slic3r-1.2.9/xs/xsp/PlaceholderParser.xsp000066400000000000000000000025541254023100400202560ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include #include "libslic3r/PlaceholderParser.hpp" %} %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { %name{_new} PlaceholderParser(); ~PlaceholderParser(); void update_timestamp(); void apply_config(DynamicPrintConfig *config) %code%{ THIS->apply_config(*config); %}; void set(std::string key, std::string value); void set_multiple(std::string key, std::vector values) %code%{ THIS->set(key, values); %}; void _single_set(std::string k, std::string v) %code%{ THIS->_single[k] = v; %}; std::string _single_get(std::string k) %code%{ RETVAL = THIS->_single[k]; %}; std::string _multiple_get(std::string k) %code%{ RETVAL = THIS->_multiple[k]; %}; std::vector _single_keys() %code{% for (std::map::iterator i = THIS->_single.begin(); i != THIS->_single.end(); ++i) { RETVAL.push_back(i->first); } %}; std::vector _multiple_keys() %code{% for (std::map::iterator i = THIS->_multiple.begin(); i != THIS->_multiple.end(); ++i) { RETVAL.push_back(i->first); } %}; }; Slic3r-1.2.9/xs/xsp/Point.xsp000066400000000000000000000105061254023100400157440ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Point.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Polyline.hpp" %} %name{Slic3r::Point} class Point { Point(long _x = 0, long _y = 0); ~Point(); Clone clone() %code{% RETVAL=THIS; %}; void scale(double factor); void translate(double x, double y); SV* arrayref() %code{% RETVAL = THIS->to_SV_pureperl(); %}; SV* pp() %code{% RETVAL = THIS->to_SV_pureperl(); %}; long x() %code{% RETVAL = THIS->x; %}; long y() %code{% RETVAL = THIS->y; %}; void set_x(long val) %code{% THIS->x = val; %}; void set_y(long val) %code{% THIS->y = val; %}; int nearest_point_index(Points points); Clone nearest_point(Points points) %code{% Point p; THIS->nearest_point(points, &p); RETVAL = p; %}; double distance_to(Point* point) %code{% RETVAL = THIS->distance_to(*point); %}; double distance_to_line(Line* line) %code{% RETVAL = THIS->distance_to(*line); %}; double perp_distance_to_line(Line* line) %code{% RETVAL = THIS->perp_distance_to(*line); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw(*p1, *p2); %}; double ccw_angle(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw_angle(*p1, *p2); %}; Clone projection_onto_polygon(Polygon* polygon) %code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %}; Clone projection_onto_polyline(Polyline* polyline) %code{% RETVAL = new Point(THIS->projection_onto(*polyline)); %}; Clone projection_onto_line(Line* line) %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; Clone negative() %code{% RETVAL = new Point(THIS->negative()); %}; bool coincides_with_epsilon(Point* point) %code{% RETVAL = THIS->coincides_with_epsilon(*point); %}; %{ void Point::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; center.from_SV_check(center_sv); THIS->rotate(angle, center); bool Point::coincides_with(point_sv) SV* point_sv; CODE: Point point; point.from_SV_check(point_sv); RETVAL = THIS->coincides_with(point); OUTPUT: RETVAL %} }; %name{Slic3r::Point3} class Point3 { Point3(long _x = 0, long _y = 0, long _z = 0); ~Point3(); Clone clone() %code{% RETVAL = THIS; %}; long x() %code{% RETVAL = THIS->x; %}; long y() %code{% RETVAL = THIS->y; %}; long z() %code{% RETVAL = THIS->z; %}; }; %name{Slic3r::Pointf} class Pointf { Pointf(double _x = 0, double _y = 0); ~Pointf(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = THIS->to_SV_pureperl(); %}; SV* pp() %code{% RETVAL = THIS->to_SV_pureperl(); %}; double x() %code{% RETVAL = THIS->x; %}; double y() %code{% RETVAL = THIS->y; %}; void set_x(double val) %code{% THIS->x = val; %}; void set_y(double val) %code{% THIS->y = val; %}; void translate(double x, double y); void scale(double factor); void rotate(double angle, Pointf* center) %code{% THIS->rotate(angle, *center); %}; Clone negative() %code{% RETVAL = THIS->negative(); %}; Clone vector_to(Pointf* point) %code{% RETVAL = THIS->vector_to(*point); %}; }; %name{Slic3r::Pointf3} class Pointf3 { Pointf3(double _x = 0, double _y = 0, double _z = 0); ~Pointf3(); Clone clone() %code{% RETVAL = THIS; %}; double x() %code{% RETVAL = THIS->x; %}; double y() %code{% RETVAL = THIS->y; %}; double z() %code{% RETVAL = THIS->z; %}; void set_x(double val) %code{% THIS->x = val; %}; void set_y(double val) %code{% THIS->y = val; %}; void set_z(double val) %code{% THIS->z = val; %}; void translate(double x, double y, double z); void scale(double factor); double distance_to(Pointf3* point) %code{% RETVAL = THIS->distance_to(*point); %}; Clone negative() %code{% RETVAL = THIS->negative(); %}; Clone vector_to(Pointf3* point) %code{% RETVAL = THIS->vector_to(*point); %}; }; Slic3r-1.2.9/xs/xsp/Polygon.xsp000066400000000000000000000035301254023100400163010ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/BoundingBox.hpp" %} %name{Slic3r::Polygon} class Polygon { ~Polygon(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = THIS->to_AV(); %}; SV* pp() %code{% RETVAL = THIS->to_SV_pureperl(); %}; void scale(double factor); void translate(double x, double y); void reverse(); Lines lines(); Clone split_at_vertex(Point* point) %code{% RETVAL = THIS->split_at_vertex(*point); %}; Clone split_at_index(int index); Clone split_at_first_point(); Points equally_spaced_points(double distance); double length(); double area(); bool is_counter_clockwise(); bool is_clockwise(); bool make_counter_clockwise(); bool make_clockwise(); bool is_valid(); Clone first_point(); bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; Polygons simplify(double tolerance); Polygons triangulate_convex() %code{% THIS->triangulate_convex(&RETVAL); %}; Clone centroid(); Clone bounding_box(); std::string wkt(); Points concave_points(double angle); Points convex_points(double angle); %{ Polygon* Polygon::new(...) CODE: RETVAL = new Polygon (); // ST(0) is class name, ST(1) is first point RETVAL->points.resize(items-1); for (unsigned int i = 1; i < items; i++) { RETVAL->points[i-1].from_SV_check( ST(i) ); } OUTPUT: RETVAL void Polygon::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; center.from_SV_check(center_sv); THIS->rotate(angle, center); %} }; Slic3r-1.2.9/xs/xsp/Polyline.xsp000066400000000000000000000050271254023100400164500ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Polyline.hpp" %} %name{Slic3r::Polyline} class Polyline { ~Polyline(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = THIS->to_AV(); %}; SV* pp() %code{% RETVAL = THIS->to_SV_pureperl(); %}; void scale(double factor); void translate(double x, double y); void pop_back() %code{% THIS->points.pop_back(); %}; void reverse(); Lines lines(); Clone first_point(); Clone last_point(); Points equally_spaced_points(double distance); double length(); bool is_valid(); void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); void simplify(double tolerance); void simplify_by_visibility(ExPolygon* expolygon) %code{% THIS->simplify_by_visibility(*expolygon); %}; void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; bool is_straight(); Clone bounding_box(); void remove_duplicate_points(); std::string wkt(); %{ Polyline* Polyline::new(...) CODE: RETVAL = new Polyline (); // ST(0) is class name, ST(1) is first point RETVAL->points.resize(items-1); for (unsigned int i = 1; i < items; i++) { RETVAL->points[i-1].from_SV_check( ST(i) ); } OUTPUT: RETVAL void Polyline::append(...) CODE: for (unsigned int i = 1; i < items; i++) { Point p; p.from_SV_check( ST(i) ); THIS->points.push_back(p); } void Polyline::append_polyline(polyline) Polyline* polyline; CODE: for (Points::const_iterator it = polyline->points.begin(); it != polyline->points.end(); ++it) { THIS->points.push_back((*it)); } void Polyline::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; center.from_SV_check(center_sv); THIS->rotate(angle, center); Polygons Polyline::grow(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtSquare, miterLimit = 3) const float delta double scale ClipperLib::JoinType joinType double miterLimit CODE: offset(*THIS, &RETVAL, delta, scale, joinType, miterLimit); OUTPUT: RETVAL %} }; Slic3r-1.2.9/xs/xsp/PolylineCollection.xsp000066400000000000000000000042231254023100400204610ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/PolylineCollection.hpp" %} %name{Slic3r::Polyline::Collection} class PolylineCollection { ~PolylineCollection(); Clone clone() %code{% RETVAL = THIS; %}; void clear() %code{% THIS->polylines.clear(); %}; PolylineCollection* chained_path(bool no_reverse) %code{% RETVAL = new PolylineCollection(); THIS->chained_path(RETVAL, no_reverse); %}; PolylineCollection* chained_path_from(Point* start_near, bool no_reverse) %code{% RETVAL = new PolylineCollection(); THIS->chained_path_from(*start_near, RETVAL, no_reverse); %}; int count() %code{% RETVAL = THIS->polylines.size(); %}; Clone leftmost_point(); %{ PolylineCollection* PolylineCollection::new(...) CODE: RETVAL = new PolylineCollection (); // ST(0) is class name, others are Polylines RETVAL->polylines.resize(items-1); for (unsigned int i = 1; i < items; i++) { // Note: a COPY of the input is stored RETVAL->polylines[i-1].from_SV_check(ST(i)); } OUTPUT: RETVAL SV* PolylineCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->polylines.size()-1); int i = 0; for (Polylines::iterator it = THIS->polylines.begin(); it != THIS->polylines.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL SV* PolylineCollection::pp() CODE: AV* av = newAV(); av_fill(av, THIS->polylines.size()-1); int i = 0; for (Polylines::iterator it = THIS->polylines.begin(); it != THIS->polylines.end(); ++it) { av_store(av, i++, (*it).to_SV_pureperl()); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void PolylineCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { Polyline polyline; polyline.from_SV_check( ST(i) ); THIS->polylines.push_back(polyline); } %} }; Slic3r-1.2.9/xs/xsp/Print.xsp000066400000000000000000000166671254023100400157650ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Print.hpp" #include "libslic3r/PlaceholderParser.hpp" %} %package{Slic3r::Print::State}; %{ IV _constant() ALIAS: STEP_SLICE = posSlice STEP_PERIMETERS = posPerimeters STEP_PREPARE_INFILL = posPrepareInfill STEP_INFILL = posInfill STEP_SUPPORTMATERIAL = posSupportMaterial STEP_SKIRT = psSkirt STEP_BRIM = psBrim PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} %name{Slic3r::Print::Region} class PrintRegion { // owned by Print, no constructor/destructor Ref config() %code%{ RETVAL = &THIS->config; %}; Ref print(); Clone flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, PrintObject* object) %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; }; %name{Slic3r::Print::Object} class PrintObject { // owned by Print, no constructor/destructor void add_region_volume(int region_id, int volume_id); std::vector get_region_volumes(int region_id) %code%{ if (0 <= region_id && region_id < THIS->region_volumes.size()) RETVAL = THIS->region_volumes[region_id]; %}; int region_count() %code%{ RETVAL = THIS->print()->regions.size(); %}; Ref print(); Ref model_object(); Ref config() %code%{ RETVAL = &THIS->config; %}; Points copies(); t_layer_height_ranges layer_height_ranges() %code%{ RETVAL = THIS->layer_height_ranges; %}; Ref size() %code%{ RETVAL = &THIS->size; %}; Clone bounding_box(); Ref _copies_shift() %code%{ RETVAL = &THIS->_copies_shift; %}; bool typed_slices() %code%{ RETVAL = THIS->typed_slices; %}; void set_typed_slices(bool value) %code%{ THIS->typed_slices = value; %}; Points _shifted_copies() %code%{ RETVAL = THIS->_shifted_copies; %}; void set_shifted_copies(Points value) %code%{ THIS->_shifted_copies = value; %}; bool add_copy(Pointf* point) %code%{ RETVAL = THIS->add_copy(*point); %}; bool delete_last_copy(); bool delete_all_copies(); bool set_copies(Points copies); bool reload_model_instances(); void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; size_t total_layer_count(); size_t layer_count(); void clear_layers(); Ref get_layer(int idx); Ref add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_layer(int idx); size_t support_layer_count(); void clear_support_layers(); Ref get_support_layer(int idx); Ref add_support_layer(int id, coordf_t height, coordf_t print_z); void delete_support_layer(int idx); bool invalidate_state_by_config_options(std::vector opt_keys); bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); bool step_done(PrintObjectStep step) %code%{ RETVAL = THIS->state.is_done(step); %}; void set_step_done(PrintObjectStep step) %code%{ THIS->state.set_done(step); %}; void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; void bridge_over_infill(); int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; }; %name{Slic3r::Print} class Print { %name{_new} Print(); ~Print(); Ref config() %code%{ RETVAL = &THIS->config; %}; Ref default_object_config() %code%{ RETVAL = &THIS->default_object_config; %}; Ref default_region_config() %code%{ RETVAL = &THIS->default_region_config; %}; Ref placeholder_parser() %code%{ RETVAL = &THIS->placeholder_parser; %}; // TODO: status_cb Ref skirt() %code%{ RETVAL = &THIS->skirt; %}; Ref brim() %code%{ RETVAL = &THIS->brim; %}; PrintObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; void clear_objects(); Ref get_object(int idx); void delete_object(int idx); void reload_object(int idx); bool reload_model_instances(); size_t object_count() %code%{ RETVAL = THIS->objects.size(); %}; PrintRegionPtrs* regions() %code%{ RETVAL = &THIS->regions; %}; Ref get_region(int idx); Ref add_region(); size_t region_count() %code%{ RETVAL = THIS->regions.size(); %}; bool invalidate_state_by_config_options(std::vector opt_keys); bool invalidate_step(PrintStep step); bool invalidate_all_steps(); bool step_done(PrintStep step) %code%{ RETVAL = THIS->state.is_done(step); %}; bool object_step_done(PrintObjectStep step) %code%{ RETVAL = THIS->step_done(step); %}; void set_step_done(PrintStep step) %code%{ THIS->state.set_done(step); %}; void set_step_started(PrintStep step) %code%{ THIS->state.set_started(step); %}; std::vector object_extruders() %code%{ std::set extruders = THIS->object_extruders(); RETVAL.reserve(extruders.size()); for (std::set::const_iterator e = extruders.begin(); e != extruders.end(); ++e) { RETVAL.push_back(*e); } %}; std::vector support_material_extruders() %code%{ std::set extruders = THIS->support_material_extruders(); RETVAL.reserve(extruders.size()); for (std::set::const_iterator e = extruders.begin(); e != extruders.end(); ++e) { RETVAL.push_back(*e); } %}; std::vector extruders() %code%{ std::set extruders = THIS->extruders(); RETVAL.reserve(extruders.size()); for (std::set::const_iterator e = extruders.begin(); e != extruders.end(); ++e) { RETVAL.push_back(*e); } %}; void _simplify_slices(double distance); double max_allowed_layer_height() const; bool has_support_material() const; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; bool has_infinite_skirt(); bool has_skirt(); void validate() %code%{ try { THIS->validate(); } catch (PrintValidationException &e) { croak("%s\n", e.what()); } %}; Clone bounding_box(); Clone total_bounding_box(); double skirt_first_layer_height(); Clone brim_flow(); Clone skirt_flow(); %{ double Print::total_used_filament(...) CODE: if (items > 1) { THIS->total_used_filament = (double)SvNV(ST(1)); } RETVAL = THIS->total_used_filament; OUTPUT: RETVAL double Print::total_extruded_volume(...) CODE: if (items > 1) { THIS->total_extruded_volume = (double)SvNV(ST(1)); } RETVAL = THIS->total_extruded_volume; OUTPUT: RETVAL %} }; Slic3r-1.2.9/xs/xsp/SupportMaterial.xsp000066400000000000000000000003611254023100400200040ustar00rootroot00000000000000%module{Slic3r::XS}; #include #include "libslic3r/SupportMaterial.hpp" %package{Slic3r::Print::SupportMaterial}; %{ SV* MARGIN() PROTOTYPE: CODE: RETVAL = newSVnv(SUPPORT_MATERIAL_MARGIN); OUTPUT: RETVAL %}Slic3r-1.2.9/xs/xsp/Surface.xsp000066400000000000000000000057441254023100400162530ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Surface.hpp" #include "libslic3r/ClipperUtils.hpp" %} %name{Slic3r::Surface} class Surface { ~Surface(); Ref expolygon() %code{% RETVAL = &(THIS->expolygon); %}; double thickness() %code{% RETVAL = THIS->thickness; %}; unsigned short thickness_layers() %code{% RETVAL = THIS->thickness_layers; %}; double area(); bool is_solid() const; bool is_external() const; bool is_internal() const; bool is_bottom() const; bool is_bridge() const; %{ Surface* _new(CLASS, expolygon, surface_type, thickness, thickness_layers, bridge_angle, extra_perimeters) char* CLASS; ExPolygon* expolygon; SurfaceType surface_type; double thickness; unsigned short thickness_layers; double bridge_angle; unsigned short extra_perimeters; CODE: RETVAL = new Surface (surface_type, *expolygon); RETVAL->thickness = thickness; RETVAL->thickness_layers = thickness_layers; RETVAL->bridge_angle = bridge_angle; RETVAL->extra_perimeters = extra_perimeters; // we don't delete expolygon here because it's referenced by a Perl SV // whose DESTROY will take care of destruction OUTPUT: RETVAL SurfaceType Surface::surface_type(...) CODE: if (items > 1) { THIS->surface_type = (SurfaceType)SvUV(ST(1)); } RETVAL = THIS->surface_type; OUTPUT: RETVAL double Surface::bridge_angle(...) CODE: if (items > 1) { THIS->bridge_angle = (double)SvNV(ST(1)); } RETVAL = THIS->bridge_angle; OUTPUT: RETVAL unsigned short Surface::extra_perimeters(...) CODE: if (items > 1) { THIS->extra_perimeters = (double)SvUV(ST(1)); } RETVAL = THIS->extra_perimeters; OUTPUT: RETVAL Polygons Surface::polygons() CODE: RETVAL.push_back(THIS->expolygon.contour); for (Polygons::iterator it = THIS->expolygon.holes.begin(); it != THIS->expolygon.holes.end(); ++it) { RETVAL.push_back((*it)); } OUTPUT: RETVAL Surfaces Surface::offset(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) const float delta double scale ClipperLib::JoinType joinType double miterLimit CODE: offset(*THIS, &RETVAL, delta, scale, joinType, miterLimit); OUTPUT: RETVAL %} }; %package{Slic3r::Surface}; %{ IV _constant() ALIAS: S_TYPE_TOP = stTop S_TYPE_BOTTOM = stBottom S_TYPE_BOTTOMBRIDGE = stBottomBridge S_TYPE_INTERNAL = stInternal S_TYPE_INTERNALSOLID = stInternalSolid S_TYPE_INTERNALBRIDGE = stInternalBridge S_TYPE_INTERNALVOID = stInternalVoid PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/SurfaceCollection.xsp000066400000000000000000000044451254023100400202640ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/SurfaceCollection.hpp" %} %name{Slic3r::Surface::Collection} class SurfaceCollection { %name{_new} SurfaceCollection(); ~SurfaceCollection(); void clear() %code{% THIS->surfaces.clear(); %}; void append(Surface* surface) %code{% THIS->surfaces.push_back(*surface); %}; int count() %code{% RETVAL = THIS->surfaces.size(); %}; void simplify(double tolerance); %{ SV* SurfaceCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->surfaces.size()-1); int i = 0; for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL SV* SurfaceCollection::filter_by_type(surface_type) SurfaceType surface_type; CODE: AV* av = newAV(); for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { if ((*it).surface_type == surface_type) av_push(av, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void SurfaceCollection::replace(index, surface) int index Surface* surface CODE: THIS->surfaces[index] = *surface; void SurfaceCollection::set_surface_type(index, surface_type) int index SurfaceType surface_type; CODE: THIS->surfaces[index].surface_type = surface_type; SV* SurfaceCollection::group() CODE: // perform grouping std::vector groups; THIS->group(&groups); // build return arrayref AV* av = newAV(); av_fill(av, groups.size()-1); size_t i = 0; for (std::vector::iterator it = groups.begin(); it != groups.end(); ++it) { AV* innerav = newAV(); av_fill(innerav, it->size()-1); size_t j = 0; for (SurfacesPtr::iterator it_s = it->begin(); it_s != it->end(); ++it_s) { av_store(innerav, j++, perl_to_SV_clone_ref(**it_s)); } av_store(av, i++, newRV_noinc((SV*)innerav)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; Slic3r-1.2.9/xs/xsp/TriangleMesh.xsp000066400000000000000000000151371254023100400172420ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/TriangleMesh.hpp" %} %name{Slic3r::TriangleMesh} class TriangleMesh { TriangleMesh(); ~TriangleMesh(); Clone clone() %code{% RETVAL = THIS; %}; void ReadSTLFile(char* input_file); void write_ascii(char* output_file); void write_binary(char* output_file); void ReadFromPerl(SV* vertices, SV* facets); void repair(); void WriteOBJFile(char* output_file); void scale(float factor); void scale_xyz(Pointf3* versor) %code{% THIS->scale(*versor); %}; void translate(float x, float y, float z); void rotate_x(float angle); void rotate_y(float angle); void rotate_z(float angle); void flip_x(); void flip_y(); void flip_z(); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split(); void merge(TriangleMesh* mesh) %code{% THIS->merge(*mesh); %}; ExPolygons horizontal_projection(); Clone convex_hull(); Clone bounding_box(); Clone center() %code{% RETVAL = THIS->bounding_box().center(); %}; int facets_count(); void reset_repair_stats(); %{ SV* TriangleMesh::stats() CODE: HV* hv = newHV(); (void)hv_stores( hv, "number_of_facets", newSViv(THIS->stl.stats.number_of_facets) ); (void)hv_stores( hv, "number_of_parts", newSViv(THIS->stl.stats.number_of_parts) ); (void)hv_stores( hv, "volume", newSVnv(THIS->stl.stats.volume) ); (void)hv_stores( hv, "degenerate_facets", newSViv(THIS->stl.stats.degenerate_facets) ); (void)hv_stores( hv, "edges_fixed", newSViv(THIS->stl.stats.edges_fixed) ); (void)hv_stores( hv, "facets_removed", newSViv(THIS->stl.stats.facets_removed) ); (void)hv_stores( hv, "facets_added", newSViv(THIS->stl.stats.facets_added) ); (void)hv_stores( hv, "facets_reversed", newSViv(THIS->stl.stats.facets_reversed) ); (void)hv_stores( hv, "backwards_edges", newSViv(THIS->stl.stats.backwards_edges) ); (void)hv_stores( hv, "normals_fixed", newSViv(THIS->stl.stats.normals_fixed) ); RETVAL = (SV*)newRV_noinc((SV*)hv); OUTPUT: RETVAL SV* TriangleMesh::vertices() CODE: if (!THIS->repaired) CONFESS("vertices() requires repair()"); if (THIS->stl.v_shared == NULL) stl_generate_shared_vertices(&(THIS->stl)); // vertices AV* vertices = newAV(); av_extend(vertices, THIS->stl.stats.shared_vertices); for (int i = 0; i < THIS->stl.stats.shared_vertices; i++) { AV* vertex = newAV(); av_store(vertices, i, newRV_noinc((SV*)vertex)); av_extend(vertex, 2); av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i].x)); av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i].y)); av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i].z)); } RETVAL = newRV_noinc((SV*)vertices); OUTPUT: RETVAL SV* TriangleMesh::facets() CODE: if (!THIS->repaired) CONFESS("facets() requires repair()"); if (THIS->stl.v_shared == NULL) stl_generate_shared_vertices(&(THIS->stl)); // facets AV* facets = newAV(); av_extend(facets, THIS->stl.stats.number_of_facets); for (int i = 0; i < THIS->stl.stats.number_of_facets; i++) { AV* facet = newAV(); av_store(facets, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); av_store(facet, 0, newSVnv(THIS->stl.v_indices[i].vertex[0])); av_store(facet, 1, newSVnv(THIS->stl.v_indices[i].vertex[1])); av_store(facet, 2, newSVnv(THIS->stl.v_indices[i].vertex[2])); } RETVAL = newRV_noinc((SV*)facets); OUTPUT: RETVAL SV* TriangleMesh::normals() CODE: if (!THIS->repaired) CONFESS("normals() requires repair()"); // normals AV* normals = newAV(); av_extend(normals, THIS->stl.stats.number_of_facets); for (int i = 0; i < THIS->stl.stats.number_of_facets; i++) { AV* facet = newAV(); av_store(normals, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal.x)); av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal.y)); av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal.z)); } RETVAL = newRV_noinc((SV*)normals); OUTPUT: RETVAL SV* TriangleMesh::size() CODE: AV* size = newAV(); av_extend(size, 2); av_store(size, 0, newSVnv(THIS->stl.stats.size.x)); av_store(size, 1, newSVnv(THIS->stl.stats.size.y)); av_store(size, 2, newSVnv(THIS->stl.stats.size.z)); RETVAL = newRV_noinc((SV*)size); OUTPUT: RETVAL SV* TriangleMesh::slice(z) std::vector z CODE: // convert doubles to floats std::vector z_f(z.begin(), z.end()); std::vector layers; TriangleMeshSlicer mslicer(THIS); mslicer.slice(z_f, &layers); AV* layers_av = newAV(); size_t len = layers.size(); if (len > 0) av_extend(layers_av, len-1); for (unsigned int i = 0; i < layers.size(); i++) { AV* expolygons_av = newAV(); len = layers[i].size(); if (len > 0) av_extend(expolygons_av, len-1); unsigned int j = 0; for (ExPolygons::iterator it = layers[i].begin(); it != layers[i].end(); ++it) { av_store(expolygons_av, j++, perl_to_SV_clone_ref(*it)); } av_store(layers_av, i, newRV_noinc((SV*)expolygons_av)); } RETVAL = (SV*)newRV_noinc((SV*)layers_av); OUTPUT: RETVAL void TriangleMesh::cut(z, upper, lower) float z; TriangleMesh* upper; TriangleMesh* lower; CODE: TriangleMeshSlicer mslicer(THIS); mslicer.cut(z, upper, lower); std::vector TriangleMesh::bb3() CODE: RETVAL.push_back(THIS->stl.stats.min.x); RETVAL.push_back(THIS->stl.stats.min.y); RETVAL.push_back(THIS->stl.stats.max.x); RETVAL.push_back(THIS->stl.stats.max.y); RETVAL.push_back(THIS->stl.stats.min.z); RETVAL.push_back(THIS->stl.stats.max.z); OUTPUT: RETVAL %} }; %package{Slic3r::TriangleMesh}; %{ PROTOTYPES: DISABLE std::string hello_world() CODE: RETVAL = "Hello world!"; OUTPUT: RETVAL %} Slic3r-1.2.9/xs/xsp/XS.xsp000066400000000000000000000002721254023100400152040ustar00rootroot00000000000000%module{Slic3r::XS}; %package{Slic3r::XS}; #include %{ %} %package{Slic3r}; %{ SV* VERSION() CODE: RETVAL = newSVpv(SLIC3R_VERSION, 0); OUTPUT: RETVAL %}Slic3r-1.2.9/xs/xsp/my.map000066400000000000000000000365301254023100400152500ustar00rootroot00000000000000coordf_t T_NV std::string T_STD_STRING t_config_option_key T_STD_STRING t_model_material_id T_STD_STRING std::vector T_STD_VECTOR_STD_STRING std::vector T_STD_VECTOR_INT std::vector T_STD_VECTOR_INT std::vector T_STD_VECTOR_INT std::vector T_STD_VECTOR_UINT std::vector T_STD_VECTOR_DOUBLE t_layer_height_ranges T_LAYER_HEIGHT_RANGES BoundingBox* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T BoundingBoxf* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T BoundingBoxf3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T DynamicPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintObjectConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintRegionConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T GCodeConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T FullPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T ZTable* O_OBJECT TriangleMesh* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Point* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Point3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Pointf* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Pointf3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Line* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Linef3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Polyline* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PolylineCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Polygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExPolygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExPolygonCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionEntityCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionPath* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Flow* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PrintState* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Surface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T SurfaceCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Extruder* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Model* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelMaterial* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelObject* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelVolume* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelInstance* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PrintRegion* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintObject* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Print* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T LayerRegion* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Layer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T SupportLayer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PlaceholderParser* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T GCodeWriter* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T GLVertexArray* O_OBJECT_SLIC3R Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV FlowRole T_UV PrintStep T_UV PrintObjectStep T_UV SurfaceType T_UV ClipperLib::JoinType T_UV ClipperLib::PolyFillType T_UV # we return these types whenever we want the items to be cloned Points T_ARRAYREF Pointfs T_ARRAYREF Lines T_ARRAYREF Polygons T_ARRAYREF Polylines T_ARRAYREF ExPolygons T_ARRAYREF ExtrusionPaths T_ARRAYREF Surfaces T_ARRAYREF # we return these types whenever we want the items to be returned # by reference and marked ::Ref because they're contained in another # Perl object Polygons* T_ARRAYREF_PTR ModelObjectPtrs* T_PTR_ARRAYREF_PTR ModelVolumePtrs* T_PTR_ARRAYREF_PTR ModelInstancePtrs* T_PTR_ARRAYREF_PTR PrintRegionPtrs* T_PTR_ARRAYREF_PTR PrintObjectPtrs* T_PTR_ARRAYREF_PTR LayerPtrs* T_PTR_ARRAYREF_PTR SupportLayerPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated # and not referenced by any Perl object TriangleMeshPtrs T_PTR_ARRAYREF INPUT T_STD_STRING { size_t len; const char * c = SvPV($arg, len); $var = std::string(c, len); } T_STD_VECTOR_STD_STRING if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int alen = av_len(av)+1; $var = std::vector(alen); STRLEN len; char* tmp; SV** elem; for (unsigned int i = 0; i < alen; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) { tmp = SvPV(*elem, len); ${var}[i] = std::string(tmp, len); } else ${var}[i] = std::string(\"\"); } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_STD_VECTOR_INT if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var = std::vector(len); SV** elem; for (unsigned int i = 0; i < len; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) ${var}[i] = SvIV(*elem); else ${var}[i] = 0; } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_STD_VECTOR_UINT if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var = std::vector(len); SV** elem; for (unsigned int i = 0; i < len; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) ${var}[i] = SvUV(*elem); else ${var}[i] = 0; } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_STD_VECTOR_DOUBLE if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var = std::vector(len); SV** elem; for (unsigned int i = 0; i < len; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) ${var}[i] = SvNV(*elem); else ${var}[i] = 0.; } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); O_OBJECT_SLIC3R if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) { if ( sv_isa($arg, perl_class_name($var) ) || sv_isa($arg, perl_class_name_ref($var) )) { $var = ($type)SvIV((SV*)SvRV( $arg )); } else { croak(\"$var is not of type %s (got %s)\", perl_class_name($var), HvNAME(SvSTASH(SvRV($arg)))); XSRETURN_UNDEF; } } else { warn( \"${Package}::$func_name() -- $var is not a blessed SV reference\" ); XSRETURN_UNDEF; } T_ARRAYREF if (SvROK($arg) && SvTYPE(SvRV($arg)) == SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var.resize(len); for (unsigned int i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); $var\[i].from_SV_check(*elem); } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_LAYER_HEIGHT_RANGES { if (!SvROK($arg) || SvTYPE(SvRV($arg)) != SVt_PVAV) { Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; t_layer_height_ranges tmp_ranges; for (unsigned int i = 0; i < len; i++) { SV* elem = *av_fetch(av, i, 0); if (!SvROK(elem) || SvTYPE(SvRV(elem)) != SVt_PVAV) { Perl_croak( aTHX_ \"%s: %s contains something that is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } AV* elemAV = (AV*)SvRV(elem); if (av_len(elemAV) + 1 != 3) { Perl_croak( aTHX_ \"%s: %s contains an array that isn't 3 elements long\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } coordf_t vals[3]; for (unsigned int j = 0; j < 3; ++j) { SV *elem_elem = *av_fetch(elemAV, j, 0); if (!looks_like_number(elem_elem)) { Perl_croak( aTHX_ \"%s: layer ranges and heights must be numbers\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}); } vals[j] = SvNV(elem_elem); } tmp_ranges[t_layer_height_range(vals[0], vals[1])] = vals[2]; } $var = tmp_ranges; } OUTPUT T_STD_STRING $arg = newSVpvn_utf8( $var.c_str(), $var.length(), true ); T_STD_VECTOR_STD_STRING AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { const std::string& str = ${var}[i]; STRLEN len = str.length(); av_store(av, i, newSVpvn_utf8(str.c_str(), len, true)); } T_STD_VECTOR_INT AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { av_store(av, i, newSViv(${var}[i])); } T_STD_VECTOR_UINT AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { av_store(av, i, newSVuv(${var}[i])); } T_STD_VECTOR_DOUBLE AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { av_store(av, i, newSVnv(${var}[i])); } # return object from pointer O_OBJECT_SLIC3R if ($var == NULL) XSRETURN_UNDEF; sv_setref_pv( $arg, perl_class_name($var), (void*)$var ); # return value handled by template class O_OBJECT_SLIC3R_T if ($var == NULL) XSRETURN_UNDEF; sv_setref_pv( $arg, $type\::CLASS(), (void*)$var ); T_ARRAYREF AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${type}::const_iterator it = $var.begin(); it != $var.end(); ++it) { av_store(av, i++, perl_to_SV_clone_ref(*it)); } T_ARRAYREF_PTR AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var->size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${ my $t = $type; $t =~ s/\*$//; \$t }::iterator it = $var->begin(); it != $var->end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } T_PTR_ARRAYREF_PTR AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var->size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${ my $t = $type; $t =~ s/\*$//; \$t }::iterator it = $var->begin(); it != $var->end(); ++it) { av_store(av, i++, perl_to_SV_ref(**it)); } T_PTR_ARRAYREF AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) { av_store(av, i++, (*it)->to_SV()); } T_LAYER_HEIGHT_RANGES AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len > 0) av_extend(av, len-1); // map is sorted, so we can just copy it in order int i = 0; for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) { const coordf_t range_values[] = { it->first.first, // key's first = minz it->first.second, // key's second = maxz it->second, // value = height }; AV *rangeAV = newAV(); av_extend(rangeAV, 2); for (int j = 0; j < 3; ++j) { av_store(rangeAV, j, newSVnv(range_values[j])); } av_store(av, i++, (SV*)newRV_noinc((SV*)rangeAV)); } Slic3r-1.2.9/xs/xsp/mytype.map000066400000000000000000000000001254023100400161310ustar00rootroot00000000000000Slic3r-1.2.9/xs/xsp/typemap.xspt000066400000000000000000000134121254023100400165150ustar00rootroot00000000000000%typemap{bool}{simple}; %typemap{size_t}{simple}; %typemap{coordf_t}{simple}; %typemap{std::string}; %typemap{t_config_option_key}; %typemap{t_model_material_id}; %typemap{std::vector}; %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; %typemap{t_layer_height_ranges}; %typemap{void*}; %typemap{SV*}; %typemap{AV*}; %typemap{Point*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Point3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Pointf*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Pointf3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BoundingBox*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BoundingBoxf*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BoundingBoxf3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{DynamicPrintConfig*}; %typemap{Ref}{simple}; %typemap{PrintObjectConfig*}; %typemap{Ref}{simple}; %typemap{PrintRegionConfig*}; %typemap{Ref}{simple}; %typemap{GCodeConfig*}; %typemap{Ref}{simple}; %typemap{PrintConfig*}; %typemap{Ref}{simple}; %typemap{FullPrintConfig*}; %typemap{Ref}{simple}; %typemap{ExPolygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExPolygonCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Flow*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Line*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Linef3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Polyline*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Polygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionEntityCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionPath*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionLoop*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{PolylineCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{MotionPlanner*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{GCodeWriter*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Surface*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{PrintState*}; %typemap{Ref}{simple}; %typemap{PrintRegion*}; %typemap{Ref}{simple}; %typemap{PrintObject*}; %typemap{Ref}{simple}; %typemap{Print*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{LayerRegion*}; %typemap{Ref}{simple}; %typemap{Layer*}; %typemap{Ref}{simple}; %typemap{SupportLayer*}; %typemap{Ref}{simple}; %typemap{PlaceholderParser*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; %typemap{Polygons}; %typemap{Polylines}; %typemap{ExPolygons}; %typemap{ExtrusionPaths}; %typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMesh*}; %typemap{TriangleMeshPtrs}; %typemap{Ref}{simple}; %typemap{Extruder*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Model*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelMaterial*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelObject*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelObjectPtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelVolume*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelVolumePtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelInstance*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelInstancePtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{GLVertexArray*}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; %typemap{LayerPtrs*}; %typemap{SupportLayerPtrs*}; %typemap{Axis}{parsed}{ %cpp_type{Axis}; %precall_code{% $CVar = (Axis)SvUV($PerlVar); %}; }; %typemap{SurfaceType}{parsed}{ %cpp_type{SurfaceType}; %precall_code{% $CVar = (SurfaceType)SvUV($PerlVar); %}; }; %typemap{ExtrusionLoopRole}{parsed}{ %cpp_type{ExtrusionLoopRole}; %precall_code{% $CVar = (ExtrusionLoopRole)SvUV($PerlVar); %}; }; %typemap{ExtrusionRole}{parsed}{ %cpp_type{ExtrusionRole}; %precall_code{% $CVar = (ExtrusionRole)SvUV($PerlVar); %}; }; %typemap{FlowRole}{parsed}{ %cpp_type{FlowRole}; %precall_code{% $CVar = (FlowRole)SvUV($PerlVar); %}; }; %typemap{PrintStep}{parsed}{ %cpp_type{PrintStep}; %precall_code{% $CVar = (PrintStep)SvUV($PerlVar); %}; }; %typemap{PrintObjectStep}{parsed}{ %cpp_type{PrintObjectStep}; %precall_code{% $CVar = (PrintObjectStep)SvUV($PerlVar); %}; };