File Coverage

blib/lib/CAD/Drawing.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package CAD::Drawing;
2             our $VERSION = '0.26';
3              
4 3     3   28063 use warnings;
  3         7  
  3         109  
5 3     3   14 use strict;
  3         6  
  3         130  
6 3     3   18 use Carp;
  3         8  
  3         262  
7              
8 3     3   2250 use CAD::Drawing::Defined;
  3         17  
  3         708  
9 3     3   2092 use CAD::Drawing::Manipulate;
  3         9  
  3         157  
10 3     3   7340 use CAD::Drawing::Calculate;
  3         11  
  3         94  
11 3     3   2071 use CAD::Drawing::IO;
  0            
  0            
12             use CAD::Calc qw(line_vec unit_angle);
13             use Math::Vec qw(NewVec);
14              
15             our @ISA = qw(
16             CAD::Drawing::Manipulate
17             CAD::Drawing::Calculate
18             CAD::Drawing::IO
19             );
20              
21              
22             ########################################################################
23             =pod
24              
25             =head1 NAME
26              
27             CAD::Drawing - Methods to create, load, and save vector graphics
28              
29             =head1 SYNOPSIS
30              
31             The primary intention of this module is to provide high-level operations
32             for creating, loading, saving and manipulating vector graphics without
33             having to be overly concerned about smile floormats. As the code has
34             seen more use, it has also drifted into a general purpose geometry API.
35              
36             =over
37              
38             =item The syntax of this works something like the following:
39              
40             A simple example of a (slightly misbehaved) file converter:
41              
42             use CAD::Drawing;
43             $drw = CAD::Drawing->new;
44             $drw->load("file.dwg");
45             my %opts = (
46             layer => "smudge",
47             height => 5,
48             );
49             $drw->addtext([10, 2, 5], "Kilroy was here", \%opts);
50             $drw->save("file.ps");
51              
52             This is a very basic example, and will barely scratch the surface of
53             this module's capabilities. See the details for each function below and
54             in the documentation for the backend modules.
55              
56             =back
57              
58             =head1 AUTHOR
59              
60             Eric L. Wilhelm
61              
62             http://scratchcomputing.com
63              
64             =head1 COPYRIGHT
65              
66             This module is copyright (C) 2004-2006 by Eric L. Wilhelm. Portions
67             copyright (C) 2003 by Eric L. Wilhelm and A. Zahner Co.
68              
69             =head1 LICENSE
70              
71             This module is distributed under the same terms as Perl. See the Perl
72             source package for details.
73              
74             You may use this software under one of the following licenses:
75              
76             (1) GNU General Public License
77             (found at http://www.gnu.org/copyleft/gpl.html)
78             (2) Artistic License
79             (found at http://www.perl.com/pub/language/misc/Artistic.html)
80              
81             =head1 NO WARRANTY
82              
83             This software is distributed with ABSOLUTELY NO WARRANTY. The author,
84             his former employer, and any other contributors will in no way be held
85             liable for any loss or damages resulting from its use.
86              
87             =head1 Modifications
88              
89             The source code of this module is made freely available and
90             distributable under the GPL or Artistic License. Modifications to and
91             use of this software must adhere to one of these licenses. Changes to
92             the code should be noted as such and this notification (as well as the
93             above copyright information) must remain intact on all copies of the
94             code.
95              
96             Additionally, while the author is actively developing this code,
97             notification of any intended changes or extensions would be most helpful
98             in avoiding repeated work for all parties involved. Please contact the
99             author with any such development plans.
100              
101             =head1 SEE ALSO
102              
103             These modules are required by Drawing.pm and will be automatically
104             included by the single I Drawing; statement. No functions are
105             exported to the main program's namespace (unless you try to use
106             CAD::Drawing::Defined from your main code (don't do that.))
107              
108             =over
109              
110             =item L
111              
112             Generally useful constants and definitions used throughout the
113             CAD::Drawing toolkit.
114              
115             =item L
116              
117             Entity manipulation methods.
118              
119             =item L
120              
121             Matrix transform methods.
122              
123             =item L
124              
125             Deals with embedded image definitions.
126              
127             =item L
128              
129             Calculations and coordinate system transforms.
130              
131             =item L
132              
133             Fitting and bounding.
134              
135             =item L
136              
137             Input/Output plugin mechanism.
138              
139             =back
140              
141             All of the backend IO::* modules are optional, and will be automagically
142             discovered as they are installed. See L for details.
143              
144             =cut
145             ########################################################################
146              
147             =head1 Constructor
148              
149             =head2 new
150              
151             Returns a blessed reference to a new CAD::Drawing object.
152              
153             $drw = CAD::Drawing->new(%options);
154              
155             %options becomes a part of the data structure, so be careful what you
156             %ask for, because you'll get it (I check nothing!)
157              
158             =over
159              
160             =item Currently useful options:
161              
162             =item nocolortrack => 1
163              
164             Disables loading of colortrack hash (breaking select by color methods,
165             but saving a few milliseconds of time on big drawings.)
166              
167             =item isbig => 1
168              
169             Stores geometry data in package global variables (one per object.) This
170             allows programs to exit more quickly, but will result in memory leaks if
171             used inside of a loop. Do not use this option if you expect the memory
172             used by the object to be freed when it goes out of scope.
173              
174             The rule of thumb is:
175              
176             my $drw = CAD::Drawing->new(); # lexically scoped (in a loop or sub)
177             or
178             $drw = CAD::Drawing->new(isbig=>1); # $main::drw
179              
180             =back
181              
182             =cut
183              
184             sub new {
185             my $caller = shift;
186             my $class = ref($caller) || $caller;
187             my $self = {@_};
188             if($self->{isbig}) {
189             # this is clunky, but saves -_#*HUGE*#_- on big drawings!
190             $CAD::Drawing::geometry_data{$self} = {};
191             $self->{g} = $CAD::Drawing::geometry_data{$self};
192             $CAD::Drawing::color_tracking{$self} = {};
193             $self->{colortrack} = $CAD::Drawing::color_tracking{$self};
194             delete($self->{isbig});
195             }
196             bless($self, $class);
197             return($self);
198             } # end subroutine new definition
199             ########################################################################
200              
201             =head1 add functions
202              
203             All of these take a small set of required arguments and a reference to
204             an options hash.
205              
206             The standard options are as follows:
207              
208             layer => $layername
209             color => $color (as name or number (0-256))
210             linetype => $linetype (marginally supported in most formats)
211             id => $id
212              
213             =head2 addline
214              
215             Add a line between @pts. No special options.
216              
217             @pts = ([$x1, $y1], [$x2, $y2]);
218             $drw->addline(\@pts, \%opts);
219              
220             =cut
221             sub addline {
222             my $self = shift;
223             my ($points, $opts) = @_;
224             (scalar(@$points) == 2) or carp("cannot draw line without 2 points");
225             my $obj;
226             ($obj, $opts) = $self->setdefaults("lines", $opts);
227             ## print ref($obj), " keys: ", join(" ", keys(%{$obj})), "\n";
228             ## print "$obj->{addr}{layer}\n";
229             # print "pretty color: $obj->{color}\n";
230             $obj->{pts} = [map({[@{$_}]} @$points)];
231             return($obj->{addr});
232             } # end subroutine addline definition
233             ########################################################################
234              
235             =head2 add_x
236              
237             Adds an "X" to the drawing, with the intersection at @pt and each of the
238             two legs having $length at $opt{ang}.
239              
240             @lines = $drw->add_x(\@pt, $length, \%opt);
241              
242             =cut
243             sub add_x {
244             my $self = shift;
245             my ($pt, $length, $opt) = @_;
246             my %options;
247             (ref($opt) eq "HASH") && (%options = %$opt);
248             my $ang = $options{ang};
249             if(defined($ang)) {
250             my @ick = ($ang, 0);
251             checkarcangs(\@ick);
252             $ang = $ick[0];
253             }
254             else {
255             $ang = 0;
256             }
257             my @diff = map({$length * $_} cos($ang), sin($ang));
258              
259             my @pts = (
260             [map({$pt->[$_] + $diff[$_]} 0..1)],
261             [map({$pt->[$_] - $diff[$_]} 0..1)],
262             );
263             my @ret = ($self->addline(\@pts, {%$opt}));
264             push(@ret, $self->addline(\@pts, {%$opt}));
265             $self->Rotate($ret[1], "90d", $pt);
266             ## print "adding lines at $ret[0]{id} $ret[1]{id}\n";exit;
267             return(@ret);
268             } # end subroutine add_x definition
269             ########################################################################
270              
271             =head2 add_fake_ray
272              
273             Adds an open polyline which has a small hook (nubbin) at one end. This
274             can be used to represent a directional line (vector.)
275              
276             $drw->add_fake_ray(\@pts, \%opts);
277              
278             Options are the same as for addpolygon but closed is forced to false.
279              
280             =cut
281             sub add_fake_ray {
282             my $self = shift;
283             my ($points, $opt) = @_;
284             my %opts;
285             (ref($opt) eq "HASH") && (%opts = %$opt);
286             # maybe we should allow three, since we actually use three?
287             (scalar(@$points) == 2) or croak("cannot draw ray without 2 points");
288             # use a percentage of length, with a 15deg rotation ccw from
289             # reversed direction (later, add options.)
290             my $portion = 0.05;
291             my $rotate = $pi / 12;
292             my $rev = NewVec(line_vec(@$points)->ScalarMult($portion * -1));
293             my $length = $rev->Length();
294             my $ang = $rev->Ang() + $rotate;
295             my $vec = unit_angle($ang);
296             $vec = NewVec($vec->ScalarMult($length));
297             my @end = $vec->Plus($points->[1]);
298             $opts{closed} = 0;
299             return($self->addpolygon([@$points, \@end], \%opts));
300             } # end subroutine add_fake_ray definition
301             ########################################################################
302              
303             =head2 addpolygon
304              
305             Add a polyline through (2D) @points.
306              
307             %opts = ( closed => BOOLEAN );
308             $drw->addpolygon(\@points, \%opts);
309              
310             =cut
311             sub addpolygon {
312             my $self = shift;
313             my ($points, $opts) = @_;
314             (ref($points) eq "ARRAY") or carp("$points is not ARRAY\n");
315             (scalar(@$points) > 1) or
316             carp("cannot draw pline without 2 or more points");
317             my $obj;
318             ($obj, $opts) = $self->setdefaults("plines", $opts);
319             $obj->{pts} = [map({[@{$_}]} @$points)];
320             ## defined($opts->{closed}) && print "closed is $obj->{closed}\n";
321             unless(defined($opts->{closed})) {
322             ## print "closing\n";
323             ($#$points > 1) && ($obj->{closed} = 1);
324             }
325             return($obj->{addr});
326             } # end subroutine addpolygon definition
327             ########################################################################
328              
329             =head2 addrec
330              
331             A shortcut to addpolygon. Specify the opposite corners with @rec, which
332             will look like a diagonal line of the rectangle.
333              
334             @rec = ( [$x1, $y1], [$x2, $y2] );
335              
336             $drw->addrec(\@rec, $opts);
337              
338             =cut
339             sub addrec {
340             my $self = shift;
341             my ($rec, $opts) = @_;
342             (ref($opts) eq "HASH") || ($opts = {});
343             my @rec = @$rec; # expect this to be of the form: ([x,y],[x,y])
344             my @points = (
345             [ $rec[0][0], $rec[0][1] ],
346             [ $rec[1][0], $rec[0][1] ],
347             [ $rec[1][0], $rec[1][1] ],
348             [ $rec[0][0], $rec[1][1] ]
349             );
350             $opts->{closed} = 1; # sounds fair
351             return($self->addpolygon(\@points, $opts) );
352             } # end subroutine addrec definition
353             ########################################################################
354              
355             =head2 addtext
356              
357             Adds text $string at @pt. Height should be specified in $opts{height},
358             which may contain font and other options in the future.
359              
360             $drw->addtext(\@pt, $string, \%opts);
361              
362             =cut
363             sub addtext {
364             my $self = shift;
365             my ($point, $string, $opts) = @_;
366             my ($obj) = $self->setdefaults("texts", $opts);
367             $obj->{pt} = [@$point];
368             $obj->{string} = $string;
369             # print "adding text string: $string\n";
370             # If I let setdefaults pass all options into $obj,
371             # I don't even have to worry about them here!
372             $obj->{height} || ($obj->{height} = 1);
373             return($obj->{addr});
374             } # end subroutine addtext definition
375             ########################################################################
376              
377             =head2 addtextlines
378              
379             Returns @addr_list for new entities.
380              
381             Similar to the syntax of addtext() , but @point is the insert point for
382             the top line. The %opts hash should contain at least 'height' and
383             'spacing', and can also include 'layer', 'color', and 'linetype' (but
384             defaults can be automatically set for all of these.)
385              
386             $drw->addtextlines(\@point, "string\nstring\n", \%opts);
387              
388             =cut
389             sub addtextlines {
390             my $self = shift;
391             my($point, $string, $opts) = @_;
392             my @point = @$point;
393             (ref($opts) eq "HASH") || ($opts = {});
394             $opts = {%$opts}; # deref as much as possible
395             my $height = 1;
396             $opts->{height} || ($opts->{height} = $height);
397             $height = $opts->{height};
398             my $spacing = 1.67;
399             if($opts->{spacing}) {
400             $spacing = $opts->{spacing};
401             #delete($opts->{spacing});
402             }
403             my $y = $point[1];
404             my @retlist;
405             my @lines = split(/\015?\012/, $string);
406             # print scalar(@lines), " lines todo\n";
407             foreach my $line (@lines) {
408             if(length($line)) {
409             # print "line $line\n";
410             push(@retlist, $self->addtext([$point[0], $y], $line, $opts));
411             # print "okay\n";
412             }
413             $y -= $spacing * $height;
414             }
415             # warn "done";
416             return(@retlist);
417             } # end subroutine addtextlines definition
418             ########################################################################
419              
420             =head2 addtexttable
421              
422             @table is a 2D array of strings. $opts{spaces} must (currently)
423             contain a ref to a list of column widths.
424              
425             $drw->addtexttable(\@point, \@table, \%opts);
426              
427             =cut
428             sub addtexttable {
429             my $self = shift;
430             my($point, $table, $opts) = @_;
431             my @point = @$point;
432             my @table = @$table;
433             my %opts;
434             (ref($opts) eq "HASH") && (%opts = %$opts);
435             my @spaces = @{$opts{spaces}};
436             #delete($opts{spaces});
437             my $length = scalar(@spaces);
438             my @tcols;
439             for(my $col = 0; $col < $length; $col++) {
440             push(@tcols, join("\n", map({$_->[$col]} @table)));
441             }
442             my $x = $point[0];
443             my @pts = map({$x+=$_;[$x, $point[1]]} @spaces);
444             my @retlist;
445             for(my $col = 0; $col < @tcols; $col++) {
446             my $ad = $self->addtextlines($pts[$col], $tcols[$col], \%opts);
447             push(@retlist, $ad);
448             }
449             return(@retlist);
450             } # end subroutine addtexttable definition
451             ########################################################################
452              
453             =head2 addpoint
454              
455             $drw->addpoint(\@pt, \%opts);
456              
457             =cut
458             sub addpoint {
459             my $self = shift;
460             my ($point, $opts) = @_;
461             my ($obj) = $self->setdefaults("points", $opts);
462             # print "saw: @$point\n";
463             $obj->{pt} = [@$point];
464             return($obj->{addr});
465             } # end subroutine addpoint definition
466             ########################################################################
467              
468             =head2 addcircle
469              
470             $drw->addcircle(\@pt, $rad, \%opts);
471              
472             =cut
473             sub addcircle {
474             my $self = shift;
475             my ($point, $rad, $opts) = @_;
476             my ($obj) = $self->setdefaults("circles", $opts);
477             $obj->{pt} = [@$point];
478             $obj->{rad} = $rad;
479             return($obj->{addr});
480             } # end subroutine addcircle definition
481             ########################################################################
482              
483             =head2 addarc
484              
485             $drw->addarc(\@pt, $rad, \@angs, \%opts);
486              
487             =cut
488             sub addarc {
489             my $self = shift;
490             my ($point, $rad, $angs, $opts) = @_;
491             my ($obj) = $self->setdefaults("arcs", $opts);
492             $obj->{pt} = [@$point];
493             $obj->{rad} = $rad;
494             $angs = [@$angs];
495             checkarcangs($angs);
496             $obj->{angs} = $angs;
497             return($obj->{addr});
498             } # end subroutine addarc definition
499             ########################################################################
500              
501             =head2 addimage
502              
503             $drw->addimage();
504              
505             =cut
506             sub addimage {
507             my $self = shift;
508             my ($point, $opts) = @_;
509             my ($obj) = $self->setdefaults("images", $opts);
510             $obj->{pt} = [@$point];
511             if($obj->{clipping}) {
512             $obj->{clipping} = [map({[@{$_}]} @{$obj->{clipping}}) ];
513             }
514             $obj->{vectors} = [map({[@{$_}]} @{$obj->{vectors}}) ];
515             $obj->{size} = [@{$obj->{size}}];
516             my $name;
517             unless($obj->{name}) {
518             $name = $obj->{fullpath};
519             $name =~ s/.*\\+//;
520             $obj->{name} = $name;
521             }
522             my $layer = $obj->{addr}{layer};
523             #print "adding image (name: $obj->{fullpath})\n";
524             push(@{$self->{imagetrack}{$layer}{$name}}, $obj->{addr});
525             return($obj->{addr});
526             } # end subroutine addimage definition
527             ########################################################################
528              
529             =head1 Query Functions
530              
531             =head2 getImgByName
532              
533             =cut
534             sub getImgByName {
535             my $self = shift;
536             my ($layer, $name) = @_;
537             if($self->{imagetrack}{$layer}{$name}) {
538             my @list = @{$self->{imagetrack}{$layer}{$name}};
539             #allow main to assume that there is only one
540             $#list || return($list[0]);
541             return(@list);
542             }
543             else {
544             return();
545             }
546             } # end subroutine getImgByName definition
547             ########################################################################
548              
549             =head2 getLayerList
550              
551             Deprecated. See list_layers().
552              
553             @list = $drw->getLayerList(\%opts);
554              
555             =cut
556             sub getLayerList {
557             my $self = shift;
558             return($self->list_layers(@_));
559             } # end subroutine getLayerList definition
560             ########################################################################
561              
562             =head2 list_layers
563              
564             Get list of layers in drawing with options as follows:
565              
566             %options = (
567             matchregex => qr/name/,
568             );
569             @list = $drw->list_layers(\%opts);
570              
571             =cut
572             sub list_layers {
573             my $self = shift;
574             my ($opts) = @_;
575             my @list;
576             @list = keys(%{$self->{g}});
577             my $reg = $opts->{matchregex};
578             if(ref($reg) eq "Regexp") {
579             # print "reg:\n";
580             @list = grep(/$reg/, @list);
581             }
582             return(@list);
583             } # end subroutine list_layers definition
584             ########################################################################
585              
586             =head2 addr_by_layer
587              
588             Returns a list of addresses for all objects on $layer.
589              
590             my @addr_list = $drw->addr_by_layer($layer);
591              
592             =cut
593             sub addr_by_layer {
594             my $self = shift;
595             return($self->getAddrByLayer(@_));
596             } # end subroutine addr_by_layer definition
597             ########################################################################
598              
599             =head2 getAddrByLayer
600              
601             deprecated
602              
603             =cut
604             sub getAddrByLayer {
605             my $self = shift;
606             my ($layer) = @_;
607             my $list = $self->select_addr({sl=>[$layer]});
608             # print "selected @$list addresses\n";
609             $#$list || return($list->[0]);
610             return(@$list);
611             } # end subroutine getAddrByLayer definition
612             ########################################################################
613              
614             =head2 addr_by_type
615              
616             Returns a list of addresses for $type entities on $layer.
617              
618             $drw->addr_by_type($layer, $type);
619              
620             =cut
621             sub addr_by_type {
622             my $self = shift;
623             return($self->getAddrByType(@_));
624             } # end subroutine addr_by_type definition
625             ########################################################################
626              
627             =head2 getAddrByType
628              
629             deprecated
630              
631             =cut
632             sub getAddrByType {
633             my $self = shift;
634             my ($layer, $type) = @_;
635             # my $list = $self->select_addr({sl=>[$layer],st=>[$type]});
636             # my @list = @$list;
637             # FIXME: is it better to have the speed and scatter this
638             # data structure all over?
639             my @list = map( {
640             {layer => $layer, type => $type, id => $_}
641             } keys(%{$self->{g}{$layer}{$type}})
642             );
643             # warn("list is ", scalar(@list), " elements long\n");
644             $#list || return($list[0]);
645             return(@list);
646             } # end subroutine getAddrByType definition
647             ########################################################################
648              
649             =head2 addr_by_regex
650              
651             @list = $drw->addr_by_regex($layer, qr/^model\s+\d+$/, $opts);
652              
653             =cut
654             sub addr_by_regex {
655             my $self = shift;
656             return($self->getAddrByRegex(@_));
657             } # end subroutine addr_by_regex definition
658             ########################################################################
659              
660             =head2 getAddrByRegex
661              
662             deprecated
663              
664             =cut
665             sub getAddrByRegex {
666             my $self = shift;
667             my ($layer, $regex, $opt) = @_;
668             my %opts;
669             (ref($opt) eq "HASH") && (%opts = %$opt);
670             (ref($regex) eq "Regexp") ||
671             croak("getAddrByRegex needs precompiled regex");
672             my @list = $self->getAddrByType($layer, "texts");
673             my @out;
674             foreach my $addr (@list) {
675             my $obj = $self->getobj($addr);
676             if($obj->{string} =~ $regex) {
677             $opts{"sub"} && ($opts{"sub"}->($obj->{string}, $regex) );
678             push(@out, $addr);
679             }
680             }
681             $#out || return($out[0]);
682             return(@out);
683             } # end subroutine getAddrByRegex definition
684             ########################################################################
685              
686             =head2 addr_by_color
687              
688             @list = $drw->addr_by_color($layer, $type, $color);
689              
690             =cut
691             sub addr_by_color {
692             my $self = shift;
693             return($self->getAddrByColor(@_));
694             } # end subroutine addr_by_color definition
695             ########################################################################
696              
697             =head2 getAddrByColor
698              
699             deprecated
700              
701             =cut
702             sub getAddrByColor {
703             my $self = shift;
704             my ($layer, $type, $color) = @_;
705             $self->{nocolortrack} && croak("nocolortrack kills getAddrByColor");
706             # my %select = (
707             # sl=>[$layer],
708             # st=>[$type],
709             # sc=>[$color]
710             # );
711             # my $list = $self->select_addr(\%select);
712             # my @list = @$list;
713             $color = color_translate($color);
714             # print "looking for $color on $layer for $type\n";
715             # print "existing colors: ",
716             # join(" ", keys(%{$self->{colortrack}{$layer}{$type}})), "\n";
717             my @list;
718             if(my $list = $self->{colortrack}{$layer}{$type}{$color}) {
719             @list = @$list;
720             }
721             $#list || return($list[0]);
722             # print "returning array\n";
723             return(@list);
724             } # end subroutine getAddrByColor definition
725             ########################################################################
726              
727             =head2 getEntPoints
728              
729             Returns the point or points found at $addr as a list.
730              
731             If the entity has only one point, the list will be (x,y,z), while a
732             many-pointed entity will give a list of the form ([x,y,z],[x,y,z]...)
733              
734             $drw->getEntPoints($addr);
735              
736             =cut
737             sub getEntPoints {
738             my $self = shift;
739             my ($addr) = @_;
740             my $obj = $self->getobj($addr);
741             #my $obj = $self->{g}{$addr->{layer}}{$addr->{type}}{$addr->{id}};
742             if($obj->{pts}) {
743             return(map({[@{$_}]} @{$obj->{pts}}));
744             }
745             elsif($obj->{pt}) {
746             return(@{$obj->{pt}});
747             }
748             else {
749             return();
750             }
751             } # end subroutine getEntPoints definition
752             ########################################################################
753              
754             =head2 addr_by_id
755              
756             $drw->addr_by_id($layer, $type, $id);
757              
758             =cut
759             sub addr_by_id {
760             my $self = shift;
761             my ($l, $t, $id) = @_;
762             if($self->{g}{$l}{$t}{$id}) {
763             return({layer => $l, type => $t, id => $id});
764             }
765             return();
766             } # end subroutine addr_by_id definition
767             ########################################################################
768              
769             =head2 Get
770              
771             Gets the thing from entity found at $addr.
772              
773             Returns the value of the thing (even if it is a reference) with the
774             exception of things that start with "p", which will result in a call to
775             getEntPoints (and return a list.)
776              
777             $drw->Get("thing", $addr);
778              
779             =cut
780             sub Get {
781             my $self = shift;
782             my ($req, $addr) = @_;
783             ($req =~ m/^p(t|oi)/i) && return( $self->getEntPoints($addr));
784             ($req =~ m/^defin/i) && return($self->getobj($addr));
785             my $obj = $self->getobj($addr);
786             if(defined(my $thing = $obj->{$req})) {
787             return($thing);
788             }
789             else {
790             return();
791             }
792             } # end subroutine Get definition
793             ########################################################################
794              
795             =head2 Set
796              
797             $drw->Set(\%items, $addr);
798              
799             =cut
800             sub Set {
801             my $self = shift;
802             my ($items, $addr) = @_;
803             my $obj = $self->getobj($addr);
804             $obj or croak("no object for that address\n");
805             foreach my $key (%{$items}) {
806             $obj->{$key} = $items->{$key};
807             }
808             } # end subroutine Set definition
809             ########################################################################
810              
811             =head1 Internal Functions
812              
813             =head2 setdefaults
814              
815             internal use only
816              
817             Performs in-place modification on \%opts and creates a new place for an
818             entity of $type to live on $opt->{layer} with id $opts->{id} (opts are
819             optional.)
820              
821             $drw->setdefaults($type, $opts);
822              
823             =cut
824             sub setdefaults {
825             my $self = shift;
826             my ($type, $opts) = @_;
827             (ref($opts) eq "HASH") || ($opts = {});
828             # foreach my $key (@defaultkeys) {
829             # defined($opts->{$key}) || ($opts->{$key} = $defaults{$key});
830             # }
831             defined($opts->{layer}) || ($opts->{layer} = $defaults{layer});
832             defined($opts->{color}) || ($opts->{color} = $defaults{color});
833             defined($opts->{linetype}) || ($opts->{linetype} = $defaults{linetype});
834             my $layer = $opts->{layer};
835             # FIXME: I do not really like making the color stupid,
836             # FIXME: but this seems to be the best place for it.
837             $opts->{color} = color_translate($opts->{color});
838             my $color = $opts->{color};
839             # print "color: $color\n";
840             my $id = $opts->{id};
841             unless(defined($id)) {
842             $id = 0;
843             my $was_id = $id;
844             my $limit = 5;
845             my $rep = 0;
846             while($self->{g}{$layer}{$type}{$id}) {
847             $id = $self->{lastid}{$layer}{$type} + 1;
848             ($id == $was_id) && $id++;
849             $was_id = $id;
850             # print "id: $id\n";
851             $rep++;
852             if($rep > $limit) {
853             $rep = 0;
854             $id+= 2;
855             $self->{lastid}{$layer}{$type} = $id;
856             }
857             }
858             $opts->{id} = $id;
859             }
860             else {
861             if($self->{g}{$layer}{$type}{$id}) {
862             # croak("id $id is not unique!");
863             while($self->{g}{$layer}{$type}{$id}) {
864             $id .= ".";
865             # print "now id $id\n";
866             }
867             }
868             }
869             # print "$layer ($type) id: $id\n";
870             $self->{lastid}{$layer}{$type} = $id;
871             my %addr = (
872             "layer" => $opts->{layer},
873             "type" => $type,
874             "id" => $id,
875             );
876             # cleanup the options hash:
877             delete($opts->{layer});
878             delete($opts->{id});
879             # print "self: ", join(" ", keys(%{$self->{g}{0}{$type}})), "\n";
880             # $self->{colortrack}{$layer}{$type}{$color} ||
881             # ($self->{colortrack}{$layer}{$type}{$color} = []);
882              
883              
884             # FIXME: color could likely be an array index here:
885             $self->{nocolortrack} ||
886             push(@{$self->{colortrack}{$layer}{$type}{$color}}, \%addr);
887             $self->{g}{$layer}{$type}{$id} = {
888             "color" => $opts->{color},
889             "linetype" => $opts->{linetype},
890             "addr" => \%addr,
891             %{$opts}, # allows arbitrary options (not sure if this is good)
892             };
893             # print "self: ", join(" ", keys(%{$self->{g}{0}{$type}})), "\n";
894             return($self->{g}{$layer}{$type}{$id}, $opts);
895             } # end subroutine setdefaults definition
896             ########################################################################
897              
898             =head2 getobj
899              
900             Internal use only.
901              
902             Returns a reference to the entity found at $addr.
903              
904             $drw->getobj($addr);
905              
906             =cut
907             sub getobj {
908             my $self = shift;
909             my ($addr) = @_;
910             return($self->{g}{$addr->{layer}}{$addr->{type}}{$addr->{id}});
911             } # end subroutine getobj definition
912             ########################################################################
913              
914             =head2 remove
915              
916             Removes the entity at $addr from the data structure.
917              
918             $drw->remove($addr);
919              
920             =cut
921             sub remove {
922             my $self = shift;
923             my ($addr) = @_;
924             if($self->{colortrack}) {
925             # must find this in the colortrack array:
926             # find based on converting a hash reference into a text string
927             # was a fatal assumption, now this does the thorough check
928             my $color = $self->Get("color", $addr);
929             my $list =
930             $self->{colortrack}{$addr->{layer}}{$addr->{type}}{$color};
931             my $rem = 0;
932             for(my $i = 0; $i < @$list; $i++) {
933             if(
934             ($list->[$i]{id} == $addr->{id}) and
935             ($list->[$i]{layer} eq $addr->{layer}) and
936             ($list->[$i]{type} eq $addr->{type})
937             ) {
938             my $removed = splice(@$list, $i, 1);
939             $rem++;
940             # print "killed color tracking element $i\n";
941             }
942             }
943             $rem or
944             warn("colortrack removal failure may cause later death");
945             }
946             delete($self->{g}{$addr->{layer}}{$addr->{type}}{$addr->{id}});
947              
948             } # end subroutine remove definition
949             ########################################################################
950              
951             =head2 select_addr
952              
953             Selects geometric entities from the Drawing object based on the hash
954             key-value pairs. Aside from the options supported by check_select()
955             this also supports the option "all", which, if true, will select all
956             entities (this is the default if no hash reference is passed.)
957              
958             Furthermore, if you already have in-hand a list of addresses, if the
959             reference passed is actually an array reference, it will be returned
960             directly, or you can store this in $opts{addr_list} and that list will
961             be returned. This allows you to pass the list directly as part of a
962             larger set of options, or by itself.
963              
964             $drw->select_addr(\%opts);
965              
966             =cut
967             sub select_addr {
968             my $self = shift;
969             my ($opt) = @_;
970             my @outlist;
971             if(ref($opt) eq "ARRAY") {
972             return([@$opt]);
973             }
974             my %opts;
975             if(ref($opt) eq "HASH") {
976             %opts = %$opt;
977             }
978             else {
979             $opts{all} = 1;
980             }
981             $opts{addr_list} && return($opts{addr_list});
982             my ($s, $n);
983             $opts{all} || (($s, $n) = check_select(\%opts));
984             my @layers_to_check = keys(%{$self->{g}});
985             $s->{l} && (@layers_to_check = keys(%{$s->{l}}));
986             # print "checking @layers_to_check\n";
987             foreach my $layer (@layers_to_check) {
988             $n->{l} && ($n->{l}{$layer} && next);
989             foreach my $type (keys(%{$self->{g}{$layer}})) {
990             # print "$layer $type\n";
991             $s->{t} && ($s->{t}{$type} || next);
992             $n->{t} && ($n->{t}{$type} && next);
993            
994             if($s->{c} or $n->{c} or $s->{lt} or $n->{lt}) {
995             my @idlist = keys(%{$self->{g}{$layer}{$type}});
996             if($s->{c} && (! $self->{nocolortrack})) {
997             # yes, this is a bit complex, but it will shorten the list
998             @idlist = ();
999             map({
1000             push(@idlist,
1001             map({$_->{id}}
1002             @{$self->{colortrack}{$layer}{$type}{$_}}
1003             ) # end map :)
1004             )
1005             } keys(%{$s->{c}})
1006             ); # end map :(
1007             } # end if we can just grab colortrack list
1008             foreach my $id ( @idlist ) {
1009             my %addr = (
1010             "layer" => $layer,
1011             "type" => $type,
1012             "id" => $id,
1013             );
1014             my $obj = $self->getobj(\%addr);
1015             my $color = $obj->{color};
1016             $s->{c} && ($s->{c}{$color} || next);
1017             $n->{c} && ($n->{c}{$color} && next);
1018             # FIXME: this is getting bad:
1019             my $linetype = $obj->{linetype};
1020             $s->{lt} && ($s->{lt}{$linetype} || next);
1021             $n->{lt} && ($n->{lt}{$linetype} && next);
1022             # print "select color: $color\n";
1023             push(@outlist, \%addr);
1024             } # end foreach $id
1025             } # end if select by color or linetype
1026             else {
1027             push(@outlist,
1028             map({
1029             {"layer" => $layer,
1030             "type" => $type,
1031             "id" => $_ }
1032             } keys(%{$self->{g}{$layer}{$type}})
1033             ) # end map :)
1034             ); # end push :)
1035             } # end else
1036             } # end foreach $type
1037             } # end foreach $layer
1038             return(\@outlist);
1039             } # end subroutine select_addr definition
1040             ########################################################################
1041              
1042              
1043              
1044             1;