#!/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 local::lib "$FindBin::Bin/../local-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->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__
