From ae54e4ddcb16f0c63c03c6ae6aa8d1d02356e0c5 Mon Sep 17 00:00:00 2001 From: Thomas Kremer <-> Date: Sun, 27 Sep 2020 00:34:52 +0200 Subject: dxf2camm.pl: added options --bbox, --align_knife and --offsetless_start, revamped CAMM::from_polylines --- CAMM.pm | 89 ++++++++++++++++++++++++++++++++++++++----------------------- dxf2camm.pl | 79 ++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 124 insertions(+), 44 deletions(-) diff --git a/CAMM.pm b/CAMM.pm index 1c5f857..b41472f 100644 --- a/CAMM.pm +++ b/CAMM.pm @@ -329,56 +329,79 @@ sub from_polylines { @options{qw(header footer)} = (1,1) if $options{headerfooter}; $self->header() if $options{header}; my $eps = $options{epsilon}//0.00001; + # since the knife follows the machine's current (pen) position by an offset, + # we need to keep track of the knife's position as well as the pen position. + my $knife = [0,0]; # current position of knife + my $pen = [0,0]; # current position of pen/knife-holder + my $last_dp = undef; # last point[i]-point[i-1] (=something*(pen-knife)) + # Note, that we cannot know the starting direction of the knife. + # DONE: make a "calibration" line at the start to determine the knife direction. for (@$paths) { my $points = $$_[1]; - $self->moveto(@{$$points[0]}); if ($options{offset}) { # if offset = 0, use the other code as well. my $offs = $options{offset}; my $short_line = $options{shortline}//80; # 1.5mm is small. my $small_angle = $options{smallangle}//10; # 10° is small. - my @p = @{$$points[0]}; - my $first = 1; - for my $i (1..$#$points) { - my $pt = $$points[$i]; - my @q = ($$pt[0]-$p[0],$$pt[1]-$p[1]); - my $l = sqrt($q[0]**2+$q[1]**2); - next unless $l > $eps; + # DONE: maybe add ($pen-$knife) here + # TODO: does the knife keep its direction during movetos? + $last_dp = undef if $options{offsetless_start}; + $knife = $$points[0]; + if (defined $last_dp) { + my $l = sqrt($$last_dp[0]**2+$$last_dp[1]**2); + $pen = [map $$knife[$_]+$$last_dp[$_]*($offs/$l), 0,1]; + } else { + $pen = $knife; + } + $self->moveto(@$pen); + for (my $i = 1; $i < @$points; $i++) { + my ($pt,@q,$l); + for (;$i < @$points;$i++) { + #for my $j ($i+1 .. $#$points) { # implicit $i < $#$points + $pt = $$points[$i]; + @q = ($$pt[0]-$$knife[0],$$pt[1]-$$knife[1]); + $l = sqrt($q[0]**2+$q[1]**2); + last if $l > $eps; + # $i++, next unless $l > $eps; + } + last if $i >= @$points; + # TODO: since arcs are rather slow, we might want to avoid real + # arcs here and use a polyline approximation instead. + # arg(q2/q1) = arg(q2*conj(q1)) + if (defined $last_dp) { + my $angle = 180/pi* + atan2($q[1]*$$last_dp[0]-$q[0]*$$last_dp[1], $q[0]*$$last_dp[0]+$q[1]*$$last_dp[1]); + # if the angle is small and the next line is short, we assume an + # interpolated curved line. No need to emphasize the corners. + if (abs($angle) > $small_angle || $l > $short_line) { + $self->arc(@$knife,$angle); + } + $pen = [map $$knife[$_]+$q[$_]*($offs/$l), 0,1]; + } + # now: + # knife is at $knife = $points[k] for some k < i + # pen is at $knife+$offs*(points[i]-$knife)° + my @r = @q; - if ($first) { + if (!defined $last_dp) { $_ *= 1+$offs/$l for @q; } - $_ *= -$offs/$l for @r; - @p = @$pt; - $first = 0; + $_ *= $offs/$l for @r; + $knife = $pt; + $last_dp = \@q; # sadly, we can't use relative coordinates here, because we don't # know how arc end coordinates are rounded by the device. #$res .= CAMM::lineto_relative(@q); #$res .= CAMM::lineto($$pt[0],$$pt[1]); - #my @q_abs = map lround($$pt[$_]-$r[$_]), 0,1; - my @q_abs = map $$pt[$_]-$r[$_], 0,1; - $self->lineto(@q_abs); - for my $j ($i+1 .. $#$points) { # implicit $i < $#$points - my $pt2 = $$points[$j]; - my @q2 = ($$pt2[0]-$$pt[0],$$pt2[1]-$$pt[1]); - my $l2 = sqrt($q2[0]**2+$q2[1]**2); - next unless $l2 > $eps; - # TODO: since arcs are rather slow, we might want to avoid real - # arcs here and use a polyline approximation instead. - # arg(q2/q1) = arg(q2*conj(q1)) - my $angle = 180/pi* - atan2($q2[1]*$q[0]-$q2[0]*$q[1], $q2[0]*$q[0]+$q2[1]*$q[1]); - #$res .= arc_relative(@r,$angle); - # if the angle is small and the next line is short, we assume an - # interpolated curved line. No need to emphasize the corners. - if (abs($angle) > $small_angle || $l2 > $short_line) { - $self->arc(@p,$angle); - } - last; - } + $pen = [map $$knife[$_]+$r[$_], 0,1]; + $self->lineto(@$pen); + # now: + # knife is at $knife = $pt = $points[i] + # pen is at $knife+$offs*($knife-points[k])° for last kmoveto(@{$$points[0]}); my @coords; if ($options{relative}) { my @p = @{$$points[0]}; diff --git a/dxf2camm.pl b/dxf2camm.pl index 9ef31c0..ed50e23 100755 --- a/dxf2camm.pl +++ b/dxf2camm.pl @@ -102,14 +102,14 @@ sub partial_sort { @$array = @res; } -sub sort_polylines { - my ($lines,$order) = @_; - check_polylines($lines,"sort"); +sub compute_bboxes { + my ($lines) = @_; + check_polylines($lines,"bboxes"); my @bboxes; for (@$lines) { - my $bbox = [$$_[1][0],$$_[1][0]]; + #my $bbox = [$$_[1][0],$$_[1][0]]; my @bbox = (undef)x4; for my $p (@{$$_[1]}) { for (0,1) { @@ -121,6 +121,41 @@ sub sort_polylines { } push @bboxes, \@bbox; } + return \@bboxes; +} + +sub bbox_union { + my ($bboxes) = @_; + my @bbox = (undef)x4; + for my $b (@$bboxes) { + for (0..3) { + my $sign = $_ <= 1 ? 1 : -1; + $bbox[$_] = $$b[$_] + if !defined $bbox[$_] || $bbox[$_]*$sign > $$b[$_]*$sign; + } + } + return \@bbox; +} + +sub sort_polylines { + my ($lines,$bboxes,$order) = @_; + check_polylines($lines,"sort"); + + # my @bboxes; + + # for (@$lines) { + # my $bbox = [$$_[1][0],$$_[1][0]]; + # my @bbox = (undef)x4; + # for my $p (@{$$_[1]}) { + # for (0,1) { + # $bbox[$_] = $$p[$_] + # if !defined $bbox[$_] || $bbox[$_] > $$p[$_]; + # $bbox[$_+2] = $$p[$_] + # if !defined $bbox[$_+2] || $bbox[$_+2] < $$p[$_]; + # } + # } + # push @bboxes, \@bbox; + # } # bboxes are calculated correctly: #@$lines = map ["closed",[[@$_[0,1]],[@$_[2,1]],[@$_[2,3]],[@$_[0,3]],[@$_[0,1]]]], @bboxes; @@ -160,13 +195,13 @@ sub sort_polylines { my @perm = 0..$#$lines; for my $crit (reverse @criteria) { if (ref $crit eq "CODE") { - partial_sort(sub {$crit->($bboxes[$a],$bboxes[$b])},\@perm); + partial_sort(sub {$crit->($$bboxes[$a],$$bboxes[$b])},\@perm); #@perm = sort {$crit->($bboxes[$a],$bboxes[$b])} @perm; } else { - @perm = sort {($bboxes[$a][$$crit[0]] <=> $bboxes[$b][$$crit[0]])*$$crit[1]} @perm; + @perm = sort {($$bboxes[$a][$$crit[0]] <=> $$bboxes[$b][$$crit[0]])*$$crit[1]} @perm; } } - return [@$lines[@perm]]; + return ([@$lines[@perm]],[@$bboxes[@perm]]); } sub add_overlap { @@ -469,6 +504,7 @@ sub usage { combine => 1, combine_cycles => 1, combine_reverse => 1, + align_knife => 1, scale => 1, help => sub { usage(0); }, ); @@ -476,6 +512,9 @@ sub usage { %opts_explained = ( output => "Write CAMM data to this file instead of stdout.", offset => "Set knive offset to this value (mm).", + offsetless_start => "Start each polyline without knife offset.", + bbox => "Add a bounding box with this much spacing.", + align_knife => "Begin with a small cut at [0,0]->[0,2] to align the knife.", overlap => "add this much (mm) of the start of a loop to its end to make it overlap.", raw => "Don't emit header/footer commands.", relative => "Use relative commands when possible (better compression).", @@ -492,7 +531,7 @@ sub usage { help => "Show this help screen.", ); -@opts = qw(output|o=s offset|off=f overlap=f raw! relative! epsilon=f shortline=f smallangle=f coarsify=f combine! combine_cycles|cycles! combine_reverse|reverse! translate=s scale=f sort=s help|h|?); +@opts = qw(output|o=s offset|off=f offsetless_start! bbox=f align_knife! overlap=f raw! relative! epsilon=f shortline=f smallangle=f coarsify=f combine! combine_cycles|cycles! combine_reverse|reverse! translate=s scale=f sort=s help|h|?); GetOptions(\%opts,@opts) or usage(2); @@ -503,6 +542,11 @@ $opts{offset} *= CAMM::units_per_mm if defined $opts{offset}; $opts{shortline} *= CAMM::units_per_mm if defined $opts{shortline}; $opts{translate} = [split /,/,$opts{translate}] if defined $opts{translate}; +# we don't want the bbox to cause negative coordinates. +if ($opts{bbox}) { + $opts{translate} //= [0,0]; + $opts{translate}[$_] += $opts{bbox} for 0,1; +} my $dxffile = shift; ## TODO: get paths from dxf in a good way. @@ -531,11 +575,24 @@ for (@$paths) { # a path $paths = coarsify_polylines($paths,$opts{coarsify}*CAMM::units_per_mm) if $opts{coarsify}; + +my $bboxes = compute_bboxes($paths); +my $bbox = bbox_union($bboxes); + +($paths,$bboxes) = sort_polylines($paths,$bboxes,$opts{sort}) + if defined $opts{sort}; + +unshift @$paths, ["open",[[0,0],[0,2*CAMM::units_per_mm]]] + if $opts{align_knife}; + +if (defined $opts{bbox}) { + my @box = @$bbox; + $box[$_] -= $opts{bbox}*CAMM::units_per_mm*($_ <= 1 ? 1 : -1) for 0..3; + push @$paths, ["closed",[map [@box[2*($_&1),($_&2)+1]], 0,1,3,2,0]]; +} + $paths = add_overlap($paths,$opts{overlap}*CAMM::units_per_mm) if $opts{overlap}; -#$CAMM::units_per_mm/4); -$paths = sort_polylines($paths,$opts{sort}) - if defined $opts{sort}; my $camm = CAMM->from_polylines($paths,%opts); #headerfooter=>1,offset=>10*$CAMM::units_per_mm); -- cgit v1.2.3