#!/usr/bin/env perl use strict; use warnings; use Carp; BEGIN { unshift @INC, '.'; }; use Arcturus; my $layer = 0; # I'm bad at naming things. Listening to the history of the atomic bomb while coding doesn't help my $fatline = 15/155; my $thinline = 4/155; my $gcode = Arcturus->new( linewidth => $fatline, printspeed => 300, movespeed => 600, ); $gcode->begin('arcturus_v3.gcode'); testobject($gcode); $gcode->finish; print "Filtering comments...\n"; $gcode->filtercomments('arcturus_v3.gcode', 'arcturus_v3_filtered.gcode'); print "Done.\n"; # This is a test object for developing crushable structures sub testobject { my $centerx = 110; my $centery = 110; my $angleoffs = 0; my $circlestartoffs = 0; while($gcode->getValue('z') < 20) { # make it 20mm in height $layer = $gcode->newlayer(); print "Generating layer $layer...\n"; if($layer == 2) { # Switch to faster, thinner print, turn on fan after the first layer $gcode->linewidth($thinline); $gcode->printspeed(800); $gcode->movespeed(2400); $gcode->rawcommand('M106 S255 ; Parts cooling fan full speed'); } my $roffs = 0; my $printradials = 1; my $printcircles = 1; if($layer > 2) { $roffs = int($layer / 2) % 8; $angleoffs = $layer % 8; if($layer % 2 == 0) { $printradials = 0; } else { $printcircles = 0; } } # Casing outer diameter $gcode->printcircle($centerx, $centery, 60); # First triangles for(my $angle = 0; $angle < 360; $angle += 20) { { my ($startx, $starty) = $gcode->calcCirclePoint($centerx, $centery, 60, $angle + 0); my ($midx, $midy) = $gcode->calcCirclePoint($centerx, $centery, 55, $angle + 10); my ($endx, $endy) = $gcode->calcCirclePoint($centerx, $centery, 60, $angle + 20); $gcode->printline($startx, $starty, $midx, $midy, 1); # Auto-swap direction if that makes it faster $gcode->printline($midx, $midy, $endx, $endy, 1); # Auto-swap direction if that makes it faster } } # Second triangles for(my $angle = 0; $angle < 360; $angle += 20) { { my ($startx, $starty) = $gcode->calcCirclePoint($centerx, $centery, 60, $angle + 10); my ($midx, $midy) = $gcode->calcCirclePoint($centerx, $centery, 55, $angle + 20); my ($endx, $endy) = $gcode->calcCirclePoint($centerx, $centery, 60, $angle + 30); $gcode->printline($startx, $starty, $midx, $midy, 1); # Auto-swap direction if that makes it faster $gcode->printline($midx, $midy, $endx, $endy, 1); # Auto-swap direction if that makes it faster } } # Radials for(my $angle = 0; $angle < 360; $angle += 10) { my ($startx, $starty) = $gcode->calcCirclePoint($centerx, $centery, 55, $angle); my ($endx, $endy) = $gcode->calcCirclePoint($centerx, $centery, 60, $angle); $gcode->printline($startx, $starty, $endx, $endy, 1); # Auto-swap direction if that makes it faster } # casing inner circle $gcode->printcircle($centerx, $centery, 55); # Spiderweb radials if($printradials) { $circlestartoffs = $angleoffs; for(my $angle = 0; $angle < 180; $angle += 10) { my ($startx, $starty) = $gcode->calcCirclePoint($centerx, $centery, 55, $angle + $angleoffs); my ($endx, $endy) = $gcode->calcCirclePoint($centerx, $centery, 55, 180 + $angle + $angleoffs); $gcode->printline($startx, $starty, $endx, $endy, 1); # Auto-swap direction if that makes it faster } } # spiderweb circles if($printcircles) { for(my $r = 10 + $roffs; $r < 55; $r += 10) { $gcode->printcircle($centerx, $centery, $r, $circlestartoffs); } } } print "Object done!\n"; return; } #### 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;