Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

3D printing with out slicers and safety nets

by cavac (Curate)
on Jun 09, 2021 at 16:21 UTC ( #11133692=CUFP: print w/replies, xml ) Need Help??

As some of you might know, i'm running a simulated space agency for fun and non-profit.

A couple of years ago i came across something called crushable aluminium honeycomb for things like one-time-use super lightweight shock absorbers. ESA also has some version of special aluminium foam for the same purpose. Those super rich engineering departments over at ESA and NASA really have all the coolest toys, though.

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.

A word of warning! The code is printer specific (i'm using a modified Creality Ender 5 Pro), so you will have to adapt at least some settings. Most 3D printers are also very dumb and will try to do whatever you tell them to do - most times "no matter what the consequences are". If you tell your printer "move the head 10 meters to the left and then set the extruder temperature to the surface temperature of the sun" it will happily crash the head and then try to burn your house down. I've named my codebase "Project Arcturus", if you are a Stargate Atlantis fan, you'll know why ;-)

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 50C ; 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 50C M104 S200 T0 ; Set Hotend 0 to 200C M109 S200 T0 ; Wait for Hotend to reach 200C 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.

perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: CUFP [id://11133692]
Approved by marto
Front-paged by Discipulus
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (7)
As of 2021-06-22 14:19 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    What does the "s" stand for in "perls"? (Whence perls)












    Results (105 votes). Check out past polls.

    Notices?