#!/usr/bin/perl -w ###################################################################### # Read a (Cura) gcode file and output a very short summary like: # 2019-01-28? Mistral-E_v1.3_Leo_N.stl # 037-Mistral-E_v1.3.gcode (35m, 0.2mm, 10% gyroid, NO SUPPORT) ###################################################################### use strict; # ALWAYS ALWAYS ALWAYS use JSON::PP; my (%setting, %mesh, %type, $setting_3, $fn); while (<>) { unless ($fn && $fn eq $ARGV) { op(); $fn = $ARGV; %setting = %mesh = %type = (); $setting_3 = ''; } # Fast skip actual gcode (for now), look only at comments: next unless /^;/; # +++ Consider a more generic way to parse templates for {setting}s? if (/^;MESH:(.+)/) { $mesh{$1}++; } elsif (/^;TYPE:(.+)/) { $type{$1}++; } elsif (/^;LAYER_COUNT:(.*)/) { $setting{layers} ||= $1; } elsif (/^;LAYER:(.*)/) { $setting{layers} ||= 0; $setting{layers} = $1 if $1 > $setting{layers}; } elsif (/^;TIME:(\d+)/) { $setting{print_time} ||= $1; # already in seconds } elsif (/^\;Print time: (\d\d):(\d\d):(\d\d)/) { $setting{print_time} ||= 60*60*$1 + 60*$2 + $3; # convert to secs } elsif (/^;Sliced at: \w+ (\d+)-(\d+)-(\d+) \d+:\d+:\d+/) { $setting{date} ||= "$3-$2-$1"; # DD-MM-YYYY -> ISO } elsif (/^;Layer height: ([.0-9]+)/) { $setting{layer_height} ||= $1; } elsif (/^;Basic settings: Layer height: ([.0-9]+) Walls: ([.0-9]+)/) { $setting{layer_height} ||= $1; $setting{wall_thickness} ||= $2; } elsif (/^;Fill: ([.0-9]+)/) { $setting{infill_sparse_density} ||= $1; } elsif (/^;SETTING_3 (.+)/) { # are there other SETTING_\d+ ? $setting_3 .= $1; } elsif ( /^;$/ || /^;(TIME_ELAPSED|FLAVOR):/ || /^;(MIN|MAX)(X|Y|Z):/ || /^;note -- min layer time used/ ) { # ignore... FOR NOW? } elsif (/^(;.*)/) { print "; UNHANDLED >$1<\n" if $ENV{DEBUG}; } # +++++ Do something about unprintable M117 00:35:54 $[0.06885498580078125] ? } op(); sub parse_settings { my ($from, $setting) = @_; if (ref $setting eq 'ARRAY') { my $n = 0; for (@$setting) { parse_settings($from . '.' . $n++, $_); } return; } elsif (ref $setting eq 'HASH') { for (sort keys %$setting) { parse_settings("$from.$_", $setting->{$_}); } return; } elsif (ref $setting) { die "Not sure what to do with $from $setting"; } # got a string of settings to parse: my $vals = 0; for (split '\\\\n', $setting) { if ($_ eq '[values]') { $vals = 1; # start reading values! } elsif (/^\[/) { $vals = 0; } elsif ($vals && /^(\w+) = (.+)/) { $setting{$1} = $2; } elsif ($vals && $_) { print ";UNPARSED $from $_\n"; } } } sub set1 { # return a setting AND THEN DELETE IT (return it once); my $name = shift; return delete $setting{$name} || ''; } sub op { parse_settings(';SETTING_3', decode_json $setting_3 || '{}'); return unless %setting; print set1("date"), " $fn\n"; # https://www.thingiverse.com/thing:\n"; for (sort keys %mesh) { next if $_ eq "NONMESH"; # support structures, skirts, etc. print " $_\n"; } # (35m, 0.2mm, 10% gyroid, NO SUPPORT) my $time = time_secs( set1("print_time") ); print " ($time, ", set1("layer_height"), "mm, ", set1("infill_sparse_density"), "% ", set1("infill_pattern"), ")\n"; return op_debug() if $ENV{DEBUG}; } sub op_debug { print ";MESH", map(":$_($mesh{$_})", sort keys %mesh), "\n" if %mesh; for (sort keys %setting) { print ";$_ = $setting{$_}\n"; } print "#"x70, "\n"; } sub time_secs { my $sec = shift; if ($sec > 3600) { return int($sec/3600) . "h" . int(($sec % 3600) / 60) . "m"; } return int($sec/60) . "m" . ($sec % 60) . "s"; }