I don't have the metal working tools to replicate that (or a furnace or blocks of aluminium, for that matter). But i do have a couple of 3D printers and i know Perl. That's pretty much the same thing, isn't it?
Looking at the requirements of how a 3D printed crushable PLA structure could work, it was pretty clear from the start that using a modeling program like OpenSCAD and a slicer software wouldn't work. My plan is to print a hard structure filled with lots of very thin PLA strands that would break under load, therefore absorbing energy. The slicer would either print those strand too thick or would remove them entirely.
The only option i could see that would generate the results i want was to generate the printer commands myself. After a lot of on-and-off tinkering, i still don't have a working crushable structure, but i decided to post my printer test code now, so you all have a chance to play around with it if you want.
This code will generate my default "test object". Let's dive into it.
#!/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 bom
+b 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.gcod
+e');
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 f
+irst layer
$gcode->linewidth($thinline);
$gcode->printspeed(800);
$gcode->movespeed(2400);
$gcode->rawcommand('M106 S255 ; Parts cooling fan full spe
+ed');
}
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($cente
+rx, $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); # Au
+to-swap direction if that makes it faster
}
}
# Second triangles
for(my $angle = 0; $angle < 360; $angle += 20) {
{
my ($startx, $starty) = $gcode->calcCirclePoint($cente
+rx, $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); # Au
+to-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, $cen
+tery, 60, $angle);
$gcode->printline($startx, $starty, $endx, $endy, 1); # Au
+to-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($cente
+rx, $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, $circlesta
+rtoffs);
}
}
}
print "Object done!\n";
return;
}
The above code plots the object layer by layer. It's a sort of stable outer structure, with a solid center thats suspended by thin strands of PLA filament.
The actual work happens in Arcturus.pm:
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 retract
+ing 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 stu
+ff
# 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 longe
+r moves
sub moveto {
my ($self, $pointx, $pointy) = @_;
if($pointx == $self->{x} && $pointy == $self->{y}) {
print {$self->{ofh}} "; Ignoring moveto command because distan
+ce is zero\n";
}
my $distance = $self->calculateDistance($self->{x}, $self->{y}, $p
+ointx, $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->{movespe
+ed}, "; Lift printhead for move and retract filament\n";
print {$self->{ofh}} "G0 X", $pointx, " Y", $pointy, " F", $se
+lf->{movespeed}, " ; Move to position\n";
print {$self->{ofh}} "G0 Z", $self->{z}, " E+1 F", $self->{mov
+espeed}, "; drop printhead after move and push filament to original p
+osition\n";
} else {
print {$self->{ofh}} "G0 X", $pointx, " Y", $pointy, " F", $se
+lf->{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->{pri
+ntspeed}, " 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 s
+egment 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, $cent
+ery, $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 tha
+t 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 circ
+le given the radius
# and the prefered segment length
#
# There is probably some math formula to do this better, but for now t
+his 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 hei
+ght\n";
print {$self->{ofh}} "\n";
return $self->{layer};
}
# Default templates (preamble, postamble) for my modified Creality End
+er 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 20m
+m 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 duri
+ng 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;
It's all a bit of a mess at the moment. That's why i haven't released it on CPAN yet. But maybe some of you get inspired by the possibilities of writing your own GCODE generator.