package Arcturus; #---AUTOPRAGMASTART--- use 5.030; use strict; use warnings; use diagnostics; use mro 'c3'; use English; use Carp qw[carp croak confess cluck longmess shortmess]; our $VERSION = 1.0; use autodie qw( close ); use utf8; use Data::Dumper; #---AUTOPRAGMAEND--- # the famous new() method, available at any store near you sub new { my ($proto, %config) = @_; my $class = ref($proto) || $proto; my $self = bless \%config, $class; my ($defaultstart, $defaultend) = $self->gettemplates(); if(!defined($self->{startcode})) { $self->{startcode} = $defaultstart; } if(!defined($self->{endcode})) { $self->{endcode} = $defaultend; } # Initialize some default values. # These values must match settings in the startcode because a lot of math is based on it if(!defined($self->{startx})) { $self->{startx} = 180; } if(!defined($self->{starty})) { $self->{starty} = 10; } if(!defined($self->{startz})) { $self->{startz} = 0; } if(!defined($self->{circleresolution})) { $self->{circleresolution} = 2; # length of circle segments in mm } if(!defined($self->{noretractmove})) { $self->{noretractmove} = 2; # maximum movement without retracting filament (in mm) } if(!defined($self->{printspeed})) { $self->{printspeed} = 300; } if(!defined($self->{movespeed})) { $self->{movespeed} = 600; } if(!defined($self->{linewidth})) { $self->{linewidth} = 15/155; } return $self; } # Start a new print sub begin { my ($self, $fname) = @_; $self->{layer} = 0; $self->{x} = $self->{startx}; $self->{y} = $self->{starty}; $self->{z} = $self->{startz}; $self->{circlefn} = []; open(my $ofh, '>', $fname) or croak($ERRNO); $self->{ofh} = $ofh; print {$self->{ofh}} $self->{startcode}; return; } # Finish up a print sub finish { my ($self) = @_; print {$self->{ofh}} $self->{endcode}; close $self->{ofh}; delete $self->{ofh}; return; } # Set printspeed sub printspeed { my ($self, $printspeed) = @_; $self->{printspeed} = $printspeed; return; } # Set movespeed sub movespeed { my ($self, $movespeed) = @_; $self->{movespeed} = $movespeed; return; } # Set linewidth sub linewidth { my ($self, $linewidth) = @_; $self->{linewidth} = $linewidth; return; } # Execute a raw command sub rawcommand { my ($self, $command) = @_; print {$self->{ofh}} $command, "\n"; return; } # Return somew configuration value sub getValue { my ($self, $valname) = @_; if(defined($self->{$valname})) { return $self->{$valname}; } return; } # This generates a much more compact file by filtering out all the stuff # that printers don't use sub filtercomments { my ($self, $ifname, $ofname) = @_; open(my $ifh, '<', $ifname) or croak($!); open(my $ofh, '>', $ofname) or croak($!); while((my $line = <$ifh>)) { if($line =~ /^\;\ process/ || $line =~ /^\;\ layer/) { print $ofh $line; next; } chomp $line; $line =~ s/\;.*//g; if(!length($line)) { next; } print $ofh $line, "\n"; } close $ifh; close $ofh; return; } # Move printhead without printing. Retract head and filament for longer moves sub moveto { my ($self, $pointx, $pointy) = @_; if($pointx == $self->{x} && $pointy == $self->{y}) { print {$self->{ofh}} "; Ignoring moveto command because distance is zero\n"; } my $distance = $self->calculateDistance($self->{x}, $self->{y}, $pointx, $pointy); if($distance > $self->{noretractmove}) { # Only lift head and retract filament if move distance exceeds $self->{noretractmove} my $tempz = $self->{z} + 0.2; print {$self->{ofh}} "G0 Z", $tempz, " E-1 F", $self->{movespeed}, "; Lift printhead for move and retract filament\n"; print {$self->{ofh}} "G0 X", $pointx, " Y", $pointy, " F", $self->{movespeed}, " ; Move to position\n"; print {$self->{ofh}} "G0 Z", $self->{z}, " E+1 F", $self->{movespeed}, "; drop printhead after move and push filament to original position\n"; } else { print {$self->{ofh}} "G0 X", $pointx, " Y", $pointy, " F", $self->{movespeed}, " ; Move to position\n"; } $self->{x} = $pointx; $self->{y} = $pointy; return; } # Print a straight line sub printline { my ($self, $startx, $starty, $endx, $endy, $allowdirectionswitch) = @_; if(!defined($startx)) { $startx = $self->{x}; } if(!defined($starty)) { $starty = $self->{y}; } if($startx == $endx && $starty == $endy) { print {$self->{ofh}} "; Ignoring zero length line\n"; return; } if(defined($allowdirectionswitch) && $allowdirectionswitch) { my $startdist = $self->calculateDistance($self->{x}, $self->{y}, $startx, $starty); my $enddist = $self->calculateDistance($self->{x}, $self->{y}, $endx, $endy); if($enddist < $startdist) { # Swap endpoints ($startx, $starty, $endx, $endy) = ($endx, $endy, $startx, $starty); } } my $len = $self->calculateDistance($startx, $starty, $endx, $endy); if($len < 0.01) { print {$self->{ofh}} "; Line length $len too small, using 0.01 length as calculation basis\n"; $len = 0.01; } my $extrude = $len * $self->{linewidth}; print {$self->{ofh}} "; Line from ($startx / $starty) to ($endx / $endy) Length $len Filament $extrude\n"; if($startx != $self->{x} || $starty != $self->{y}) { $self->moveto($startx, $starty); } print {$self->{ofh}} "G1 X", $endx, " Y", $endy, " F", $self->{printspeed}, " E", $extrude, " ; Extrude line length $len\n"; print {$self->{ofh}} "\n"; $self->{x} = $endx; $self->{y} = $endy; return; } # Calculate the distance between two points sub calculateDistance { my ($self, $startx, $starty, $endx, $endy) = @_; my $distancex = abs($startx - $endx); my $distancey = abs($starty - $endy); my $distance = sqrt(($distancex ** 2) + ($distancey ** 2)); return $distance; } # Print a sort-of-circle in small segments sub printcircle { my ($self, $centerx, $centery, $radius, $startangle) = @_; # We need to guess an approriate step angle to match our desired segment length at the given circle radius. if(!defined($self->{circlefn}->[$radius])) { $self->calculateCircleFN($radius); } if(!defined($startangle)) { $startangle = 0; } my $stepangle = $self->{circlefn}->[$radius]; my $stepcount = 360 / $stepangle; print {$self->{ofh}} "; ####### START CIRCLE ($centerx / $centery) radius $radius in $stepcount steps (stepangle $stepangle)\n"; my ($startx, $starty) = $self->calcCirclePoint($centerx, $centery, $radius, 0 + $startangle); if($startx != $self->{x} || $starty != $self->{y}) { $self->moveto($startx, $starty); } for(my $deg = $stepangle; $deg < 360; $deg += $stepangle) { my ($pointx, $pointy) = $self->calcCirclePoint($centerx, $centery, $radius, $deg + $startangle); print {$self->{ofh}} "; Circle arc to angle $deg\n"; $self->printline(undef, undef, $pointx, $pointy); } my $closinglength = $self->calculateDistance($self->{x}, $self->{y}, $startx, $starty); if($closinglength > 0) { print {$self->{ofh}} "; Closing circle\n"; $self->printline(undef, undef, $startx, $starty); } print {$self->{ofh}} "; ################### END OF CIRCLE ################\n"; print {$self->{ofh}} "\n"; return; } # Calculated X/Y on a plane, given the center, radius and angle of that circle sub calcCirclePoint { my ($self, $centerx, $centery, $radius, $angle) = @_; my $radangle = $self->toRadians($angle); my $pointx = $centerx + ($radius * cos($radangle)); my $pointy = $centery + ($radius * sin($radangle)); return ($pointx, $pointy); } # This guesses a somewhat appropriate "step" angle for plotting a circle given the radius # and the prefered segment length # # There is probably some math formula to do this better, but for now this should be more or less OK. This # isn't rocket science, except when it is.... uh-uh, better FIXME soon sub calculateCircleFN { my ($self, $radius) = @_; my ($startx, $starty) = $self->calcCirclePoint(0, 0, $radius, 0); my $stepangle = 0.01; while($stepangle < 360) { my ($pointx, $pointy) = $self->calcCirclePoint(0, 0, $radius, $stepangle); if($self->calculateDistance($startx, $starty, $pointx, $pointy) > $self->{circleresolution}) { last; } $stepangle += 0.01; } #print "Stepangle for radius $radius is $stepangle\n"; $self->{circlefn}->[$radius] = $stepangle; return; } # More circle math stuff. sub toRadians { my ($self, $degrees) = @_; my $pi = 3.141592653589793; my $radians = $degrees * ($pi / 180); return $radians; } # Prepare to print the next layer sub newlayer { my ($self) = @_; $self->{layer}++; $self->{z} += 0.1; print {$self->{ofh}} "\n"; print {$self->{ofh}} "; --------------------------------------\n"; print {$self->{ofh}} "; process Process", $self->{layer}, "\n"; print {$self->{ofh}} "; layer ", $self->{layer}, ", Z = ", $self->{z}, "\n"; print {$self->{ofh}} "T0\n"; print {$self->{ofh}} "; Trigger layer change in Octolapse\n"; print {$self->{ofh}} "G4 P0\n"; print {$self->{ofh}} "G0 Z", $self->{z}, " ; Move to new layer height\n"; print {$self->{ofh}} "\n"; return $self->{layer}; } # Default templates (preamble, postamble) for my modified Creality Ender 5 Pro sub gettemplates { my ($self) = @_; my $startcode = <<'STARTCODE'; ; Initialization code G90 ; absolute positioning M82 ; extruder absolute positioning M106 S0 ; Fan off M140 S50 ; Bed temperature to 50°C ; Use bed the warmup time to home all axes and move build platform 20mm below print head G28 ; home all axes M420 S1 ; use bed leveling data G0 Z20; Move platform down a bit to keep nozzle from touching bed during lengthy warmup M190 S50 ; Wait for bed temperature to reach 50°C M104 S200 T0 ; Set Hotend 0 to 200°C M109 S200 T0 ; Wait for Hotend to reach 200°C G1 X5 Y10 F3000 ; get in position to prime G1 Z0.2 F3000 ; Raise platform into working position AFTER move G92 E0 ; reset extrusion distance G1 X160 E15 F600 ; prime nozzle G1 X180 F5000 ; quick wipe G92 E0 ; reset extrusion distance G90 ; Set axis to absolute positioning M83 ; Set extruder to RELATIVE positioning ; --------- Initialization done ------------------------ STARTCODE my $endcode = <<'ENDCODE'; ; ------------------------------ ; Finish up M106 S0 ; turn off cooling fan M104 S0 ; turn off extruder M140 S0 ; turn off bed G91 ; Set axis to relative mode G1 Z20; move build down 3 cm G28 X0 Y0 ; home X axis M84 ; disable motors ; printing done ENDCODE return ($startcode, $endcode); } 1;