File Coverage

blib/lib/CAD/Drawing/Manipulate.pm
Criterion Covered Total %
statement 24 166 14.4
branch 0 36 0.0
condition 0 3 0.0
subroutine 8 27 29.6
pod 19 19 100.0
total 51 251 20.3


line stmt bran cond sub pod time code
1             package CAD::Drawing::Manipulate;
2             our $VERSION = '0.12';
3              
4             # use CAD::Drawing;
5 3     3   16 use CAD::Drawing::Defined;
  3         5  
  3         523  
6 3     3   3132 use CAD::Drawing::Manipulate::Transform;
  3         9  
  3         195  
7              
8             our @ISA = qw(
9             CAD::Drawing::Manipulate::Transform
10             );
11              
12 3     3   4914 use Math::Geometry::Planar;
  3         145936  
  3         1564  
13 3     3   3905 use CAD::Calc qw(signdist);
  3         360653  
  3         31  
14              
15 3         255 use vars qw(
16             %movefunc
17             @mirrorfunc
18             @scalefunc
19             @rotatefunc
20 3     3   2133 );
  3         8  
21              
22 3     3   18 use warnings;
  3         6  
  3         106  
23 3     3   19 use strict;
  3         7  
  3         116  
24 3     3   17 use Carp;
  3         6  
  3         8571  
25             ########################################################################
26             =pod
27              
28             =head1 NAME
29              
30             CAD::Drawing::Manipulate - Manipulate CAD::Drawing objects
31              
32             =head1 Description
33              
34             Move, Copy, Scale, Mirror, and Rotate methods for single entities and
35             groups of entities.
36              
37             =head1 AUTHOR
38              
39             Eric L. Wilhelm
40              
41             http://scratchcomputing.com
42              
43             =head1 COPYRIGHT
44              
45             This module is copyright (C) 2004-2006 by Eric L. Wilhelm. Portions
46             copyright (C) 2003 by Eric L. Wilhelm and A. Zahner Co.
47              
48             =head1 LICENSE
49              
50             This module is distributed under the same terms as Perl. See the Perl
51             source package for details.
52              
53             You may use this software under one of the following licenses:
54              
55             (1) GNU General Public License
56             (found at http://www.gnu.org/copyleft/gpl.html)
57             (2) Artistic License
58             (found at http://www.perl.com/pub/language/misc/Artistic.html)
59              
60             =head1 NO WARRANTY
61              
62             This software is distributed with ABSOLUTELY NO WARRANTY. The author,
63             his former employer, and any other contributors will in no way be held
64             liable for any loss or damages resulting from its use.
65              
66             =head1 Modifications
67              
68             The source code of this module is made freely available and
69             distributable under the GPL or Artistic License. Modifications to and
70             use of this software must adhere to one of these licenses. Changes to
71             the code should be noted as such and this notification (as well as the
72             above copyright information) must remain intact on all copies of the
73             code.
74              
75             Additionally, while the author is actively developing this code,
76             notification of any intended changes or extensions would be most helpful
77             in avoiding repeated work for all parties involved. Please contact the
78             author with any such development plans.
79              
80             =cut
81             ########################################################################
82              
83             =head1 Group Methods
84              
85             These methods are called with required values, followed by a hash
86             reference of option values. Note the difference between this and the
87             individual entity manipulation syntax shown below. The absence of an
88             \%options hash reference implies everything in the drawing.
89              
90             For details about each of the group manipulation methods, see the
91             corresponding individual entity manipulation method.
92              
93             =head2 Options
94              
95             The $opts value shown for each of the group manipulation methods is fed
96             directly to CAD::Drawing::select_addr(). See the documentation for
97             this function for additional details.
98              
99             One of the most common methods of selection (after the implicit all)
100             may be the explicit list of addresses. This is done by simply passing
101             an array reference rather than a hash reference.
102              
103             =cut
104             ########################################################################
105              
106             =head2 GroupMove
107              
108             Move selected entities by @dist.
109              
110             $drw->GroupMove(\@dist, $opts);
111              
112             =cut
113             sub GroupMove {
114 0     0 1   my $self = shift;
115 0           my ($dist, $opts) = @_;
116 0           my $retref = $self->select_addr($opts);
117 0           foreach my $addr (@$retref) {
118 0           $self->Move($addr, $dist);
119             }
120             } # end subroutine GroupMove definition
121             ########################################################################
122              
123             =head2 GroupCopy
124              
125             Returns a list of addresses for newly created entities.
126              
127             @new = $drw->GroupCopy(\@dist, $opts);
128              
129             =cut
130             sub GroupCopy {
131 0     0 1   my $self = shift;
132 0           my ($dist, $opts) = @_;
133 0           my $retref = $self->select_addr($opts);
134 0           my @outlist;
135 0           foreach my $addr (@$retref) {
136 0           push(@outlist, $self->Copy($addr, $dist));
137             }
138 0           return(@outlist);
139             } # end subroutine GroupCopy definition
140             ########################################################################
141              
142             =head2 GroupClone
143              
144             Returns a list of addresses for newly created entities.
145              
146             @new = $drw->GroupClone($dest, $opts);
147              
148             =cut
149             sub GroupClone {
150 0     0 1   my $self = shift;
151 0           my ($dest, $opts) = @_;
152 0           my $retref = $self->select_addr($opts);
153 0           my @outlist;
154 0           foreach my $addr (@$retref) {
155 0           push(@outlist, $self->Clone($addr, $dest, $opts));
156             }
157 0           return(@outlist);
158             } # end subroutine GroupClone definition
159             ########################################################################
160              
161             =head2 place
162              
163             Clones items from $source into $drw and moves them to @pt. Selects items according to %opts and optionally rotates them by $opts{ang} (given in radians.)
164              
165             $drw->place($source, \@pt, \%opts);
166              
167             =cut
168             sub place {
169 0     0 1   my $self = shift;
170 0           my ($source, $pt, $opts) = @_;
171 0           my %options;
172 0 0         (ref($opts) eq "HASH") && (%options = %$opts);
173 0           my @newlist = $source->GroupClone($self, $opts);
174 0 0         if($options{ang}) {
175 0           $self->GroupRotate($options{ang}, \@newlist);
176             }
177 0           $self->GroupMove($pt, \@newlist);
178             # print "worked\n";
179 0           return(@newlist);
180             } # end subroutine place definition
181             ########################################################################
182              
183             =head2 GroupMirror
184              
185             Mirrors the entities specified by %options (see select_addr()) across
186             @axis.
187              
188             @new = $drw->GroupMirror(\@axis, \%options);
189              
190             =cut
191             sub GroupMirror {
192 0     0 1   my $self = shift;
193 0           my ($axis, $opts) = @_;
194 0           my $retref = $self->select_addr($opts);
195 0           my @outlist;
196 0           foreach my $addr (@$retref) {
197 0           push(@outlist, $self->Mirror($addr, $axis, $opts));
198             }
199 0           return(@outlist);
200             } # end subroutine GroupMirror definition
201             ########################################################################
202              
203             =head2 GroupScale
204              
205             Sorry, \@pt is required here.
206              
207             $drw->GroupScale($factor, \@pt, \%opts);
208              
209             =cut
210             sub GroupScale {
211 0     0 1   my $self = shift;
212 0           my ($factor, $pt, $opts) = @_;
213 0           my $retref = $self->select_addr($opts);
214 0           foreach my $addr (@$retref) {
215 0           $self->Scale($addr, $factor, $pt);
216             }
217             } # end subroutine GroupScale definition
218             ########################################################################
219              
220             =head2 GroupRotate
221              
222             Rotates specified entities by $angle. A center point may be specified
223             via $opts{pt} = \@pt.
224              
225             $drw->GroupRotate($angle, \%opts);
226              
227             =cut
228             sub GroupRotate {
229 0     0 1   my $self = shift;
230 0           my ($angle, $opts) = @_;
231 0           my %opt;
232 0 0         (ref($opts) eq "HASH") && (%opt = %$opts);
233 0           my @pt = (0,0);
234 0 0         $opt{pt} && (@pt = @{$opt{pt}});
  0            
235 0           my $retref = $self->select_addr($opts);
236 0           foreach my $addr (@$retref) {
237 0           $self->Rotate($addr, $angle, \@pt);
238             }
239             } # end subroutine GroupRotate definition
240             ########################################################################
241              
242             =head1 Individual Methods
243              
244             =cut
245             ########################################################################
246              
247             =head2 Move
248              
249             Moves entity at $addr by @dist (@dist may be three-dimensional.)
250              
251             $drw->Move($addr, \@dist);
252              
253             =cut
254             sub Move {
255 0     0 1   my $self = shift;
256 0           my ($addr, $dist) = @_;
257 0           my $obj = $self->getobj($addr);
258 0           my $mv_this = $call_syntax{$addr->{type}}[1];
259 0           $movefunc{$mv_this}->($obj->{$mv_this}, $dist);
260             } # end subroutine Move definition
261             ########################################################################
262             %movefunc = (
263             "pt" => sub {
264             my($pt, $dist) = @_;
265             foreach my $c (0..2) {
266             $pt->[$c] += $dist->[$c];
267             }
268             }, # end subroutine $movefunc{pt} definition
269             "pts" => sub {
270             my($pts, $dist) = @_;
271             for(my $i = 0; $i < @$pts; $i++) {
272             foreach my $c (0..2) {
273             $pts->[$i][$c] += $dist->[$c];
274             }
275             }
276             }, # end subroutine $movefunc{pts} definition
277             ); # end %movefunc function hash definition
278             ########################################################################
279              
280             =head2 Copy
281              
282             $drw->Copy($addr, \@dist);
283              
284             =cut
285             sub Copy {
286 0     0 1   my $self = shift;
287 0           my ($addr, $dist) = @_;
288 0           $addr = $self->Clone($addr);
289 0           $self->Move($addr, $dist);
290 0           return($addr);
291             } # end subroutine Copy definition
292             ########################################################################
293              
294             =head2 Clone
295              
296             Clones the entity at $addr into drawing $dest.
297              
298             $drw->Clone($addr, $dest, \%opts);
299              
300             %opts may contain:
301              
302             to_layer => $layer_name, # layer to clone into
303              
304             =cut
305             sub Clone {
306 0     0 1   my $self = shift;
307 0           my ($addr, $dest, $opts) = @_;
308 0           my %opts;
309 0 0         (defined($dest)) || ($dest = $self);
310 0 0         if(ref($opts) eq "HASH") {
311 0           %opts = %$opts;
312             }
313 0           my $type = $addr->{type};
314 0           my $obj = $self->getobj($addr);
315 0 0         $obj or croak("no object for $addr->{layer} $addr->{type} $addr->{id}");
316             # first gather the required arguments
317 0           my @args;
318 0           my @argstrings = (@{$call_syntax{$type}});
  0            
319 0           my $function = shift(@argstrings);
320             # uses the object's current contents as the options hash
321 0           my %optarg = %{$obj};
  0            
322 0           foreach my $argstring ( @argstrings) {
323 0           push(@args, $obj->{$argstring});
324 0           delete($optarg{$argstring});
325             }
326             # now build the rest of the options hash
327 0           $optarg{layer} = $addr->{layer};
328 0 0         defined($opts{"to layer"}) &&
329             ($optarg{"layer"} = $opts{"to layer"}); # DEPRECATED!
330 0 0         defined($opts{"to_layer"}) &&
331             ($optarg{"layer"} = $opts{"to_layer"});
332 0           delete($optarg{addr});
333             # print "layer cloned: $obj->{layer}\n";
334 0           $addr = $dest->$function(@args, \%optarg);
335             # print "landed on $addr->{layer}\n";
336 0           return($addr);
337             } # end subroutine Clone definition
338             ########################################################################
339              
340             =head2 Mirror
341              
342             Mirrors entity specified by $addr across @axis.
343              
344             Returns the address of the manipulated entity. If $opts{copy} is true,
345             will clone the entity, otherwise modify in-place.
346              
347             $drw->Mirror($addr, \@axis, \%opts);
348              
349             =cut
350             sub Mirror {
351 0     0 1   my $self = shift;
352 0           my ($addr, $axis, $opts) = @_;
353 0           my %opts;
354 0 0         (ref($opts) eq "HASH") && (%opts = %$opts);
355 0 0         $opts{copy} && ($addr = $self->Clone($addr));
356 0           my $type = $addr->{type};
357 0           my $obj = $self->getobj($addr);
358 0           my $stg = $call_syntax{$type}[1];
359 0           $mirrorfunc[0]{$stg}->($obj->{$stg}, $axis);
360 0           my $syn_len = scalar(@{$call_syntax{$type}});
  0            
361 0           for(my $i = 2; $i < $syn_len; $i++) {
362 0           $stg = $call_syntax{$type}[$i];
363 0 0         $mirrorfunc[1]{$stg} && $mirrorfunc[1]{$stg}->($obj, $axis);
364             }
365 0           return($addr);
366             } # end subroutine Mirror definition
367             ########################################################################
368             @mirrorfunc = (
369             { # First hash for stage-1 operations
370             "pt" => sub {
371             my($pt, $axis) = @_;
372             @{$pt} = pointmirror($axis, $pt);
373             }, # end subroutine $mirror[0]{pt} definition
374             "pts" => sub {
375             my($pts, $axis) = @_;
376             for(my $i = 0; $i < @$pts; $i++) {
377             @{$pts->[$i]} = pointmirror($axis, $pts->[$i]);
378             }
379             }, # end subroutine $mirror[0]{pts} definition
380             }, # end %{$mirrorfunc[0]} hash definition
381             { # Second hash for stage-2 operations
382             "angs" => sub {
383             my($obj, $axis) = @_;
384             my $a_ang = angle_of($axis);
385             # printf("angle: %0.4f\n", $a_ang * 180 / $pi);
386             # printf("s: %0.4f\n", $obj->{angs}[0] * 180 / $pi);
387             # printf("e: %0.4f\n", $obj->{angs}[1] * 180 / $pi);
388             $obj->{angs}[0] = $a_ang + ($a_ang - $obj->{angs}[0]);
389             $obj->{angs}[1] = $a_ang + ($a_ang - $obj->{angs}[1]);
390             @{$obj->{angs}} = reverse(@{$obj->{angs}});
391             checkarcangs($obj->{angs});
392             # printf("now s: %0.4f\n", $obj->{angs}[0] * 180 / $pi);
393             # printf("now e: %0.4f\n", $obj->{angs}[1] * 180 / $pi);
394             }, # end subroutine $mirrorfunc[1]{rad} definition
395             }, # end %{$mirrorfunc[1]} hash definition
396             ); # end @mirrorfunc array definition
397             ########################################################################
398              
399             =head2 Scale
400              
401             $drw->Scale($addr, $factor, \@pt);
402              
403             =cut
404             sub Scale {
405 0     0 1   my $self = shift;
406 0           my ($addr, $factor, $pt) = @_;
407 0           my $obj = $self->getobj($addr);
408 0   0       my $domove = (defined($pt->[0]) or defined($pt->[1]));
409 0 0         $domove && ($self->Move($addr, [map({-$_} @$pt)]));
  0            
410 0           my $stg = $call_syntax{$addr->{type}}[1];
411 0           $scalefunc[0]{$stg}->($obj->{$stg}, $factor);
412             # my $syn_len = scalar(@{$call_syntax{$addr->{type}}});
413             # for(my $i = 2; $i < $syn_len; $i++) {
414             # $stg = $call_syntax{$addr->{type}}[$i];
415             ## print "looking for $stg for $addr->{type}\n";
416             ## $scalefunc[1]{$stg} && print "ok, found it\n";
417             # $scalefunc[1]{$stg} && $scalefunc[1]{$stg}->($obj, $factor);
418             # }
419 0           foreach my $key ( keys(%{$scalefunc[1]})) {
  0            
420 0 0         defined($obj->{$key}) && $scalefunc[1]{$key}->($obj, $factor);
421             }
422 0 0         $domove && ($self->Move($addr, $pt));
423             } # end subroutine Scale definition
424             ########################################################################
425             @scalefunc = (
426             { # First hash for stage-1 operations
427             "pt" => sub {
428             my($pt, $factor) = @_;
429             foreach my $c (0..2) {
430             $pt->[$c] *= $factor;
431             }
432             }, # end subroutine $scalefunc[0]{pt} definition
433             "pts" => sub {
434             my($pts, $factor) = @_;
435             for(my $i = 0; $i < @$pts; $i++) {
436             foreach my $c (0..2) {
437             $pts->[$i][$c] *= $factor;
438             }
439             }
440             }, # end subroutine $scalefunc[0]{pts} definition
441             }, # end %{$scalefunc[0]} hash definition
442             { # Second hash for stage-2 operations
443             "rad" => sub {
444             my($hashref, $factor) = @_;
445             $hashref->{rad} *= $factor;
446             }, # end subroutine $scalefunc[1]{rad} definition
447             "height" => sub {
448             my($hashref, $factor) = @_;
449             $hashref->{height} *= $factor;
450             }, # end subroutine $scalefunc[1]{height} definition
451             }, # end %{$scalefunc[1]} hash definition
452             ); # end @scalefunc array definition
453             ########################################################################
454              
455             =head2 Rotate
456              
457             Rotates entity specified by $addr by $angle (+ccw radians) about @pt.
458             Angle may be in degrees if $angle =~ s/d$// returns a true value (but I
459             hope the "d" is the only thing on the end, because I'm not looking for
460             anything beyond that.) $angle = "45" . "d" will get converted, but
461             $angle = "45" . "bad" will be called 0. Remember, this is Perl:)
462              
463             $drw->Rotate($addr, $angle, \@pt);
464              
465             =cut
466             sub Rotate {
467 0     0 1   my $self = shift;
468 0           my ($addr, $angle, $pt) = @_;
469 0 0         (ref($pt) eq "ARRAY") || ($pt = [0,0]);
470 0           my $obj = $self->getobj($addr);
471 0           my $type = $addr->{type};
472 0 0         if($angle =~ s/d$//) {
473             # allow spec of angle in degrees with $angle . "d";
474 0           $angle *= $pi / 180;
475             }
476 0           my $stg = $call_syntax{$type}[1];
477 0           $rotatefunc[0]{$stg}->($obj->{$stg}, $angle, $pt);
478 0           my $syn_len = scalar(@{$call_syntax{$type}});
  0            
479 0           for(my $i = 2; $i < $syn_len; $i++) {
480 0           $stg = $call_syntax{$type}[$i];
481 0 0         $rotatefunc[1]{$stg} && $rotatefunc[1]{$stg}->($obj,$angle, $pt);
482             }
483             } # end subroutine Rotate definition
484             ########################################################################
485             @rotatefunc = (
486             { # First hash for stage-1 operations
487             "pt" => sub {
488             my($pt, $angle, $cpt) = @_;
489             @{$pt}[0,1] = pointrotate(@{$pt}[0,1], $angle, @{$cpt});
490             }, # end subroutine $rotatefunc[0]{pt} definition
491             "pts" => sub {
492             my($pts, $angle, $cpt) = @_;
493             for(my $i = 0; $i < @$pts; $i++) {
494             @{$pts->[$i]}[0,1] =
495             pointrotate(@{$pts->[$i]}[0,1],$angle, @{$cpt});
496             }
497             }, # end subroutine $rotatefunc[0]{pts} definition
498             }, # end %{$rotatefunc[0]} hash definition
499             { # Second hash for stage-2 operations
500             "angs" => sub {
501             my($hashref, $angle) = @_;
502             foreach my $ang (0, 1) {
503             $hashref->{angs}[$ang] += $angle;
504             }
505             checkarcangs($hashref->{angs});
506             }, # end subroutine $rotatefunc[1]{angs} definition
507             # NOTE: I'm ignoring the vector on images and rotation
508             # angle of text for now
509             }, # end %{$rotatefunc[1]} hash definition
510             ); # end @rotatefunc array definition
511             ########################################################################
512              
513             =head1 Internal Functions
514              
515             =cut
516             ########################################################################
517              
518             =head2 pointrotate
519              
520             Internal use only.
521              
522             ($x, $y) = pointrotate($x, $y, $ang, $xc, $yc);
523              
524             =cut
525             sub pointrotate {
526 0     0 1   my ($x, $y, $ang, $xc, $yc) = @_;
527 0           my $xn = $xc + cos($ang) * ($x - $xc) - sin($ang) * ($y - $yc);
528 0           my $yn = $yc + sin($ang) * ($x - $xc) + cos($ang) * ($y - $yc);
529 0           return($xn, $yn);
530             } # end subroutine pointrotate definition
531             ########################################################################
532              
533             =head2 pointmirror
534              
535             @point = pointmirror($axis, $pt);
536              
537             =cut
538             sub pointmirror {
539 0     0 1   my ($axis, $pt) = @_;
540             # print "axis: ", join(" ", map({join(",", @{$_})} @{$axis}[0,1])), "\n";
541             # print "point: ", join(",", @{$pt}), "\n";
542 0           my $foot = PerpendicularFoot([ @{$axis}[0,1], $pt ]);
  0            
543             # print "foot: @$foot\n";
544 0           my $x = $foot->[0] - ($pt->[0] - $foot->[0]);
545 0           my $y = $foot->[1] - ($pt->[1] - $foot->[1]);
546 0           return($x, $y);
547             } # end subroutine pointmirror definition
548             ########################################################################
549              
550             =head2 angle_of
551              
552             angle_of(\@segment);
553              
554             =cut
555             sub angle_of {
556 0     0 1   my ($axis) = @_;
557 0           my @delta = signdist(@{$axis});
  0            
558 0           return(atan2($delta[1], $delta[0]));
559             } # end subroutine angle_of definition
560             ########################################################################
561              
562             =head1 Polygon Methods
563              
564             These don't do anything yet and need to be moved to another module anyway.
565              
566             =cut
567             ########################################################################
568              
569             =head2 CutPline
570              
571             $drw->CutPline();
572              
573             =cut
574             sub CutPline {
575 0     0 1   my $self = shift;
576              
577             } # end subroutine CutPline definition
578             ########################################################################
579              
580             =head2 IntPline
581              
582             $drw->IntPline();
583              
584             =cut
585             sub IntPline {
586 0     0 1   my $self = shift;
587              
588             } # end subroutine IntPline definition
589             ########################################################################
590              
591             =head2 intersect_pgon
592              
593             intersect_pgon();
594              
595             =cut
596 0     0 1   sub intersect_pgon {
597              
598             } # end subroutine intersect_pgon definition
599             ########################################################################
600              
601             1;